| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-18 00:56:20 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-18 00:56:20 UTC |
| parent | 3f79d2b8ae7982cf0696a6929f4a1d60990bf704 |
| borogove/Client.hx | +17 | -11 |
| test/TestClient.hx | +48 | -0 |
diff --git a/borogove/Client.hx b/borogove/Client.hx index c545f52..3dbaaa5 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -589,17 +589,7 @@ class Client extends EventEmitter { } } - for (receipt in stanza.allTags("received", "urn:xmpp:receipts")) { - final id = receipt.attr.get("id"); - if (id != null) { - persistence.updateMessageStatus( - this.accountId(), - id, - MessageDeliveredToDevice, - null - ).then((m) -> notifyMessageHandlers(m, StatusEvent), _ -> null); - } - } + checkForReceipts(stanza); final pubsubEvent = PubsubEvent.fromStanza(stanza); if (pubsubEvent != null && pubsubEvent.getFrom() != null && pubsubEvent.getNode() == "urn:xmpp:avatar:metadata" && pubsubEvent.getItems().length > 0) { @@ -1759,6 +1749,20 @@ class Client extends EventEmitter { sendQuery(pubsubGet); } + private function checkForReceipts(stanza: Stanza) { + for (receipt in stanza.allTags("received", "urn:xmpp:receipts")) { + final id = receipt.attr.get("id"); + if (id != null) { + persistence.updateMessageStatus( + this.accountId(), + id, + MessageDeliveredToDevice, + null + ).then((m) -> notifyMessageHandlers(m, StatusEvent), _ -> null); + } + } + } + private function sync(?callback: (Bool)->Void) { if (Std.isOfType(persistence, borogove.persistence.Dummy)) { callback(true); // No reason to sync if we're not storing anyway @@ -1823,6 +1827,8 @@ class Client extends EventEmitter { ).then(m -> [m], _ -> [])); case MucInviteStanza(serverId, serverIdBy, reason, password): mucInvite(m.chatId, getChat(m.chatId), m.senderId, m.threadId, serverId, serverIdBy, reason, password); + case UnknownMessageStanza(stanza): + checkForReceipts(stanza); default: // ignore } diff --git a/test/TestClient.hx b/test/TestClient.hx index 3e1ee00..25b65b2 100644 --- a/test/TestClient.hx +++ b/test/TestClient.hx @@ -286,13 +286,61 @@ class TestClient extends utest.Test { client.stream.onStanza(receiptStanza); } + + public function testHandleReceiptInSync(async: Async) { + final persistence = new MockPersistence(); + final client = new Client("test@example.com", persistence); + + client.stream.on("sendStanza", (stanza: Stanza) -> { + final query = stanza.findChild("{urn:xmpp:mam:2}query"); + if (stanza.name == "iq" && query != null) { + final queryId = query.attr.get("queryid"); + + final receiptStanza = new Stanza("message", { xmlns: "jabber:client", from: "bob@example.com", to: "test@example.com" }) + .tag("received", { xmlns: "urn:xmpp:receipts", id: "msg-id" }).up(); + + final mamResult = new Stanza("message", { xmlns: "jabber:client", to: "test@example.com", from: "test@example.com" }) + .tag("result", { xmlns: "urn:xmpp:mam:2", queryid: queryId, id: "mam-id-1" }) + .tag("forwarded", { xmlns: "urn:xmpp:forward:0" }) + .tag("delay", { xmlns: "urn:xmpp:delay", stamp: "2023-01-01T00:00:00Z" }).up() + .addChild(receiptStanza) + .up() + .up(); + + client.stream.onStanza(mamResult); + + final finishedIq = new Stanza("iq", { xmlns: "jabber:client", type: "result", id: stanza.attr.get("id"), from: "test@example.com" }) + .tag("fin", { xmlns: "urn:xmpp:mam:2", complete: "true" }) + .tag("set", { xmlns: "http://jabber.org/protocol/rsm" }) + .up() + .up(); + client.stream.onStanza(finishedIq); + } + return EventHandled; + }); + + client.on("message/new", (data: { message: ChatMessage, event: ChatMessageEvent }) -> { + if (data.event == StatusEvent) { + Assert.equals("msg-id", data.message.localId); + Assert.equals(MessageDeliveredToDevice, data.message.status); + } + return EventHandled; + }); + + client.doSync((_) -> { + Assert.equals(MessageDeliveredToDevice, persistence.statusUpdates.get("msg-id")); + async.done(); + }, null); + } } @:access(borogove) class MockPersistence extends Dummy { + public var statusUpdates: Map<String, MessageStatus> = []; public function new() { super(); } override public function updateMessageStatus(accountId: String, localId: String, status:MessageStatus, statusText: Null<String>): Promise<ChatMessage> { + statusUpdates.set(localId, status); final builder = new ChatMessageBuilder(); builder.localId = localId; builder.status = status;