git » sdk » commit 8791e45

Fetch and store bookmarks2

author Stephen Paul Weber
2023-10-19 03:00:37 UTC
committer Stephen Paul Weber
2023-10-19 03:00:37 UTC
parent 2e643339aada086ad26929665f74eeb011247ace

Fetch and store bookmarks2

xmpp/Chat.hx +27 -11
xmpp/Client.hx +54 -1
xmpp/persistence/browser.js +4 -0
xmpp/queries/PubsubGet.hx +1 -1

diff --git a/xmpp/Chat.hx b/xmpp/Chat.hx
index a7a598c..b8d539e 100644
--- a/xmpp/Chat.hx
+++ b/xmpp/Chat.hx
@@ -30,13 +30,15 @@ abstract class Chat {
 	public var jingleSessions: Map<String, xmpp.jingle.Session> = [];
 	private var displayName:String;
 	public var uiState = Open;
+	private var extensions: Stanza;
 
-	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open) {
+	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, extensions: Null<Stanza> = null) {
 		this.client = client;
 		this.stream = stream;
 		this.persistence = persistence;
 		this.chatId = chatId;
 		this.uiState = uiState;
+		this.extensions = extensions ?? new Stanza("extensions", { xmlns: "urn:xmpp:bookmarks:1" });
 		this.displayName = chatId;
 	}
 
