| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-11-01 15:51:17 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-11-01 15:52:18 UTC |
| parent | 8791e454165bb520f9bd8fcf011c22857016499d |
| xmpp/Chat.hx | +13 | -0 |
| xmpp/ChatMessage.hx | +17 | -3 |
| xmpp/persistence/browser.js | +42 | -29 |
diff --git a/xmpp/Chat.hx b/xmpp/Chat.hx index b8d539e..2267c0b 100644 --- a/xmpp/Chat.hx +++ b/xmpp/Chat.hx @@ -215,7 +215,13 @@ class DirectChat extends Chat { public function sendMessage(message:ChatMessage):Void { client.chatActivity(this); + message.timestamp = message.timestamp ?? Date.format(std.Date.now()); + message.direction = MessageSent; + message.from = JID.parse(client.jid); + message.sender = message.from.asBare(); + message.replyTo = [message.sender]; message.recipients = getParticipants().map((p) -> JID.parse(p)); + persistence.storeMessage(client.accountId(), message); for (recipient in message.recipients) { message.to = recipient; client.sendStanza(message.asStanza()); @@ -374,7 +380,14 @@ class Channel extends Chat { public function sendMessage(message:ChatMessage):Void { client.chatActivity(this); + message.timestamp = message.timestamp ?? Date.format(std.Date.now()); + message.direction = MessageSent; + message.from = JID.parse(client.jid); + message.sender = getFullJid(); + message.replyTo = [message.sender]; message.to = JID.parse(chatId); + message.recipients = [message.to]; + persistence.storeMessage(client.accountId(), message); client.sendStanza(message.asStanza("groupchat")); } diff --git a/xmpp/ChatMessage.hx b/xmpp/ChatMessage.hx index 2670d25..5247f54 100644 --- a/xmpp/ChatMessage.hx +++ b/xmpp/ChatMessage.hx @@ -20,16 +20,17 @@ class ChatAttachment { class ChatMessage { public var localId (default, set) : Null<String> = null; public var serverId (default, set) : Null<String> = null; + public var serverIdBy : Null<String> = null; public var timestamp (default, set) : Null<String> = null; public var to: Null<JID> = null; - private var from: Null<JID> = null; + public var from: Null<JID> = null; public var sender: Null<JID> = null; public var recipients: Array<JID> = []; public var replyTo: Array<JID> = []; - var threadId (default, null): Null<String> = null; + public var threadId (default, null): Null<String> = null; public var attachments : Array<ChatAttachment> = []; @@ -41,7 +42,10 @@ class ChatMessage { public function new() { } public static function fromStanza(stanza:Stanza, localJidStr:String):Null<ChatMessage> { + if (stanza.attr.get("type") == "error") return null; + var msg = new ChatMessage(); + msg.timestamp = stanza.findText("{urn:xmpp:delay}delay@stamp") ?? Date.format(std.Date.now()); msg.lang = stanza.attr.get("xml:lang"); msg.text = stanza.getChildText("body"); if (msg.text != null && (msg.lang == null || msg.lang == "")) { @@ -67,12 +71,22 @@ class ChatMessage { final localId = stanza.attr.get("id"); if (localId != null) msg.localId = localId; + var altServerId = null; for (stanzaId in stanza.allTags("stanza-id", "urn:xmpp:sid:0")) { final id = stanzaId.attr.get("id"); if ((stanzaId.attr.get("by") == domain || stanzaId.attr.get("by") == localJidBare.asString()) && id != null) { + msg.serverIdBy = localJidBare.asString(); msg.serverId = id; break; } + altServerId = stanzaId; + } + if (msg.serverId == null && altServerId != null && stanza.attr.get("type") != "error") { + final id = altServerId.attr.get("id"); + if (id != null) { + msg.serverId = id; + msg.serverIdBy = altServerId.attr.get("by"); + } } msg.direction = (msg.to == null || msg.to.asBare().equals(localJidBare)) ? MessageReceived : MessageSent; @@ -139,7 +153,7 @@ class ChatMessage { } public function set_serverId(serverId:String):String { - if(this.serverId != null) { + if(this.serverId != null && this.serverId != serverId) { throw new Exception("Message already has a serverId set"); } return this.serverId = serverId; diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js index c10a2a3..7bd9498 100644 --- a/xmpp/persistence/browser.js +++ b/xmpp/persistence/browser.js @@ -10,8 +10,7 @@ exports.xmpp.persistence = { dbOpenReq.onupgradeneeded = (event) => { const upgradeDb = event.target.result; if (!db.objectStoreNames.contains("messages")) { - const messages = upgradeDb.createObjectStore("messages", { keyPath: "serverId" }); - messages.createIndex("account", ["account", "timestamp"]); + const messages = upgradeDb.createObjectStore("messages", { keyPath: ["account", "serverIdBy", "serverId", "localId"] }); messages.createIndex("chats", ["account", "chatId", "timestamp"]); messages.createIndex("localId", ["account", "chatId", "localId"]); } @@ -47,24 +46,47 @@ exports.xmpp.persistence = { }); } + function hydrateMessage(value) { + const message = new xmpp.ChatMessage(); + message.localId = value.localId ? value.localId : null; + message.serverId = value.serverId ? value.serverId : null; + message.serverIdBy = value.serverIdBy ? value.serverIdBy : null; + message.timestamp = value.timestamp && value.timestamp.toISOString(); + message.to = value.to && xmpp.JID.parse(value.to); + message.from = value.from && xmpp.JID.parse(value.from); + message.sender = value.sender && xmpp.JID.parse(value.sender); + message.recipients = value.recipients.map((r) => xmpp.JID.parse(r)); + message.replyTo = value.replyTo.map((r) => xmpp.JID.parse(r)); + message.threadId = value.threadId; + message.attachments = value.attachments; + message.text = value.text; + message.lang = value.lang; + message.direction = value.direction == "MessageReceived" ? xmpp.MessageDirection.MessageReceived : xmpp.MessageDirection.MessageSent; + return message; + } + return { lastId: function(account, jid, callback) { const tx = db.transaction(["messages"], "readonly"); const store = tx.objectStore("messages"); var cursor = null; if (jid === null) { - cursor = store.index("account").openCursor( - IDBKeyRange.bound([account, new Date(0)], [account, new Date("9999-01-01")]), - "prev" + cursor = store.index("chats").openCursor( + IDBKeyRange.bound([account], [account, [], []]), + "prev" ); } else { cursor = store.index("chats").openCursor( - IDBKeyRange.bound([account, jid, new Date(0)], [account, jid, new Date("9999-01-01")]), + IDBKeyRange.bound([account, jid], [account, jid, []]), "prev" ); } cursor.onsuccess = (event) => { - callback(event.target.result ? event.target.result.value.serverId : null); + if (!event.target.result || (event.target.result.value.serverId && (jid || event.target.result.value.serverIdBy === account))) { + callback(event.target.result ? event.target.result.value.serverId : null); + } else { + event.target.result.continue(); + } } cursor.onerror = (event) => { console.error(event); @@ -110,11 +132,16 @@ exports.xmpp.persistence = { storeMessage: function(account, message) { const tx = db.transaction(["messages"], "readwrite"); const store = tx.objectStore("messages"); - promisifyRequest(store.index("localId").get([account, message.chatId(), message.localId])).then((result) => { - if (result && message.direction === xmpp.MessageDirection.MessageSent && result.direction === "MessageSent") return; // duplicate, we trust our own stanza ids + if (!message.serverId && !message.localId) throw "Cannot store a message with no id"; + if (!message.serverId && message.isIncoming()) throw "Cannot store an incoming message with no server id"; + 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 || "", account: account, chatId: message.chatId(), to: message.to?.asString(), @@ -128,41 +155,27 @@ exports.xmpp.persistence = { }); }, - getMessages: function(account, chatId, _beforeId, beforeTime, callback) { - const beforeDate = beforeTime ? new Date(beforeTime) : new Date("9999-01-01"); + getMessages: function(account, chatId, beforeId, beforeTime, callback) { + const beforeDate = beforeTime ? new Date(beforeTime) : []; const tx = db.transaction(["messages"], "readonly"); const store = tx.objectStore("messages"); const cursor = store.index("chats").openCursor( - IDBKeyRange.bound([account, chatId, new Date(0)], [account, chatId, beforeDate]), + IDBKeyRange.bound([account, chatId], [account, chatId, beforeDate]), "prev" ); const result = []; cursor.onsuccess = (event) => { if (event.target.result && result.length < 50) { const value = event.target.result.value; - if (value.timestamp && value.timestamp.getTime() === beforeDate.getTime()) { + if (value.serverId === beforeId || (value.timestamp && value.timestamp.getTime() === (beforeDate instanceof Date && beforeDate.getTime()))) { event.target.result.continue(); return; } - const message = new xmpp.ChatMessage(); - message.localId = value.localId; - message.serverId = value.serverId; - message.timestamp = value.timestamp && value.timestamp.toISOString(); - message.to = value.to && xmpp.JID.parse(value.to); - message.from = value.from && xmpp.JID.parse(value.from); - message.sender = value.sender && xmpp.JID.parse(value.sender); - message.recipients = value.recipients.map((r) => xmpp.JID.parse(r)); - message.replyTo = value.replyTo.map((r) => xmpp.JID.parse(r)); - message.threadId = value.threadId; - message.attachments = value.attachments; - message.text = value.text; - message.lang = value.lang; - message.direction = value.direction == "MessageReceived" ? xmpp.MessageDirection.MessageReceived : xmpp.MessageDirection.MessageSent; - result.push(message); + result.unshift(hydrateMessage(value)); event.target.result.continue(); } else { - callback(result.reverse()); + callback(result); } } cursor.onerror = (event) => {