git » sdk » commit 4ae996e

Persist chats

author Stephen Paul Weber
2023-10-11 20:14:30 UTC
committer Stephen Paul Weber
2023-10-11 23:15:00 UTC
parent e52cfd0a574d9e94b7040319b5fac4fed64b440f

Persist chats

xmpp/Chat.hx +35 -14
xmpp/Client.hx +18 -6
xmpp/Persistence.hx +3 -0
xmpp/persistence/browser.js +37 -5

diff --git a/xmpp/Chat.hx b/xmpp/Chat.hx
index 0eda2d2..9fd8a0d 100644
--- a/xmpp/Chat.hx
+++ b/xmpp/Chat.hx
@@ -12,24 +12,17 @@ import xmpp.jingle.Session;
 import xmpp.queries.MAMQuery;
 using Lambda;
 
-enum ChatType {
-	ChatTypeDirect;
-	ChatTypeGroup;
-	ChatTypePublic;
-}
-
 abstract class Chat {
 	private var client:Client;
 	private var stream:GenericStream;
 	private var persistence:Persistence;
 	private var avatarSha1:Null<BytesData> = null;
-	private var caps:Map<String, Caps> = [];
+	private var caps:haxe.DynamicAccess<Caps> = {};
 	private var trusted:Bool = false;
 	public var chatId(default, null):String;
-	public var type(default, null):Null<ChatType>;
 	public var jingleSessions: Map<String, xmpp.jingle.Session> = [];
 
-	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, type:ChatType) {
+	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String) {
 		this.client = client;
 		this.stream = stream;
 		this.persistence = persistence;
@@ -44,10 +37,6 @@ abstract class Chat {
 
 	abstract public function getParticipants():Array<String>;
 
-	public function isDirectChat():Bool { return type.match(ChatTypeDirect); };
-	public function isGroupChat():Bool  { return type.match(ChatTypeGroup);  };
-	public function isPublicChat():Bool { return type.match(ChatTypePublic); };
-
 	public function setCaps(resource:String, caps:Caps) {
 		this.caps.set(resource, caps);
 	}
@@ -143,10 +132,11 @@ abstract class Chat {
 	}
 }
 
+@:expose
 class DirectChat extends Chat {
 	private var displayName:String;
 	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String) {
-		super(client, stream, persistence, chatId, ChatTypeDirect);
+		super(client, stream, persistence, chatId);
 		this.displayName = chatId;
 	}
 
@@ -222,3 +212,34 @@ class DirectChat extends Chat {
 		}
 	}
 }
+
+@:expose
+class SerializedChat {
+	public final chatId:String;
+	public final trusted:Bool;
+	public final avatarSha1:Null<BytesData>;
+	public final caps:haxe.DynamicAccess<Caps>;
+	public final displayName:Null<String>;
+	public final klass:String;
+
+	public function new(chatId: String, trusted: Bool, avatarSha1: Null<BytesData>, caps: haxe.DynamicAccess<Caps>, displayName: Null<String>, klass: String) {
+		this.chatId = chatId;
+		this.trusted = trusted;
+		this.avatarSha1 = avatarSha1;
+		this.caps = caps;
+		this.displayName = displayName;
+		this.klass = klass;
+	}
+
+	public function toDirectChat(client: Client, stream: GenericStream, persistence: Persistence) {
+		if (klass != "DirectChat") throw "Not a direct chat: " + klass;
+		final chat = new DirectChat(client, stream, persistence, chatId);
+		if (displayName != null) chat.setDisplayName(displayName);
+		if (avatarSha1 != null) chat.setAvatarSha1(avatarSha1);
+		chat.setTrusted(trusted);
+		for (resource => c in caps) {
+			chat.setCaps(resource, c);
+		}
+		return chat;
+	}
+}
diff --git a/xmpp/Client.hx b/xmpp/Client.hx
index 7a740d2..d0b90d6 100644
--- a/xmpp/Client.hx
+++ b/xmpp/Client.hx
@@ -54,13 +54,20 @@ class Client extends xmpp.EventEmitter {
 	}
 
 	public function start() {
-		persistence.getLogin(jid, (login) -> {
-			if (login.token == null) {
-				stream.on("auth/password-needed", (data)->this.trigger("auth/password-needed", { jid: this.jid }));
-			} else {
-				stream.on("auth/password-needed", (data)->this.stream.trigger("auth/password", { password: login.token }));
+		persistence.getChats(jid, (protoChats) -> {
+			for (protoChat in protoChats) {
+				chats.push(protoChat.toDirectChat(this, stream, persistence));
 			}
-			stream.connect(login.clientId == null ? jid : jid + "/" + login.clientId);
+			this.trigger("chats/update", chats);
+
+			persistence.getLogin(jid, (login) -> {
+				if (login.token == null) {
+					stream.on("auth/password-needed", (data)->this.trigger("auth/password-needed", { jid: this.jid }));
+				} else {
+					stream.on("auth/password-needed", (data)->this.stream.trigger("auth/password", { password: login.token }));
+				}
+				stream.connect(login.clientId == null ? jid : jid + "/" + login.clientId);
+			});
 		});
 	}
 
@@ -145,6 +152,7 @@ class Client extends xmpp.EventEmitter {
 				}
 				final chat = this.getDirectChat(JID.parse(pubsubEvent.getFrom()).asBare().asString(), false);
 				chat.setAvatarSha1(avatarSha1);
+				persistence.storeChat(jid, chat);
 				persistence.getMediaUri("sha-1", avatarSha1, (uri) -> {
 					if (uri == null) {
 						final pubsubGet = new PubsubGet(pubsubEvent.getFrom(), "urn:xmpp:avatar:data", avatarSha1Hex);
@@ -267,11 +275,13 @@ class Client extends xmpp.EventEmitter {
 							if (discoGet.getResult() != null) {
 								persistence.storeCaps(discoGet.getResult());
 								chat.setCaps(JID.parse(stanza.attr.get("from")).resource, discoGet.getResult());
+								persistence.storeChat(jid, chat);
 							}
 						});
 						sendQuery(discoGet);
 					} else {
 						chat.setCaps(JID.parse(stanza.attr.get("from")).resource, caps);
+						persistence.storeChat(jid, chat);
 					}
 				});
 				return EventHandled;
@@ -313,6 +323,7 @@ class Client extends xmpp.EventEmitter {
 			}
 		}
 		var chat = new DirectChat(this, this.stream, this.persistence, chatId);
+		persistence.storeChat(jid, chat);
 		chats.unshift(chat);
 		if (triggerIfNew) this.trigger("chats/update", [chat]);
 		return chat;
@@ -449,6 +460,7 @@ class Client extends xmpp.EventEmitter {
 				var chat = getDirectChat(item.jid, false);
 				chat.setTrusted(item.subscription == "both" || item.subscription == "from");
 				if (item.fn != null && item.fn != "") chat.setDisplayName(item.fn);
+				persistence.storeChat(jid, chat);
 			}
 			this.trigger("chats/update", chats);
 		});
