| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2024-10-22 19:29:56 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2024-10-22 19:29:56 UTC |
| parent | 5089e9b3730356dc349eaeab839f9b6c125966fd |
| Makefile | +2 | -0 |
| browserjs.hxml | +5 | -4 |
| nodejs.hxml | +6 | -5 |
| npm/index.ts | +1 | -0 |
| snikket/Chat.hx | +13 | -2 |
| snikket/ChatMessage.hx | +50 | -10 |
| snikket/Client.hx | +35 | -30 |
| snikket/Message.hx | +31 | -11 |
| snikket/jingle/Session.hx | +77 | -17 |
| snikket/persistence/browser.js | +14 | -4 |
| snikket/streams/XmppJsStream.hx | +1 | -1 |
diff --git a/Makefile b/Makefile index d24f144..12f7501 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ npm/snikket-browser.js: sed -i 's/snikket\.UiState/enums.UiState/g' npm/snikket-browser.d.ts sed -i 's/snikket\.MessageStatus/enums.MessageStatus/g' npm/snikket-browser.d.ts sed -i 's/snikket\.MessageDirection/enums.MessageDirection/g' npm/snikket-browser.d.ts + sed -i 's/snikket\.MessageType/enums.MessageType/g' npm/snikket.d.ts sed -i 's/snikket\.UserState/enums.UserState/g' npm/snikket-browser.d.ts sed -i 's/_Push.Push_Fields_/Push/g' npm/snikket-browser.d.ts sed -i '1ivar exports = {};' npm/snikket-browser.js @@ -24,6 +25,7 @@ npm/snikket.js: sed -i 's/snikket\.UiState/enums.UiState/g' npm/snikket.d.ts sed -i 's/snikket\.MessageStatus/enums.MessageStatus/g' npm/snikket.d.ts sed -i 's/snikket\.MessageDirection/enums.MessageDirection/g' npm/snikket.d.ts + sed -i 's/snikket\.MessageType/enums.MessageType/g' npm/snikket.d.ts sed -i 's/snikket\.UserState/enums.UserState/g' npm/snikket.d.ts sed -i 's/_Push.Push_Fields_/Push/g' npm/snikket.d.ts sed -i '1iimport { createRequire } from "module";' npm/snikket.js diff --git a/browserjs.hxml b/browserjs.hxml index 829cf6d..cdfab9f 100644 --- a/browserjs.hxml +++ b/browserjs.hxml @@ -1,11 +1,12 @@ +--library HtmlParser +--library datetime --library haxe-strings ---library hxtsdgen --library hsluv ---library tink_http +--library hxtsdgen +--library jsImport --library sha --library thenshim ---library HtmlParser ---library jsImport +--library tink_http snikket.Client snikket.Push diff --git a/nodejs.hxml b/nodejs.hxml index c00a82d..6223ee5 100644 --- a/nodejs.hxml +++ b/nodejs.hxml @@ -1,12 +1,13 @@ +--library HtmlParser +--library datetime --library haxe-strings ---library hxtsdgen --library hsluv ---library tink_http ---library sha ---library thenshim ---library HtmlParser --library hxnodejs +--library hxtsdgen --library jsImport +--library sha +--library thenshim +--library tink_http snikket.Client snikket.Push diff --git a/npm/index.ts b/npm/index.ts index 938e6aa..fd63ff2 100644 --- a/npm/index.ts +++ b/npm/index.ts @@ -24,6 +24,7 @@ export import jingle = snikket.jingle; export import UiState = enums.UiState; export import MessageStatus = enums.MessageStatus; export import MessageDirection = enums.MessageDirection; +export import MessageType = enums.MessageType; export import UserState = enums.UserState; export namespace persistence { diff --git a/snikket/Chat.hx b/snikket/Chat.hx index 9eecdb6..40d37a0 100644 --- a/snikket/Chat.hx +++ b/snikket/Chat.hx @@ -7,6 +7,7 @@ import snikket.ChatMessage; import snikket.Color; import snikket.GenericStream; import snikket.ID; +import snikket.Message; import snikket.MessageSync; import snikket.jingle.PeerConnection; import snikket.jingle.Session; @@ -330,7 +331,16 @@ abstract class Chat { A preview of the chat, such as the most recent message body **/ public function preview() { - return lastMessage?.text ?? ""; + if (lastMessage == null) return ""; + + return switch (lastMessage.type) { + case MessageCall: + lastMessage.isIncoming() ? "Incoming Call" : "Outgoing Call"; + case MessageChannel: + getParticipantDetails(lastMessage.senderId()).displayName + ": " + lastMessage.text; + default: + lastMessage.text; + } } @:allow(snikket) @@ -1046,6 +1056,7 @@ class Channel extends Chat { @:allow(snikket) private function prepareIncomingMessage(message:ChatMessage, stanza:Stanza) { message.syncPoint = !syncing(); + if (message.type == MessageChat) message.type = MessageChannelPrivate; message.sender = JID.parse(stanza.attr.get("from")); // MUC always needs full JIDs if (message.senderId() == getFullJid().asString()) { message.recipients = message.replyTo; @@ -1055,7 +1066,7 @@ class Channel extends Chat { } private function prepareOutgoingMessage(message:ChatMessage) { - message.isGroupchat = true; + message.type = MessageChannel; message.timestamp = message.timestamp ?? Date.format(std.Date.now()); message.direction = MessageSent; message.from = client.jid; diff --git a/snikket/ChatMessage.hx b/snikket/ChatMessage.hx index 06a2aac..dd22b24 100644 --- a/snikket/ChatMessage.hx +++ b/snikket/ChatMessage.hx @@ -1,5 +1,6 @@ package snikket; +import datetime.DateTime; import haxe.crypto.Base64; import haxe.io.Bytes; import haxe.io.BytesData; @@ -68,9 +69,17 @@ class ChatMessage { The ID of the server which set the serverId **/ public var serverIdBy : Null<String> = null; + /** + The type of this message (Chat, Call, etc) + **/ + public var type : MessageType = MessageChat; + @:allow(snikket) private var syncPoint : Bool = false; + @:allow(snikket) + private var replyId : Null<String> = null; + /** The timestamp of this message, in format YYYY-MM-DDThh:mm:ss[.sss]+00:00 **/ @@ -115,13 +124,6 @@ class ChatMessage { **/ public var lang: Null<String> = null; - /** - Is this a Group Chat message? - - If the message is in the context of a Channel but this is false, - then it is a private message - **/ - public var isGroupchat: Bool = false; // Only really useful for distinguishing whispers /** Direction of this message **/ @@ -178,12 +180,17 @@ class ChatMessage { **/ public function reply() { final m = new ChatMessage(); - m.isGroupchat = isGroupchat; + m.type = type; m.threadId = threadId ?? ID.long(); m.replyToMessage = this; return m; } + public function getReplyId() { + if (replyId != null) return replyId; + return type == MessageChannel || type == MessageChannelPrivate ? serverId : localId; + } + private function set_localId(localId:Null<String>) { if(this.localId != null) { throw new Exception("Message already has a localId set"); @@ -350,10 +357,43 @@ class ChatMessage { return threadId == null ? null : Identicon.svg(threadId); } + /** + The last status of the call if this message is related to a call + **/ + public function callStatus() { + return payloads.find((el) -> el.attr.get("xmlns") == "urn:xmpp:jingle-message:0")?.name; + } + + /** + The duration of the call if this message is related to a call + **/ + public function callDuration(): Null<String> { + if (versions.length < 2) return null; + final startedStr = versions[versions.length - 1].timestamp; + + return switch (callStatus()) { + case "finish": + final endedStr = versions[0].timestamp; + if (startedStr == null || endedStr == null) return null; + final started = DateTime.fromString(startedStr); + final ended = DateTime.fromString(endedStr); + final duration = ended - started; + duration.format("%I:%S"); + case "proceed": + if (startedStr == null) return null; + final started = DateTime.fromString(startedStr); + final ended = DateTime.now(); // ongoing + final duration = ended - started; + duration.format("%I:%S"); + default: + null; + } + } + @:allow(snikket) private function asStanza():Stanza { var body = text; - var attrs: haxe.DynamicAccess<String> = { type: isGroupchat ? "groupchat" : "chat" }; + var attrs: haxe.DynamicAccess<String> = { type: type == MessageChannel ? "groupchat" : "chat" }; if (from != null) attrs.set("from", from.asString()); if (to != null) attrs.set("to", to.asString()); if (localId != null) attrs.set("id", localId); @@ -386,7 +426,7 @@ class ChatMessage { } final reaction = EmojiUtil.isEmoji(StringTools.trim(body)) ? StringTools.trim(body) : null; body = quoteText + body; - final replyId = replyToM.isGroupchat ? replyToM.serverId : replyToM.localId; + final replyId = replyToM.getReplyId(); if (replyId != null) { final codepoints = StringUtil.codepointArray(quoteText); if (reaction != null) { diff --git a/snikket/Client.hx b/snikket/Client.hx index db58b17..1287b28 100644 --- a/snikket/Client.hx +++ b/snikket/Client.hx @@ -144,6 +144,36 @@ class Client extends EventEmitter { } } + final message = Message.fromStanza(stanza, this.jid); + switch (message.parsed) { + case ChatMessageStanza(chatMessage): + for (hash in chatMessage.inlineHashReferences()) { + fetchMediaByHash([hash], [chatMessage.from]); + } + 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); + } + notifyMessageHandlers(chatMessage); + }; + chatMessage = chat.prepareIncomingMessage(chatMessage, stanza); + if (chatMessage.serverId == null) { + updateChat(chatMessage); + } else { + persistence.storeMessage(accountId(), chatMessage, updateChat); + } + } + case ReactionUpdateStanza(update): + persistence.storeReaction(accountId(), update, (stored) -> if (stored != null) notifyMessageHandlers(stored)); + default: + // ignore + } + final jmiP = stanza.getChild("propose", "urn:xmpp:jingle-message:0"); if (jmiP != null && jmiP.attr.get("id") != null) { final session = new IncomingProposedSession(this, from, jmiP.attr.get("id")); @@ -199,36 +229,6 @@ class Client extends EventEmitter { } } - final message = Message.fromStanza(stanza, this.jid); - switch (message.parsed) { - case ChatMessageStanza(chatMessage): - for (hash in chatMessage.inlineHashReferences()) { - fetchMediaByHash([hash], [chatMessage.from]); - } - 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); - } - notifyMessageHandlers(chatMessage); - }; - chatMessage = chat.prepareIncomingMessage(chatMessage, stanza); - if (chatMessage.serverId == null) { - updateChat(chatMessage); - } else { - persistence.storeMessage(accountId(), chatMessage, updateChat); - } - } - case ReactionUpdateStanza(update): - persistence.storeReaction(accountId(), update, (stored) -> if (stored != null) notifyMessageHandlers(stored)); - default: - // ignore - } - if (stanza.attr.get("type") != "error") { final chatState = stanza.getChild(null, "http://jabber.org/protocol/chatstates"); final userState = switch (chatState?.name) { @@ -1067,6 +1067,11 @@ class Client extends EventEmitter { chats.sort((a, b) -> -Reflect.compare(a.lastMessageTimestamp() ?? "0", b.lastMessageTimestamp() ?? "0")); } + @:allow(snikket) + private function storeMessage(message: ChatMessage, ?callback: Null<(ChatMessage)->Void>) { + persistence.storeMessage(accountId(), message, callback ?? (_)->{}); + } + @:allow(snikket) private function sendQuery(query:GenericQuery) { this.stream.sendIq(query.getQueryStanza(), query.handleResponse); diff --git a/snikket/Message.hx b/snikket/Message.hx index d9dfb51..8bbe47e 100644 --- a/snikket/Message.hx +++ b/snikket/Message.hx @@ -14,6 +14,13 @@ enum abstract MessageStatus(Int) { var MessageFailedToSend; // There was an error sending this message } +enum abstract MessageType(Int) { + var MessageChat; + var MessageCall; + var MessageChannel; + var MessageChannelPrivate; +} + enum MessageStanza { ErrorMessageStanza(stanza: Stanza); ChatMessageStanza(message: ChatMessage); @@ -50,8 +57,9 @@ class Message { msg.lang = stanza.getChild("body")?.attr.get("xml:lang"); } msg.from = JID.parse(from); - msg.isGroupchat = stanza.attr.get("type") == "groupchat"; - msg.sender = stanza.attr.get("type") == "groupchat" ? msg.from : msg.from?.asBare(); + final isGroupchat = stanza.attr.get("type") == "groupchat"; + msg.type = isGroupchat ? MessageChannel : MessageChat; + msg.sender = isGroupchat ? msg.from : msg.from?.asBare(); final localJidBare = localJid.asBare(); final domain = localJid.domain; final to = stanza.attr.get("to"); @@ -85,6 +93,11 @@ class Message { msg.serverIdBy = altServerId.attr.get("by"); } } + if (msg.serverIdBy != null && msg.serverIdBy != localJid.domain) { + msg.replyId = msg.serverId; + } else if (msg.serverIdBy == localJid.domain) { + msg.replyId = msg.localId; + } msg.direction = (msg.to == null || msg.to.asBare().equals(localJidBare)) ? MessageReceived : MessageSent; if (msg.from != null && msg.from.asBare().equals(localJidBare)) msg.direction = MessageSent; msg.status = msg.direction == MessageReceived ? MessageDeliveredToDevice : MessageDeliveredToServer; // Delivered to us, a device @@ -96,7 +109,7 @@ class Message { } final from = msg.from; if (msg.direction == MessageReceived && from != null) { - replyTo[stanza.attr.get("type") == "groupchat" ? from.asBare().asString() : from.asString()] = true; + replyTo[isGroupchat ? from.asBare().asString() : from.asString()] = true; } else if(msg.to != null) { replyTo[msg.to.asString()] = true; } @@ -148,8 +161,8 @@ class Message { if (reactionId != null) { return new Message(msg.chatId(), msg.senderId(), msg.threadId, ReactionUpdateStanza(new ReactionUpdate( stanza.attr.get("id") ?? ID.long(), - stanza.attr.get("type") == "groupchat" ? reactionId : null, - stanza.attr.get("type") != "groupchat" ? reactionId : null, + isGroupchat ? reactionId : null, + isGroupchat ? null : reactionId, msg.chatId(), timestamp, msg.senderId(), @@ -169,6 +182,18 @@ class Message { msg.attachSims(sims); } + final jmi = stanza.getChild(null, "urn:xmpp:jingle-message:0"); + if (jmi != null) { + msg.type = MessageCall; + msg.payloads.push(jmi); + if (msg.text == null) msg.text = "call " + jmi.name; + if (jmi.name != "propose") { + msg.versions = [msg.clone()]; + } + // The session id is what really identifies us + Reflect.setField(msg, "localId", jmi.attr.get("id")); + } + if (msg.text == null && msg.attachments.length < 1) return new Message(msg.chatId(), msg.senderId(), msg.threadId, UnknownMessageStanza(stanza)); for (fallback in stanza.allTags("fallback", "urn:xmpp:fallback:0")) { @@ -192,13 +217,8 @@ class Message { if (replyToID != null) { // Reply stub final replyToMessage = new ChatMessage(); - replyToMessage.isGroupchat = msg.isGroupchat; replyToMessage.from = replyToJid == null ? null : JID.parse(replyToJid); - if (msg.isGroupchat) { - replyToMessage.serverId = replyToID; - } else { - replyToMessage.localId = replyToID; - } + replyToMessage.replyId = replyToID; msg.replyToMessage = replyToMessage; } } diff --git a/snikket/jingle/Session.hx b/snikket/jingle/Session.hx index a6f6d63..fcbc4ac 100644 --- a/snikket/jingle/Session.hx +++ b/snikket/jingle/Session.hx @@ -1,5 +1,7 @@ package snikket.jingle; +import snikket.ChatMessage; +import snikket.Message; import snikket.ID; import snikket.jingle.PeerConnection; import snikket.jingle.SessionDescription; @@ -35,6 +37,26 @@ interface Session { public function dtmf():Null<DTMFSender>; } +private function mkCallMessage(to: JID, from: JID, event: Stanza) { + final m = new ChatMessage(); + m.type = MessageCall; + m.to = to; + m.recipients = [to.asBare()]; + m.from = from; + m.sender = m.from.asBare(); + m.replyTo = [m.sender]; + m.direction = MessageSent; + m.text = "call " + event.name; + m.timestamp = Date.format(std.Date.now()); + m.payloads.push(event); + m.localId = ID.long(); + if (event.name != "propose") { + m.versions = [m.clone()]; + } + Reflect.setField(m, "localId", event.attr.get("id")); + return m; +} + class IncomingProposedSession implements Session { public var sid (get, null): String; private final client: Client; @@ -50,12 +72,24 @@ class IncomingProposedSession implements Session { public function ring() { // XEP-0353 says to send <ringing/> but that leaks presence if not careful + // Store it for ourselves at least + final event = new Stanza("ringing", { xmlns: "urn:xmpp:jingle-message:0", id: sid }); + final msg = mkCallMessage(from, client.jid, event); + client.storeMessage(msg, (stored) -> { + client.notifyMessageHandlers(stored); + }); client.trigger("call/ring", { chatId: from.asBare().asString(), session: this }); } public function hangup() { // XEP-0353 says to send <reject/> but that leaks presence if not careful // It also tells all other devices to stop ringing, which you may or may not want + // Store it for ourselves at least + final event = new Stanza("reject", { xmlns: "urn:xmpp:jingle-message:0", id: sid }); + final msg = mkCallMessage(from, client.jid, event); + client.storeMessage(msg, (stored) -> { + client.notifyMessageHandlers(stored); + }); client.getDirectChat(from.asBare().asString(), false).jingleSessions.remove(sid); } @@ -85,11 +119,16 @@ class IncomingProposedSession implements Session { if (accepted) return; accepted = true; client.sendPresence(from.asString()); - client.sendStanza( - new Stanza("message", { to: from.asString(), type: "chat" }) - .tag("proceed", { xmlns: "urn:xmpp:jingle-message:0", id: sid }).up() - .tag("store", { xmlns: "urn:xmpp:hints" }) - ); + final event = new Stanza("proceed", { xmlns: "urn:xmpp:jingle-message:0", id: sid }); + final msg = mkCallMessage(from, client.jid, event); + client.storeMessage(msg, (stored) -> { + client.notifyMessageHandlers(stored); + client.sendStanza( + new Stanza("message", { to: from.asString(), type: "chat", id: msg.versions[0].localId }) + .addChild(event) + .tag("store", { xmlns: "urn:xmpp:hints" }) + ); + }); } public function initiate(stanza: Stanza) { @@ -139,17 +178,22 @@ class OutgoingProposedSession implements Session { public function propose(audio: Bool, video: Bool) { this.audio = audio; this.video = video; - final stanza = new Stanza("message", { to: to.asString(), type: "chat" }) - .tag("propose", { xmlns: "urn:xmpp:jingle-message:0", id: sid }); + final event = new Stanza("propose", { xmlns: "urn:xmpp:jingle-message:0", id: sid }); if (audio) { - stanza.tag("description", { xmlns: "urn:xmpp:jingle:apps:rtp:1", media: "audio" }).up(); + event.tag("description", { xmlns: "urn:xmpp:jingle:apps:rtp:1", media: "audio" }).up(); } if (video) { - stanza.tag("description", { xmlns: "urn:xmpp:jingle:apps:rtp:1", media: "video" }).up(); + event.tag("description", { xmlns: "urn:xmpp:jingle:apps:rtp:1", media: "video" }).up(); } - stanza.up().tag("store", { xmlns: "urn:xmpp:hints" }); - client.sendStanza(stanza); - client.trigger("call/ringing", { chatId: to.asBare().asString() }); + final msg = mkCallMessage(to, client.jid, event); + client.storeMessage(msg, (stored) -> { + final stanza = new Stanza("message", { to: to.asString(), type: "chat", id: msg.localId }) + .addChild(event) + .tag("store", { xmlns: "urn:xmpp:hints" }); + client.sendStanza(stanza); + client.notifyMessageHandlers(stored); + client.trigger("call/ringing", { chatId: to.asBare().asString() }); + }); } public function ring() { @@ -157,11 +201,16 @@ class OutgoingProposedSession implements Session { } public function hangup() { - client.sendStanza( - new Stanza("message", { to: to.asString(), type: "chat" }) - .tag("retract", { xmlns: "urn:xmpp:jingle-message:0", id: sid }).up() - .tag("store", { xmlns: "urn:xmpp:hints" }) - ); + final event = new Stanza("retract", { xmlns: "urn:xmpp:jingle-message:0", id: sid }); + final msg = mkCallMessage(to, client.jid, event); + client.storeMessage(msg, (stored) -> { + client.sendStanza( + new Stanza("message", { to: to.asString(), type: "chat", id: msg.versions[0].localId }) + .addChild(event) + .tag("store", { xmlns: "urn:xmpp:hints" }) + ); + client.notifyMessageHandlers(stored); + }); client.getDirectChat(to.asBare().asString(), false).jingleSessions.remove(sid); } @@ -315,6 +364,17 @@ class InitiatedSession implements Session { } pc = null; client.trigger("call/retract", { chatId: counterpart.asBare().asString() }); + + final event = new Stanza("finish", { xmlns: "urn:xmpp:jingle-message:0", id: sid }); + final msg = mkCallMessage(counterpart, client.jid, event); + client.storeMessage(msg, (stored) -> { + client.notifyMessageHandlers(stored); + client.sendStanza( + new Stanza("message", { to: counterpart.asString(), type: "chat", id: msg.versions[0].localId }) + .addChild(event) + .tag("store", { xmlns: "urn:xmpp:hints" }) + ); + }); } @:allow(snikket) diff --git a/snikket/persistence/browser.js b/snikket/persistence/browser.js index e7ff5fb..9bc1e7b 100644 --- a/snikket/persistence/browser.js +++ b/snikket/persistence/browser.js @@ -70,6 +70,7 @@ const browser = (dbname, tokenize, stemmer) => { message.localId = value.localId ? value.localId : null; message.serverId = value.serverId ? value.serverId : null; message.serverIdBy = value.serverIdBy ? value.serverIdBy : null; + message.replyId = value.replyId ? value.replyId : null; message.syncPoint = !!value.syncPoint; message.direction = value.direction; message.status = value.status; @@ -84,7 +85,7 @@ const browser = (dbname, tokenize, stemmer) => { message.reactions = value.reactions; message.text = value.text; message.lang = value.lang; - message.isGroupchat = value.isGroupchat || value.groupchat; + message.type = value.type || (value.isGroupchat || value.groupchat ? enums.MessageType.Channel : enums.MessageType.Chat); message.payloads = (value.payloads || []).map(snikket.Stanza.parse); return message; } @@ -137,6 +138,15 @@ const browser = (dbname, tokenize, stemmer) => { head.timestamp = result.value.timestamp; // Edited version is not newer head.versions = versions; head.reactions = result.value.reactions; // Preserve these, edit doesn't touch them + // Calls can "edit" from multiple senders, but the original direction and sender holds + if (result.value.type === enums.MessageType.MessageCall) { + head.direction = result.value.direction; + head.sender = result.value.sender; + head.from = result.value.from; + head.to = result.value.to; + head.replyTo = result.value.replyTo; + head.recipients = result.value.recipients; + } result.update(head); return head; } @@ -324,7 +334,7 @@ const browser = (dbname, tokenize, stemmer) => { if (message.serverId && !message.serverIdBy) throw "Cannot store a message with a server id and no by"; new Promise((resolve) => // Hydrate reply stubs - message.replyToMessage && !message.replyToMessage.serverIdBy ? this.getMessage(account, message.chatId(), message.replyToMessage?.serverId, message.replyToMessage?.localId, resolve) : resolve(message.replyToMessage) + message.replyToMessage && !message.replyToMessage.serverIdBy ? this.getMessage(account, message.chatId(), message.replyToMessage.getReplyId(), message.replyToMessage.getReplyId(), resolve) : resolve(message.replyToMessage) ).then((replyToMessage) => { message.replyToMessage = replyToMessage; const tx = db.transaction(["messages", "reactions"], "readwrite"); @@ -333,14 +343,14 @@ const browser = (dbname, tokenize, stemmer) => { if (result?.value && !message.isIncoming() && result?.value.direction === enums.MessageDirection.MessageSent && message.versions.length < 1) { // Duplicate, we trust our own sent ids return promisifyRequest(result.delete()); - } else if (result?.value && result.value.sender == message.senderId() && (message.versions.length > 0 || (result.value.versions || []).length > 0)) { + } else if (result?.value && (result.value.sender == message.senderId() || result.value.type == enums.MessageType.MessageCall) && (message.versions.length > 0 || (result.value.versions || []).length > 0)) { hydrateMessage(correctMessage(account, message, result)).then(callback); return true; } }).then((done) => { if (!done) { // There may be reactions already if we are paging backwards - const cursor = tx.objectStore("reactions").index("senders").openCursor(IDBKeyRange.bound([account, message.chatId(), (message.isGroupchat ? message.serverId : message.localId) || ""], [account, message.chatId(), (message.isGroupchat ? message.serverId : message.localId) || "", []]), "prev"); + const cursor = tx.objectStore("reactions").index("senders").openCursor(IDBKeyRange.bound([account, message.chatId(), message.getReplyId() || ""], [account, message.chatId(), message.getReplyId() || "", []]), "prev"); const reactions = new Map(); const reactionTimes = new Map(); cursor.onsuccess = (event) => { diff --git a/snikket/streams/XmppJsStream.hx b/snikket/streams/XmppJsStream.hx index 2e29ed7..27cf00b 100644 --- a/snikket/streams/XmppJsStream.hx +++ b/snikket/streams/XmppJsStream.hx @@ -331,7 +331,7 @@ class XmppJsStream extends GenericStream { } private function triggerSMupdate() { - if (!client.streamManagement.enabled || !client.streamManagement.allowResume) return; + if (client == null || !client.streamManagement.enabled || !client.streamManagement.allowResume) return; this.trigger( "sm/update", {