| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-13 01:19:22 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-13 01:19:22 UTC |
| parent | 1b44c7b382a10357b97396526bd91d807d22be32 |
| borogove/ChatMessage.hx | +3 | -0 |
| borogove/Client.hx | +12 | -0 |
| test/TestChatMessageBuilder.hx | +28 | -1 |
| test/TestClient.hx | +41 | -1 |
diff --git a/borogove/ChatMessage.hx b/borogove/ChatMessage.hx index e5a15f5..d1d3483 100644 --- a/borogove/ChatMessage.hx +++ b/borogove/ChatMessage.hx @@ -599,6 +599,9 @@ class ChatMessage { for (payload in payloads) { stanza.addDirectChild(Element(payload)); } + if (type == MessageChat) { + stanza.tag("request", { xmlns: "urn:xmpp:receipts" }).up(); + } return stanza; } } diff --git a/borogove/Client.hx b/borogove/Client.hx index 21b0bfa..c545f52 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -589,6 +589,18 @@ 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); + } + } + final pubsubEvent = PubsubEvent.fromStanza(stanza); if (pubsubEvent != null && pubsubEvent.getFrom() != null && pubsubEvent.getNode() == "urn:xmpp:avatar:metadata" && pubsubEvent.getItems().length > 0) { final item = pubsubEvent.getItems()[0]; diff --git a/test/TestChatMessageBuilder.hx b/test/TestChatMessageBuilder.hx index 0f74653..4dd814f 100644 --- a/test/TestChatMessageBuilder.hx +++ b/test/TestChatMessageBuilder.hx @@ -3,8 +3,9 @@ package test; import utest.Assert; import utest.Async; -import borogove.Html; import borogove.ChatMessageBuilder; +import borogove.Html; +import borogove.JID; @:access(borogove) class TestChatMessageBuilder extends utest.Test { @@ -96,4 +97,30 @@ class TestChatMessageBuilder extends utest.Test { Assert.equals("*hello*", msgHtml.text); Assert.equals("<html xmlns=\"http://jabber.org/protocol/xhtml-im\"><body xmlns=\"http://www.w3.org/1999/xhtml\"><b>hello</b></body></html>", msgHtml.payloads[0].toString()); } + + public function testReceiptRequest() { + final builder = new ChatMessageBuilder(); + builder.localId = "test-id"; + builder.from = JID.parse("alice@example.com"); + builder.to = JID.parse("bob@example.com"); + builder.senderId = "alice@example.com"; + builder.type = MessageChat; + builder.setBody(Html.text("Hello")); + final msg = builder.build(); + final stanza = msg.asStanza(); + Assert.notNull(stanza.getChild("request", "urn:xmpp:receipts")); + } + + public function testNoReceiptRequestForGroupchat() { + final builder = new ChatMessageBuilder(); + builder.localId = "test-id"; + builder.from = JID.parse("alice@example.com"); + builder.to = JID.parse("room@example.com"); + builder.senderId = "alice@example.com"; + builder.type = MessageChannel; + builder.setBody(Html.text("Hello")); + final msg = builder.build(); + final stanza = msg.asStanza(); + Assert.isNull(stanza.getChild("request", "urn:xmpp:receipts")); + } } diff --git a/test/TestClient.hx b/test/TestClient.hx index f0cf8f6..3e1ee00 100644 --- a/test/TestClient.hx +++ b/test/TestClient.hx @@ -1,11 +1,16 @@ package test; +import thenshim.Promise; import utest.Assert; import utest.Async; +import borogove.Chat; +import borogove.ChatMessage; +import borogove.ChatMessageBuilder; import borogove.Client; +import borogove.JID; +import borogove.Message; import borogove.Stanza; -import borogove.Chat; import borogove.persistence.Dummy; using Lambda; @@ -262,4 +267,39 @@ class TestClient extends utest.Test { .textTag("nick", "Stranger", { xmlns: "http://jabber.org/protocol/nick" }) ); } + + public function testHandleReceipt(async: Async) { + final persistence = new MockPersistence(); + final client = new Client("test@example.com", persistence); + + 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); + async.done(); + } + return EventHandled; + }); + + 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(); + + client.stream.onStanza(receiptStanza); + } +} + +@:access(borogove) +class MockPersistence extends Dummy { + public function new() { super(); } + + override public function updateMessageStatus(accountId: String, localId: String, status:MessageStatus, statusText: Null<String>): Promise<ChatMessage> { + final builder = new ChatMessageBuilder(); + builder.localId = localId; + builder.status = status; + builder.from = JID.parse("bob@example.com"); + builder.to = JID.parse(accountId); + builder.senderId = "bob@example.com"; + builder.replyTo = [JID.parse("bob@example.com")]; + return Promise.resolve(builder.build()); + } }