| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-11-29 16:41:55 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-12-11 15:59:51 UTC |
| parent | 2b710d8a4846c31d7bfaf81f5c948922cea22e3b |
| 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; + } +}