| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-18 01:15:13 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-18 01:15:13 UTC |
| parent | 7a77cb8c8e027bf49ad7a5f0a4e3f6c6db1cce2b |
| borogove/Client.hx | +18 | -1 |
| test/TestClient.hx | +62 | -0 |
diff --git a/borogove/Client.hx b/borogove/Client.hx index 3dbaaa5..bcc8244 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -74,6 +74,7 @@ class Client extends EventEmitter { "http://jabber.org/protocol/disco#info", "http://jabber.org/protocol/caps", "urn:xmpp:caps", + "urn:xmpp:receipts", "urn:xmpp:avatar:metadata+notify", "http://jabber.org/protocol/nick+notify", "urn:xmpp:bookmarks:1+notify", @@ -1593,6 +1594,14 @@ class Client extends EventEmitter { private function notifyMessageHandlers(message: ChatMessage, event: ChatMessageEvent) { final chat = getChat(message.chatId()); if (chat != null && chat.isBlocked) return; // Don't notify blocked chats + + if (event == DeliveryEvent && message.type == MessageChat && message.localId != null && chat != null && chat.isTrusted()) { + sendStanza( + new Stanza("message", { to: message.from.asString(), type: "chat" }) + .tag("received", { xmlns: "urn:xmpp:receipts", id: message.localId }) + ); + } + this.trigger("message/new", { message: message, event: event }); } @@ -1601,6 +1610,14 @@ class Client extends EventEmitter { if (message == null || message.versions.length > 1) return; final chat = getChat(message.chatId()); if (chat != null && chat.isBlocked) return; // Don't notify blocked chats + + if (message.type == MessageChat && message.localId != null && chat != null && chat.isTrusted()) { + sendStanza( + new Stanza("message", { to: message.from.asString(), type: "chat" }) + .tag("received", { xmlns: "urn:xmpp:receipts", id: message.localId }) + ); + } + this.trigger("message/sync", message); } @@ -1839,7 +1856,7 @@ class Client extends EventEmitter { for (messages in results) { if (messages != null) { for (message in messages) { - this.trigger("message/sync", message); + notifySyncMessageHandlers(message); sortId = message.sortId; } } diff --git a/test/TestClient.hx b/test/TestClient.hx index 25b65b2..ba6fae8 100644 --- a/test/TestClient.hx +++ b/test/TestClient.hx @@ -332,6 +332,68 @@ class TestClient extends utest.Test { async.done(); }, null); } + + public function testSendReceipt(async: Async) { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + client.getDirectChat("bob@example.com").setTrusted(true); + + client.stream.on("sendStanza", (stanza: Stanza) -> { + if (stanza.name == "message" && stanza.getChild("received", "urn:xmpp:receipts") != null) { + Assert.equals("bob@example.com", stanza.attr.get("to")); + Assert.equals("msg123", stanza.getChild("received", "urn:xmpp:receipts").attr.get("id")); + async.done(); + return EventHandled; + } + return EventUnhandled; + }); + + client.stream.onStanza(new Stanza("message", { xmlns: "jabber:client", from: "bob@example.com", id: "msg123" }).textTag("body", "hello")); + } + + public function testSendReceiptSync(async: Async) { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + client.getDirectChat("bob@example.com").setTrusted(true); + + client.stream.on("sendStanza", (stanza: Stanza) -> { + if (stanza.name == "message" && stanza.getChild("received", "urn:xmpp:receipts") != null) { + Assert.equals("bob@example.com", stanza.attr.get("to")); + Assert.equals("sync123", stanza.getChild("received", "urn:xmpp:receipts").attr.get("id")); + async.done(); + return EventHandled; + } + return EventUnhandled; + }); + + client.stream.on("sendStanza", (stanza: Stanza) -> { + if (stanza.name == "iq" && stanza.findChild("{urn:xmpp:mam:2}query") != null) { + final queryId = stanza.findChild("{urn:xmpp:mam:2}query").attr.get("queryid"); + 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() + .tag("message", { xmlns: "jabber:client", from: "bob@example.com", id: "sync123" }) + .textTag("body", "sync message") + .up() + .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; + } + return EventUnhandled; + }); + + client.doSync((_) -> {}, null); + } } @:access(borogove)