git » sdk » commit 049e71c

Discover and store services

author Stephen Paul Weber
2023-11-29 16:41:55 UTC
committer Stephen Paul Weber
2023-12-11 15:59:51 UTC
parent 2b710d8a4846c31d7bfaf81f5c948922cea22e3b

Discover and store services

xmpp/Client.hx +18 -0
xmpp/Persistence.hx +2 -0
xmpp/persistence/browser.js +43 -1
xmpp/queries/DiscoItemsGet.hx +54 -0

diff --git a/xmpp/Client.hx b/xmpp/Client.hx
index 5523533..f9a659e 100644
--- a/xmpp/Client.hx
+++ b/xmpp/Client.hx
@@ -13,6 +13,7 @@ import xmpp.PubsubEvent;
 import xmpp.Stream;
 import xmpp.jingle.Session;
 import xmpp.queries.DiscoInfoGet;
+import xmpp.queries.DiscoItemsGet;
 import xmpp.queries.ExtDiscoGet;
 import xmpp.queries.GenericQuery;
 import xmpp.queries.JabberIqGatewayGet;
@@ -440,6 +441,9 @@ class Client extends xmpp.EventEmitter {
 				.up()
 		);
 
+		discoverServices(new JID(null, jid.domain), (service, caps) -> {
+			persistence.storeService(accountId(), service.jid.asString(), service.name, service.node, caps);
+		});
 		rosterGet();
 		bookmarksGet(() -> {
 			sync(() -> {
@@ -670,6 +674,20 @@ class Client extends xmpp.EventEmitter {
 		sendQuery(extDiscoGet);
 	}
 
+	public function discoverServices(target: JID, ?node: String, callback: ({ jid: JID, name: Null<String>, node: Null<String> }, Caps)->Void) {
+		final itemsGet = new DiscoItemsGet(target.asString(), node);
+		itemsGet.onFinished(()-> {
+			for (item in itemsGet.getResult() ?? []) {
+				final infoGet = new DiscoInfoGet(item.jid.asString(), item.node);
+				infoGet.onFinished(() -> {
+					callback(item, infoGet.getResult());
+				});
+				sendQuery(infoGet);
+			}
+		});
+		sendQuery(itemsGet);
+	}
+
 	private function rosterGet() {
 		var rosterGet = new RosterGet();
 		rosterGet.onFinished(() -> {
diff --git a/xmpp/Persistence.hx b/xmpp/Persistence.hx
index 428f796..4dbc56f 100644
--- a/xmpp/Persistence.hx
+++ b/xmpp/Persistence.hx
@@ -21,4 +21,6 @@ abstract class Persistence {
 	abstract public function getLogin(login:String, callback:(clientId:String, token:Null<String>, fastCount: Int, displayName:String)->Void):Void;
 	abstract public function storeStreamManagement(accountId:String, smId:String, outboundCount:Int, inboundCount:Int, outboundQueue:Array<String>):Void;
 	abstract public function getStreamManagement(accountId:String, callback: (smId:String, outboundCount:Int, inboundCount:Int, outboundQueue:Array<String>)->Void):Void;
+	abstract public function storeService(accountId:String, serviceId:String, name:Null<String>, node:Null<String>, caps:Caps):Void;
+	abstract public function findServicesWithFeature(accountId:String, feature:String, callback:(Array<{serviceId:String, name:Null<String>, node:Null<String>, caps: Caps}>)->Void):Void;
 }
diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js
index c255264..05b8793 100644
--- a/xmpp/persistence/browser.js
+++ b/xmpp/persistence/browser.js
@@ -28,10 +28,13 @@ exports.xmpp.persistence = {
 				if (!db.objectStoreNames.contains("chats")) {
 					upgradeDb.createObjectStore("chats", { keyPath: ["account", "chatId"] });
 				}
+				if (!db.objectStoreNames.contains("services")) {
+					upgradeDb.createObjectStore("services", { keyPath: ["account", "serviceId"] });
+				}
 			};
 			dbOpenReq.onsuccess = (event) => {
 				db = event.target.result;
-				if (!db.objectStoreNames.contains("messages") || !db.objectStoreNames.contains("keyvaluepairs") || !db.objectStoreNames.contains("chats")) {
+				if (!db.objectStoreNames.contains("messages") || !db.objectStoreNames.contains("keyvaluepairs") || !db.objectStoreNames.contains("chats") || !db.objectStoreNames.contains("services")) {
 					db.close();
 					openDb(db.version + 1);
 					return;
@@ -404,6 +407,45 @@ exports.xmpp.persistence = {
 					console.error(e);
 					callback(null, null, null);
 				});
+			},
+
+			storeService(account, serviceId, name, node, caps) {
+				this.storeCaps(caps);
+
+				const tx = db.transaction(["services"], "readwrite");
+				const store = tx.objectStore("services");
+
+				store.put({
+					account: account,
+					serviceId: serviceId,
+					name: name,
+					node: node,
+					caps: caps.ver(),
+				});
+			},
+
+			findServicesWithFeature(account, feature, callback) {
+				const tx = db.transaction(["services"], "readonly");
+				const store = tx.objectStore("services");
+
+				// Almost full scan shouldn't be too expensive, how many services are we aware of?
+				const cursor = store.openCursor(IDBKeyRange.bound([account], [account, []]));
+				const result = [];
+				cursor.onsuccess = (event) => {
+					if (event.target.result) {
+						const value = event.target.result.value;
+						this.getCaps(value.caps, (caps) => {
+							if (caps && caps.features.includes(feature)) result.push({ ...value, caps: caps });
+							event.target.result.continue();
+						});
+					} else {
+						callback(result);
+					}
+				}
+				cursor.onerror = (event) => {
+					console.error(event);
+					callback([]);
+				}
 			}
 		}
 	}
diff --git a/xmpp/queries/DiscoItemsGet.hx b/xmpp/queries/DiscoItemsGet.hx
new file mode 100644
index 0000000..8738660
--- /dev/null
+++ b/xmpp/queries/DiscoItemsGet.hx
@@ -0,0 +1,54 @@
+package xmpp.queries;
+
+import haxe.DynamicAccess;
+import haxe.Exception;
+
+import xmpp.ID;
+import xmpp.JID;
+import xmpp.ResultSet;
+import xmpp.Stanza;
+import xmpp.Stream;
+import xmpp.queries.GenericQuery;
+
+class DiscoItemsGet extends GenericQuery {
+	public var xmlns(default, null) = "http://jabber.org/protocol/disco#items";
+	public var queryId:String = null;
+	public var responseStanza(default, null):Stanza;
+	private var result: Array<{ jid: JID, name: Null<String>, node: Null<String> }>;
+
+	public function new(to: String, ?node: String) {
+		var attr: DynamicAccess<String> = { xmlns: xmlns };
+		if (node != null) attr["node"] = node;
+		/* Build basic query */
+		queryId = ID.short();
+		queryStanza = new Stanza(
+			"iq",
+			{ to: to, type: "get", id: queryId }
+		).tag("query", attr).up();
+	}
+
+	public function handleResponse(stanza:Stanza) {
+		responseStanza = stanza;
+		finish();
+	}
+
+	public function getResult() {
+		if (responseStanza == null) {
+			return null;
+		}
+		if(result == null) {
+			final q = responseStanza.getChild("query", xmlns);
+			if(q == null) {
+				return null;
+			}
+			result = [];
+			for (item in q.allTags("item")) {
+				final jid = item.attr.get("jid");
+				if (jid != null) {
+					result.push({jid: JID.parse(jid), name: item.attr.get("name"), node: item.attr.get("node")});
+				}
+			}
+		}
+		return result;
+	}
+}