diff --git a/xmpp/Persistence.hx b/xmpp/Persistence.hx
index 80bc318..3ef9b06 100644
--- a/xmpp/Persistence.hx
+++ b/xmpp/Persistence.hx
@@ -2,9 +2,12 @@ package xmpp;
 
 import haxe.io.BytesData;
 import xmpp.ChatMessage;
+import xmpp.Chat;
 
 abstract class Persistence {
 	abstract public function lastId(accountId: String, chatId: Null<String>, callback:(serverId:Null<String>)->Void):Void;
+	abstract public function storeChat(accountId: String, chat: Chat):Void;
+	abstract public function getChats(accountId: String, callback: (chats:Array<SerializedChat>)->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;
 	abstract public function getMediaUri(hashAlgorithm:String, hash:BytesData, callback: (uri:Null<String>)->Void):Void;
diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js
index 5fa6b7b..bf81fbe 100644
--- a/xmpp/persistence/browser.js
+++ b/xmpp/persistence/browser.js
@@ -10,18 +10,21 @@ exports.xmpp.persistence = {
 			dbOpenReq.onupgradeneeded = (event) => {
 				const upgradeDb = event.target.result;
 				if (!db.objectStoreNames.contains("messages")) {
-					const store = upgradeDb.createObjectStore("messages", { keyPath: "serverId" });
-					store.createIndex("account", ["account", "timestamp"]);
-					store.createIndex("chats", ["account", "chatId", "timestamp"]);
-					store.createIndex("localId", ["account", "chatId", "localId"]);
+					const messages = upgradeDb.createObjectStore("messages", { keyPath: "serverId" });
+					messages.createIndex("account", ["account", "timestamp"]);
+					messages.createIndex("chats", ["account", "chatId", "timestamp"]);
+					messages.createIndex("localId", ["account", "chatId", "localId"]);
 				}
 				if (!db.objectStoreNames.contains("keyvaluepairs")) {
 					upgradeDb.createObjectStore("keyvaluepairs");
 				}
+				if (!db.objectStoreNames.contains("chats")) {
+					upgradeDb.createObjectStore("chats", { keyPath: ["account", "chatId"] });
+				}
 			};
 			dbOpenReq.onsuccess = (event) => {
 				db = event.target.result;
-				if (!db.objectStoreNames.contains("messages") || !db.objectStoreNames.contains("keyvaluepairs")) {
+				if (!db.objectStoreNames.contains("messages") || !db.objectStoreNames.contains("keyvaluepairs") || !db.objectStoreNames.contains("chats")) {
 					db.close();
 					openDb(db.version + 1);
 				}
@@ -69,6 +72,35 @@ exports.xmpp.persistence = {
 				}
 			},
 
+			storeChat: function(account, chat) {
+				const tx = db.transaction(["chats"], "readwrite");
+				const store = tx.objectStore("chats");
+
+				store.put({
+					account: account,
+					chatId: chat.chatId,
+					trusted: chat.trusted,
+					avatarSha1: chat.avatarSha1,
+					caps: chat.caps,
+					displayName: chat.displayName,
+					class: chat instanceof xmpp.DirectChat ? "DirectChat" : "Chat"
+				});
+			},
+
+			getChats: function(account, callback) {
+				const tx = db.transaction(["chats"], "readonly");
+				const store = tx.objectStore("chats");
+				const range = IDBKeyRange.bound([account], [account, []]);
+				promisifyRequest(store.getAll(range)).then((result) => callback(result.map((r) => new xmpp.SerializedChat(
+					r.chatId,
+					r.trusted,
+					r.avatarSha1,
+					r.caps,
+					r.displayName,
+					r.class
+				))));
+			},
+
 			storeMessage: function(account, message) {
 				const tx = db.transaction(["messages"], "readwrite");
 				const store = tx.objectStore("messages");