| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-09-13 02:32:12 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-09-13 14:32:32 UTC |
| parent | 0fee5af15ae4cc812bca0fc9dd8603576d9e8a61 |
| Makefile | +1 | -1 |
| xmpp/Chat.hx | +23 | -11 |
| xmpp/ChatMessage.hx | +1 | -1 |
| xmpp/Client.hx | +5 | -2 |
| xmpp/Date.hx | +13 | -0 |
| xmpp/Persistence.hx | +1 | -0 |
| xmpp/persistence/browser.js | +90 | -50 |
diff --git a/Makefile b/Makefile index bcc7d8e..73573dc 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,6 @@ run-nodejs: test.node.js browser.js: haxe browser.hxml echo "var exports = {};" > browser.js - sed -e 's/hxEnums\["xmpp.EventResult"\] = {/hxEnums["xmpp.EventResult"] = $$hx_exports.xmpp.EventResult = {/'< browser.haxe.js >> browser.js + sed -e 's/hxEnums\["xmpp.EventResult"\] = {/hxEnums["xmpp.EventResult"] = $$hx_exports.xmpp.EventResult = {/' < browser.haxe.js | sed -e 's/hxEnums\["xmpp.MessageDirection"\] = {/hxEnums["xmpp.MessageDirection"] = $$hx_exports.xmpp.MessageDirection = {/' >> browser.js cat xmpp/persistence/*.js >> browser.js echo "export const { xmpp } = exports;" >> browser.js diff --git a/xmpp/Chat.hx b/xmpp/Chat.hx index bb9e5c9..116a263 100644 --- a/xmpp/Chat.hx +++ b/xmpp/Chat.hx @@ -15,18 +15,20 @@ enum ChatType { abstract class Chat { private var client:Client; private var stream:GenericStream; + private var persistence:Persistence; public var chatId(default, null):String; public var type(default, null):Null<ChatType>; - private function new(client:Client, stream:GenericStream, chatId:String, type:ChatType) { + private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, type:ChatType) { this.client = client; this.stream = stream; + this.persistence = persistence; this.chatId = chatId; } abstract public function sendMessage(message:ChatMessage):Void; - abstract public function getMessages(beforeId:Null<String>, handler:MessageListHandler):MessageSync; + abstract public function getMessages(beforeId:Null<String>, beforeTime:Null<String>, handler:(Array<ChatMessage>)->Void):Void; public function isDirectChat():Bool { return type.match(ChatTypeDirect); }; public function isGroupChat():Bool { return type.match(ChatTypeGroup); }; @@ -47,17 +49,27 @@ abstract class Chat { } class DirectChat extends Chat { - public function new(client:Client, stream:GenericStream, chatId:String) { - super(client, stream, chatId, ChatTypeDirect); + public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String) { + super(client, stream, persistence, chatId, ChatTypeDirect); } - public function getMessages(beforeId:Null<String>, handler:MessageListHandler):MessageSync { - var filter:MAMQueryParams = { with: this.chatId }; - if (beforeId != null) filter.page = { before: beforeId }; - var sync = new MessageSync(this.client, this.stream, filter); - sync.onMessages(handler); - sync.fetchNext(); - return sync; + public function getMessages(beforeId:Null<String>, beforeTime:Null<String>, handler:(Array<ChatMessage>)->Void):Void { + persistence.getMessages(client.jid, chatId, beforeId, beforeTime, (messages) -> { + if (messages.length > 0) { + handler(messages); + } else { + var filter:MAMQueryParams = { with: this.chatId }; + if (beforeId != null) filter.page = { before: beforeId }; + var sync = new MessageSync(this.client, this.stream, filter); + sync.onMessages((messages) -> { + for (message in messages.messages) { + persistence.storeMessage(chatId, message); + } + handler(messages.messages); + }); + sync.fetchNext(); + } + }); } public function sendMessage(message:ChatMessage):Void { diff --git a/xmpp/ChatMessage.hx b/xmpp/ChatMessage.hx index 05e165c..8ae6668 100644 --- a/xmpp/ChatMessage.hx +++ b/xmpp/ChatMessage.hx @@ -49,7 +49,7 @@ class ChatMessage { break; } } - msg.direction = (JID.parse(msg.to).asBare().asString() == localJidBare.asString()) ? MessageReceived : MessageSent; + msg.direction = (msg.to == null || JID.parse(msg.to).asBare().asString() == localJidBare.asString()) ? MessageReceived : MessageSent; if (msg.text == null) return null; diff --git a/xmpp/Client.hx b/xmpp/Client.hx index 3223345..19d2980 100644 --- a/xmpp/Client.hx +++ b/xmpp/Client.hx @@ -69,7 +69,7 @@ class Client extends xmpp.EventEmitter { return Std.downcast(chat, DirectChat); } } - var chat = new DirectChat(this, this.stream, chatId); + var chat = new DirectChat(this, this.stream, this.persistence, chatId); chats.unshift(chat); if (triggerIfNew) this.trigger("chats/update", [chat]); return chat; @@ -105,11 +105,14 @@ class Client extends xmpp.EventEmitter { } private function sync() { + var thirtyDaysAgo = Date.format( + DateTools.delta(std.Date.now(), DateTools.days(-30)) + ); persistence.lastId(jid, null, function(lastId) { var sync = new MessageSync( this, stream, - lastId == null ? {} : { page: { after: lastId } } + lastId == null ? { startTime: thirtyDaysAgo } : { page: { after: lastId } } ); sync.setNewestPageFirst(false); sync.onMessages((messageList) -> { diff --git a/xmpp/Date.hx b/xmpp/Date.hx new file mode 100644 index 0000000..82a3385 --- /dev/null +++ b/xmpp/Date.hx @@ -0,0 +1,13 @@ +package xmpp; + +class Date { + public static function format(d: std.Date):String { + var str = DateTools.format(d, "%Y-%m-%dT%H:%M:%S"); + var tzHour = Std.int(d.getTimezoneOffset()/60); + var tzMinute = Std.int(Math.abs(d.getTimezoneOffset())%60); + return + str + (tzHour > 0 ? "+" : "-") + + StringTools.lpad(Std.string(tzHour), "0", 2) + ":" + + StringTools.lpad(Std.string(tzMinute), "0", 2); + } +} diff --git a/xmpp/Persistence.hx b/xmpp/Persistence.hx index d344fb9..ffdd84a 100644 --- a/xmpp/Persistence.hx +++ b/xmpp/Persistence.hx @@ -5,4 +5,5 @@ import xmpp.ChatMessage; abstract class Persistence { abstract public function lastId(accountId: String, chatId: Null<String>, callback:(serverId:Null<String>)->Void):Void; abstract public function storeMessage(accountId: String, message: ChatMessage):Void; + abstract public function getMessages(accountId: String, chatId: String, beforeId: Null<String>, beforeTime: Null<String>, callback: (messages:Array<ChatMessage>)->Void):Void; } diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js index 594bdd4..1481c88 100644 --- a/xmpp/persistence/browser.js +++ b/xmpp/persistence/browser.js @@ -2,56 +2,96 @@ // so that SDK users can easily see how to write their own exports.xmpp.persistence = { - browser: (dbname) => { - var db = null; - var dbOpenReq = indexedDB.open(dbname, 1); - dbOpenReq.onerror = console.error; - dbOpenReq.onupgradeneeded = (event) => { - const upgradeDb = event.target.result; - const store = upgradeDb.createObjectStore("messages", { keyPath: "serverId" }); - store.createIndex("account", ["timestamp", "account"]); - store.createIndex("conversation", ["timestamp", "account", "conversation"]); - }; - dbOpenReq.onsuccess = (event) => { - db = event.target.result; - }; + browser: (dbname) => { + var db = null; + var dbOpenReq = indexedDB.open(dbname, 1); + dbOpenReq.onerror = console.error; + dbOpenReq.onupgradeneeded = (event) => { + const upgradeDb = event.target.result; + const store = upgradeDb.createObjectStore("messages", { keyPath: "serverId" }); + store.createIndex("account", ["account", "timestamp"]); + store.createIndex("conversation", ["account", "conversation", "timestamp"]); + }; + dbOpenReq.onsuccess = (event) => { + db = event.target.result; + }; - 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([new Date(0), account], [new Date("9999-01-01"), account]), - "prev" - ); - } else { - cursor = store.index("conversation").openCursor( - IDBKeyRange.bound([new Date(0), account, jid], [new Date("9999-01-01"), account, jid]), - "prev" - ); - } - cursor.onsuccess = (event) => { - callback(event.target.result ? event.target.result.value.serverId : null); - } - cursor.onerror = (event) => { - console.error(event); - callback(null); - } - }, + 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" + ); + } else { + cursor = store.index("conversation").openCursor( + IDBKeyRange.bound([account, jid, new Date(0)], [account, jid, new Date("9999-01-01")]), + "prev" + ); + } + cursor.onsuccess = (event) => { + callback(event.target.result ? event.target.result.value.serverId : null); + } + cursor.onerror = (event) => { + console.error(event); + callback(null); + } + }, - storeMessage: function(account, message) { - const tx = db.transaction(["messages"], "readwrite"); - const store = tx.objectStore("messages"); - store.put({ - ...message, - account: account, - conversation: message.conversation(), - timestamp: new Date(message.timestamp), - direction: message.direction.toString() - }); - } - } - } + storeMessage: function(account, message) { + const tx = db.transaction(["messages"], "readwrite"); + const store = tx.objectStore("messages"); + store.put({ + ...message, + account: account, + conversation: message.conversation(), + timestamp: new Date(message.timestamp), + direction: message.direction.toString() + }); + }, + + getMessages: function(account, conversation, _beforeId, beforeTime, callback) { + const beforeDate = beforeTime ? new Date(beforeTime) : new Date("9999-01-01"); + const tx = db.transaction(["messages"], "readonly"); + const store = tx.objectStore("messages"); + const cursor = store.index("conversation").openCursor( + IDBKeyRange.bound([account, conversation, new Date(0)], [account, conversation, 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()) { + 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; + message.from = value.from; + message.threadId = value.threadId; + message.replyTo = value.replyTo; + message.attachments = value.attachments; + message.text = value.text; + message.direction = value.direction == "MessageReceived" ? xmpp.MessageDirection.MessageReceived : xmpp.MessageDirection.MessageSent; + result.push(message); + event.target.result.continue(); + } else { + callback(result.reverse()); + } + } + cursor.onerror = (event) => { + console.error(event); + callback([]); + } + } + } + } };