@@ -54,6 +56,14 @@ abstract class Chat {
 
 	abstract public function close():Void;
 
+	public function updateFromBookmark(item: Stanza) {
+		final conf = item.getChild("conference", "urn:xmpp:bookmarks:1");
+		final fn = conf.attr.get("name");
+		if (fn != null) setDisplayName(fn);
+		uiState = (conf.attr.get("autojoin") == "1" || conf.attr.get("autojoin") == "true") ? Open : Closed;
+		extensions = conf.getChild("extensions") ?? new Stanza("extensions", { xmlns: "urn:xmpp:bookmarks:1" });
+	}
+
 	public function getPhoto(callback:(String)->Void) {
 		callback(Color.defaultPhoto(chatId, getDisplayName().charAt(0)));
 	}
@@ -250,10 +260,10 @@ class DirectChat extends Chat {
 
 @:expose
 class Channel extends Chat {
-	private var disco: Caps = new Caps("", [], ["http://jabber.org/protocol/muc"]); // TODO: persist this
+	public var disco: Caps = new Caps("", [], ["http://jabber.org/protocol/muc"]);
 
-	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, ?disco: Caps) {
-		super(client, stream, persistence, chatId, uiState);
+	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, extensions = null, ?disco: Caps) {
+		super(client, stream, persistence, chatId, uiState, extensions);
 		if (disco != null) this.disco = disco;
 		selfPing(disco == null);
 	}
@@ -369,15 +379,13 @@ class Channel extends Chat {
 	}
 
 	public function bookmark() {
-		// TODO: we should have been created from an existing bookmark if there was one,
-		// and we need to be sure to preserve everything we don't mean to change if this is an update
 		stream.sendIq(
 			new Stanza("iq", { type: "set" })
 				.tag("pubsub", { xmlns: "http://jabber.org/protocol/pubsub" })
 				.tag("publish", { node: "urn:xmpp:bookmarks:1" })
 				.tag("item", { id: chatId })
 				.tag("conference", { xmlns: "urn:xmpp:bookmarks:1", name: getDisplayName(), autojoin: uiState == Closed ? "false" : "true" })
-				.textTag("nick", client.displayName())
+				.addChild(extensions)
 				.up().up()
 				.tag("publish-options")
 				.tag("x", { xmlns: "jabber:x:data", type: "submit" })
@@ -435,16 +443,20 @@ class SerializedChat {
 	public final avatarSha1:Null<BytesData>;
 	public final caps:haxe.DynamicAccess<Caps>;
 	public final displayName:Null<String>;
-	public final uiState: String;
+	public final uiState:String;
+	public final extensions:String;
+	public final disco:Null<Caps>;
 	public final klass:String;
 
-	public function new(chatId: String, trusted: Bool, avatarSha1: Null<BytesData>, caps: haxe.DynamicAccess<Caps>, displayName: Null<String>, uiState: Null<String>, klass: String) {
+	public function new(chatId: String, trusted: Bool, avatarSha1: Null<BytesData>, caps: haxe.DynamicAccess<Caps>, displayName: Null<String>, uiState: Null<String>, extensions: Null<String>, disco: Null<Caps>, klass: String) {
 		this.chatId = chatId;
 		this.trusted = trusted;
 		this.avatarSha1 = avatarSha1;
 		this.caps = caps;
 		this.displayName = displayName;
 		this.uiState = uiState ?? "Open";
+		this.extensions = extensions ?? "<extensions xmlns='urn:app:bookmarks:1' />";
+		this.disco = disco;
 		this.klass = klass;
 	}
 
@@ -455,10 +467,14 @@ class SerializedChat {
 			default: Open;
 		}
 
+		final extensionsStanza = Stanza.fromXml(Xml.parse(extensions));
+
 		final chat = if (klass == "DirectChat") {
-			new DirectChat(client, stream, persistence, chatId, uiStateEnum);
+			new DirectChat(client, stream, persistence, chatId, uiStateEnum, extensionsStanza);
 		} else if (klass == "Channel") {
-			new Channel(client, stream, persistence, chatId, uiStateEnum);
+			final channel = new Channel(client, stream, persistence, chatId, uiStateEnum, extensionsStanza);
+			channel.disco = disco ?? new Caps("", [], ["http://jabber.org/protocol/muc"]);
+			channel;
 		} else {
 			throw "Unknown class: " + klass;
 		}
diff --git a/xmpp/Client.hx b/xmpp/Client.hx
index 79a04e4..4d42ce2 100644
--- a/xmpp/Client.hx
+++ b/xmpp/Client.hx
@@ -322,6 +322,7 @@ class Client extends xmpp.EventEmitter {
 		);
 
 		rosterGet();
+		bookmarksGet();
 		sync(() -> {
 			// Set self to online
 			sendPresence();
@@ -352,7 +353,7 @@ class Client extends xmpp.EventEmitter {
 		}
 
 		final chat = if (caps.isChannel(chatId)) {
-			final channel = new Channel(this, this.stream, this.persistence, chatId, Open, caps);
+			final channel = new Channel(this, this.stream, this.persistence, chatId, Open, null, caps);
 			chats.unshift(channel);
 			channel;
 		} else {
@@ -544,6 +545,58 @@ class Client extends xmpp.EventEmitter {
 		sendQuery(rosterGet);
 	}
 
+	private function bookmarksGet() {
+		final pubsubGet = new PubsubGet(null, "urn:xmpp:bookmarks:1");
+		pubsubGet.onFinished(() -> {
+			for (item in pubsubGet.getResult()) {
+				if (item.attr.get("id") != null) {
+					final chat = getChat(item.attr.get("id"));
+					if (chat == null) {
+						final discoGet = new DiscoInfoGet(item.attr.get("id"));
+						discoGet.onFinished(() -> {
+							final resultCaps = discoGet.getResult();
+							if (resultCaps == null) {
+								final err = discoGet.responseStanza?.getChild("error")?.getChild(null, "urn:ietf:params:xml:ns:xmpp-stanzas");
+								if (err == null || err?.name == "service-unavailable" || err?.name == "feature-not-implemented") {
+									final chat = getDirectChat(item.attr.get("id"), false);
+									chat.updateFromBookmark(item);
+									persistence.storeChat(accountId(), chat);
+									this.trigger("chats/update", [chat]);
+								}
+							} else {
+								persistence.storeCaps(resultCaps);
+								final identity = resultCaps.identities[0];
+								final conf = item.getChild("conference", "urn:xmpp:bookmarks:1");
+								if (conf.attr.get("name") == null) {
+									conf.attr.set("name", identity?.name);
+								}
+								if (resultCaps.isChannel(item.attr.get("id"))) {
+									final uiState = (conf.attr.get("autojoin") == "1" || conf.attr.get("autojoin") == "true") ? Open : Closed;
+									final chat = new Channel(this, this.stream, this.persistence, item.attr.get("id"), uiState, null, resultCaps);
+									chat.updateFromBookmark(item);
+									chats.unshift(chat);
+									persistence.storeChat(accountId(), chat);
+									this.trigger("chats/update", [chat]);
+								} else {
+									final chat = getDirectChat(item.attr.get("id"), false);
+									chat.updateFromBookmark(item);
+									persistence.storeChat(accountId(), chat);
+									this.trigger("chats/update", [chat]);
+								}
+							}
+						});
+						sendQuery(discoGet);
+					} else {
+						chat.updateFromBookmark(item);
+						persistence.storeChat(accountId(), chat);
+						this.trigger("chats/update", [chat]);
+					}
+				}
+			}
+		});
+		sendQuery(pubsubGet);
+	}
+
 	private function sync(?callback: ()->Void) {
 		persistence.lastId(jid, null, (lastId) -> doSync(callback, lastId));
 	}
diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js
index 19a86be..c10a2a3 100644
--- a/xmpp/persistence/browser.js
+++ b/xmpp/persistence/browser.js
@@ -84,6 +84,8 @@ exports.xmpp.persistence = {
 					caps: chat.caps,
 					displayName: chat.displayName,
 					uiState: chat.uiState?.toString(),
+					extensions: chat.extensions?.toString(),
+					disco: chat.disco,
 					class: chat instanceof xmpp.DirectChat ? "DirectChat" : (chat instanceof xmpp.Channel ? "Channel" : "Chat")
 				});
 			},
@@ -99,6 +101,8 @@ exports.xmpp.persistence = {
 					r.caps,
 					r.displayName,
 					r.uiState,
+					r.extensions,
+					r.disco,
 					r.class
 				))));
 			},
diff --git a/xmpp/queries/PubsubGet.hx b/xmpp/queries/PubsubGet.hx
index 29407f8..0399de7 100644
--- a/xmpp/queries/PubsubGet.hx
+++ b/xmpp/queries/PubsubGet.hx
@@ -16,7 +16,7 @@ class PubsubGet extends GenericQuery {
 	private var responseStanza:Stanza;
 	private var result: Array<Stanza>;
 
-	public function new(to: String, node: String, ?itemId: String) {
+	public function new(to: Null<String>, node: String, ?itemId: String) {
 		var attr: DynamicAccess<String> = { node: node };
 		if (ver != null) attr["ver"] = ver;
 		/* Build basic query */