git » sdk » commit 4ca1ca4

Support inbound message correction

author Stephen Paul Weber
2023-11-09 03:34:43 UTC
committer Stephen Paul Weber
2023-11-09 03:34:43 UTC
parent 333b32d491c25365a7be41842c65b0f2faf263f7

Support inbound message correction

xmpp/ChatMessage.hx +1 -0
xmpp/Client.hx +16 -6
xmpp/Persistence.hx +1 -0
xmpp/persistence/browser.js +44 -16

diff --git a/xmpp/ChatMessage.hx b/xmpp/ChatMessage.hx
index 50c8dc3..3e1eeb9 100644
--- a/xmpp/ChatMessage.hx
+++ b/xmpp/ChatMessage.hx
@@ -39,6 +39,7 @@ class ChatMessage {
 	public var lang (default, null): Null<String> = null;
 
 	public var direction: MessageDirection = MessageReceived;
+	public var versions: Array<ChatMessage> = [];
 
 	public function new() { }
 
diff --git a/xmpp/Client.hx b/xmpp/Client.hx
index f0d0eaa..f65729f 100644
--- a/xmpp/Client.hx
+++ b/xmpp/Client.hx
@@ -108,13 +108,23 @@ class Client extends xmpp.EventEmitter {
 				var chat = getChat(chatMessage.chatId());
 				if (chat == null && stanza.attr.get("type") != "groupchat") chat = getDirectChat(chatMessage.chatId());
 				if (chat != null) {
+					final updateChat = (chatMessage) -> {
+						if (chatMessage.versions.length < 1 || chat.lastMessageId() == chatMessage.serverId || chat.lastMessageId() == chatMessage.localId) {
+							chat.setLastMessage(chatMessage);
+							if (chatMessage.versions.length < 1) chat.setUnreadCount(chatMessage.isIncoming() ? chat.unreadCount() + 1 : 0);
+							chatActivity(chat);
+						}
+						for (handler in chatMessageHandlers) {
+							handler(chatMessage);
+						}
+					};
 					chatMessage = chat.prepareIncomingMessage(chatMessage, stanza);
-					chat.setLastMessage(chatMessage);
-					chat.setUnreadCount(chatMessage.isIncoming() ? chat.unreadCount() + 1 : 0);
-					if (chatMessage.serverId != null) persistence.storeMessage(accountId(), chatMessage);
-					chatActivity(chat);
-					for (handler in chatMessageHandlers) {
-						handler(chatMessage);
+					final replace = stanza.getChild("replace", "urn:xmpp:message-correct:0");
+					if (replace == null || replace.attr.get("id") == null) {
+						if (chatMessage.serverId != null) persistence.storeMessage(accountId(), chatMessage);
+						updateChat(chatMessage);
+					} else {
+						persistence.correctMessage(accountId(), replace.attr.get("id"), chatMessage, updateChat);
 					}
 				}
 			}
diff --git a/xmpp/Persistence.hx b/xmpp/Persistence.hx
index a252aee..febda3e 100644
--- a/xmpp/Persistence.hx
+++ b/xmpp/Persistence.hx
@@ -10,6 +10,7 @@ abstract class Persistence {
 	abstract public function getChats(accountId: String, callback: (chats:Array<SerializedChat>)->Void):Void;
 	abstract public function getChatsUnreadDetails(accountId: String, chats: Array<Chat>, callback: (details:Array<{ chatId: String, message: ChatMessage, unreadCount: Int }>)->Void):Void;
 	abstract public function storeMessage(accountId: String, message: ChatMessage):Void;
+	abstract public function correctMessage(accountId: String, localId: String, message: ChatMessage, callback: (ChatMessage)->Void):Void;
 	abstract public function getMessages(accountId: String, chatId: String, beforeId: Null<String>, beforeTime: Null<String>, callback: (messages:Array<ChatMessage>)->Void):Void;
 	abstract public function getMediaUri(hashAlgorithm:String, hash:BytesData, callback: (uri:Null<String>)->Void):Void;
 	abstract public function storeMedia(mime:String, bytes:BytesData, callback: ()->Void):Void;
diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js
index 1ba4e84..1094cfb 100644
--- a/xmpp/persistence/browser.js
+++ b/xmpp/persistence/browser.js
@@ -74,9 +74,30 @@ exports.xmpp.persistence = {
 			message.text = value.text;
 			message.lang = value.lang;
 			message.direction = value.direction == "MessageReceived" ? xmpp.MessageDirection.MessageReceived : xmpp.MessageDirection.MessageSent;
+			message.versions = (value.versions || []).map(hydrateMessage);
 			return message;
 		}
 
+		function serializeMessage(account, message) {
+			return {
+				...message,
+				serverId: message.serverId || "",
+				serverIdBy: message.serverIdBy || "",
+				localId: message.localId || "",
+				syncPoint: !!message.syncPoint,
+				account: account,
+				chatId: message.chatId(),
+				to: message.to?.asString(),
+				from: message.from?.asString(),
+				sender: message.sender?.asString(),
+				recipients: message.recipients.map((r) => r.asString()),
+				replyTo: message.replyTo.map((r) => r.asString()),
+				timestamp: new Date(message.timestamp),
+				direction: message.direction.toString(),
+				versions: message.versions.map((m) => serializeMessage(account, m)),
+			}
+		}
+
 		return {
 			test: function() {
 				//messages = upgradeDb.createObjectStore("messages", { keyPath: ["account", "serverIdBy", "serverId", "localId"] });
@@ -197,25 +218,32 @@ exports.xmpp.persistence = {
 				promisifyRequest(store.index("localId").get([account, message.chatId(), message.localId || []])).then((result) => {
 					if (result && !message.isIncoming() && result.direction === "MessageSent") return; // duplicate, we trust our own stanza ids
 
-					store.put({
-						...message,
-						serverId: message.serverId || "",
-						serverIdBy: message.serverIdBy || "",
-						localId: message.localId || "",
-						syncPoint: !!message.syncPoint,
-						account: account,
-						chatId: message.chatId(),
-						to: message.to?.asString(),
-						from: message.from?.asString(),
-						sender: message.sender?.asString(),
-						recipients: message.recipients.map((r) => r.asString()),
-						replyTo: message.replyTo.map((r) => r.asString()),
-						timestamp: new Date(message.timestamp),
-						direction: message.direction.toString()
-					});
+					store.put(serializeMessage(account, message));
 				});
 			},
 
+			correctMessage: function(account, localId, message, callback) {
+				const tx = db.transaction(["messages"], "readwrite");
+				const store = tx.objectStore("messages");
+				const cursor = store.index("localId").openCursor(IDBKeyRange.only([account, message.chatId(), localId]));
+				cursor.onsuccess = (event) => {
+					if (event.target.result?.value && event.target.result.value.sender == message.senderId()) {
+						// Note, this strategy loses the ids of the replacement messages
+						const withAnnotation = serializeMessage(account, message);
+						withAnnotation.serverIdBy = event.target.result.value.serverIdBy;
+						withAnnotation.serverId = event.target.result.value.serverId;
+						withAnnotation.localId = event.target.result.value.localId;
+						withAnnotation.versions = [{ ...event.target.result.value, versions: [] }].concat(event.target.result.value.versions || [])
+						event.target.result.update(withAnnotation);
+						callback(hydrateMessage(withAnnotation));
+					} else {
+						this.storeMessage(account, message);
+						callback(message);
+					}
+				};
+				cursor.onerror = console.error;
+			},
+
 			getMessages: function(account, chatId, beforeId, beforeTime, callback) {
 				const beforeDate = beforeTime ? new Date(beforeTime) : [];
 				const tx = db.transaction(["messages"], "readonly");