git » sdk » commit 316705b

Fetch messages back out of persistence

author Stephen Paul Weber
2023-09-13 02:32:12 UTC
committer Stephen Paul Weber
2023-09-13 14:32:32 UTC
parent 0fee5af15ae4cc812bca0fc9dd8603576d9e8a61

Fetch messages back out of persistence

Only go to MAM when we have nothing locally

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([]);
+				}
+			}
+		}
+	}
 };