git » sdk » commit 53e7aae

Send receipts to trusted chats

author Stephen Paul Weber
2026-04-18 01:15:13 UTC
committer Stephen Paul Weber
2026-04-18 01:15:13 UTC
parent 7a77cb8c8e027bf49ad7a5f0a4e3f6c6db1cce2b

Send receipts to trusted chats

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)