| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-11-27 19:13:18 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-11-27 19:13:18 UTC |
| parent | fd63db6faf60f595f670dc56ffcd3e90b0af1b4c |
| xmpp/Client.hx | +25 | -11 |
| xmpp/GenericStream.hx | +1 | -0 |
| xmpp/Persistence.hx | +1 | -1 |
| xmpp/persistence/browser.js | +11 | -3 |
| xmpp/streams/XmppJsStream.hx | +80 | -52 |
diff --git a/xmpp/Client.hx b/xmpp/Client.hx index 185b14f..621aeb2 100644 --- a/xmpp/Client.hx +++ b/xmpp/Client.hx @@ -47,6 +47,7 @@ class Client extends xmpp.EventEmitter { ] ); private var _displayName: String; + private var fastMechanism: Null<String> = null; public function new(jid: String, persistence: Persistence) { super(); @@ -55,6 +56,12 @@ class Client extends xmpp.EventEmitter { this.persistence = persistence; stream = new Stream(); stream.on("status/online", this.onConnected); + + stream.on("fast-token", (data) -> { + persistence.storeLogin(this.jid.asBare().asString(), stream.clientId ?? this.jid.resource, displayName(), data.token); + return EventHandled; + }); + stream.on("sm/update", (data) -> { persistence.storeStreamManagement(accountId(), data.id, data.outbound, data.inbound, data.outbound_q); return EventHandled; @@ -366,13 +373,14 @@ class Client extends xmpp.EventEmitter { } public function setDisplayName(fn: String) { - if (fn == null || fn == "" || fn == displayName()) return; + if (fn == null || fn == "" || fn == displayName()) return false; _displayName = fn; - persistence.storeLogin(jid.asBare().asString(), jid.resource, fn, null); + persistence.storeLogin(jid.asBare().asString(), stream.clientId ?? jid.resource, fn, null); for (chat in getChats()) { if (Std.isOfType(chat, Channel)) Std.downcast(chat, Channel)?.selfPing(false); } // TODO: should this path publish to server too? But we use it for notifications from server right now... + return true; } public function start() { @@ -392,14 +400,20 @@ class Client extends xmpp.EventEmitter { this.trigger("chats/update", chats); persistence.getStreamManagement(accountId(), (smId, smOut, smIn, smOutQ) -> { - persistence.getLogin(accountId(), (clientId, token, displayName) -> { - setDisplayName(displayName); - if (clientId != null) jid = jid.withResource(clientId); - if (token == null) { - stream.on("auth/password-needed", (data)->this.trigger("auth/password-needed", { accountId: accountId() })); - } else { - stream.on("auth/password-needed", (data)->this.stream.trigger("auth/password", { password: token })); + persistence.getLogin(accountId(), (clientId, token, fastCount, displayName) -> { + stream.clientId = clientId ?? ID.long(); + jid = jid.withResource(stream.clientId); + if (!setDisplayName(displayName) && clientId == null) { + persistence.storeLogin(jid.asBare().asString(), stream.clientId, this.displayName(), null); } + stream.on("auth/password-needed", (data) -> { + fastMechanism = data.mechanisms.find((mech) -> mech.canFast)?.name; + if (token == null || fastMechanism == null) { + this.trigger("auth/password-needed", { accountId: accountId() }); + } else { + this.stream.trigger("auth/password", { password: token, mechanism: fastMechanism, fastCount: fastCount }); + } + }); stream.connect(jid.asString(), smId == null || smId == "" ? null : { id: smId, outbound: smOut, inbound: smIn, outbound_q: smOutQ }); }); }); @@ -414,7 +428,7 @@ class Client extends xmpp.EventEmitter { private function onConnected(data) { // Fired on connect or reconnect if (data != null && data.jid != null) { jid = JID.parse(data.jid); - if (!jid.isBare()) persistence.storeLogin(jid.asBare().asString(), jid.resource, displayName(), null); + if (stream.clientId == null && !jid.isBare()) persistence.storeLogin(jid.asBare().asString(), jid.resource, displayName(), null); } if (data.resumed) return EventHandled; @@ -452,7 +466,7 @@ class Client extends xmpp.EventEmitter { } public function usePassword(password: String):Void { - this.stream.trigger("auth/password", { password: password }); + this.stream.trigger("auth/password", { password: password, requestToken: fastMechanism }); } /* Return array of chats, sorted by last activity */ diff --git a/xmpp/GenericStream.hx b/xmpp/GenericStream.hx index edfa3e3..cb2cbbc 100644 --- a/xmpp/GenericStream.hx +++ b/xmpp/GenericStream.hx @@ -10,6 +10,7 @@ enum IqResult { } abstract class GenericStream extends EventEmitter { + public var clientId: Null<String> = null; public function new() { super(); diff --git a/xmpp/Persistence.hx b/xmpp/Persistence.hx index 5115c19..428f796 100644 --- a/xmpp/Persistence.hx +++ b/xmpp/Persistence.hx @@ -18,7 +18,7 @@ abstract class Persistence { abstract public function storeCaps(caps:Caps):Void; abstract public function getCaps(ver:String, callback: (Caps)->Void):Void; abstract public function storeLogin(login:String, clientId:String, displayName:String, token:Null<String>):Void; - abstract public function getLogin(login:String, callback:(clientId:String, token:Null<String>, displayName:String)->Void):Void; + 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; } diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js index 7e24598..c255264 100644 --- a/xmpp/persistence/browser.js +++ b/xmpp/persistence/browser.js @@ -361,7 +361,10 @@ exports.xmpp.persistence = { const store = tx.objectStore("keyvaluepairs"); store.put(clientId, "login:clientId:" + login).onerror = console.error; store.put(displayName, "fn:" + login).onerror = console.error; - if (token != null) store.put(token, "login:token:" + login).onerror = console.error; + if (token != null) { + store.put(token, "login:token:" + login).onerror = console.error; + store.put(0, "login:fastCount:" + login).onerror = console.error; + } }, storeStreamManagement: function(account, id, outbound, inbound, outbound_q) { @@ -385,15 +388,20 @@ exports.xmpp.persistence = { }, getLogin: function(login, callback) { - const tx = db.transaction(["keyvaluepairs"], "readonly"); + const tx = db.transaction(["keyvaluepairs"], "readwrite"); const store = tx.objectStore("keyvaluepairs"); Promise.all([ promisifyRequest(store.get("login:clientId:" + login)), promisifyRequest(store.get("login:token:" + login)), + promisifyRequest(store.get("login:fastCount:" + login)), promisifyRequest(store.get("fn:" + login)), ]).then((result) => { - callback(result[0], result[1], result[2]); + if (result[1]) { + store.put((result[2] || 0) + 1, "login:fastCount:" + login).onerror = console.error; + } + callback(result[0], result[1], result[2] || 0, result[3]); }).catch((e) => { + console.error(e); callback(null, null, null); }); } diff --git a/xmpp/streams/XmppJsStream.hx b/xmpp/streams/XmppJsStream.hx index ea7e0c8..e1cc8de 100644 --- a/xmpp/streams/XmppJsStream.hx +++ b/xmpp/streams/XmppJsStream.hx @@ -3,11 +3,18 @@ package xmpp.streams; import js.lib.Promise; import haxe.Http; import haxe.Json; +using Lambda; import xmpp.FSM; import xmpp.GenericStream; import xmpp.Stanza; +@:jsRequire("@xmpp/sasl-scram-sha-1") +extern class XmppJsScramSha1 { + @:selfCall + function new(sasl: Dynamic); +} + @:jsRequire("@xmpp/client", "client") extern class XmppJsClient { function new(options:Dynamic); @@ -15,12 +22,14 @@ extern class XmppJsClient { function on(eventName:String, callback:(Dynamic)->Void):Void; function send(stanza:XmppJsXml):Void; var jid:XmppJsJID; + var streamFrom:Null<XmppJsJID>; var status: String; var iqCallee:{ get: (String, String, ({stanza: XmppJsXml})->Any)->Void, set: (String, String, ({stanza: XmppJsXml})->Any)->Void, }; var streamManagement: { id:String, outbound: Int, inbound: Int, outbound_q: Array<XmppJsXml>, enabled: Bool, allowResume: Bool }; + var sasl2: Dynamic; } @:jsRequire("@xmpp/jid", "jid") @@ -133,71 +142,90 @@ class XmppJsStream extends GenericStream { } connectionURI = uri; - this.on("auth/password", function (event) { - var xmpp = new XmppJsClient({ - service: connectionURI, - domain: jid.domain, - username: jid.local, - resource: jid.resource, - password: event.password, + final waitForCreds = new js.lib.Promise((resolve, reject) -> { + this.on("auth/password", (event: Dynamic) -> { + if (event.username == null) event.username = jid.local; + resolve(event); + return EventHandled; }); + }); - if(this.debug) { - new XmppJsDebug(xmpp, true); + final clientId = jid.resource; + final xmpp = new XmppJsClient({ + service: connectionURI, + domain: jid.domain, + resource: jid.resource, + clientId: clientId, + credentials: (callback, mechanisms: Dynamic) -> { + this.clientId = Std.is(mechanisms, Array) ? clientId : null; + final mechs: Array<{name: String, canFast: Bool, canOther: Bool}> = Std.is(mechanisms, Array) ? mechanisms : [{ name: mechanisms, canFast: false, canOther: true }]; + final mech = mechs.find((m) -> m.canOther)?.name; + this.trigger("auth/password-needed", { mechanisms: mechs }); + return waitForCreds.then((creds) -> { + return callback(creds, creds.mechanism ?? mech); + }); } + }); + new XmppJsScramSha1(xmpp.sasl2); + xmpp.streamFrom = this.jid; - if (initialSM != null) { - xmpp.streamManagement.id = initialSM.id; - xmpp.streamManagement.outbound = initialSM.outbound; - xmpp.streamManagement.inbound = initialSM.inbound; - xmpp.streamManagement.outbound_q = (initialSM.outbound_q ?? []).map(XmppJsLtx.parse); - initialSM = null; - } + if(this.debug) { + new XmppJsDebug(xmpp, true); + } + + if (initialSM != null) { + xmpp.streamManagement.id = initialSM.id; + xmpp.streamManagement.outbound = initialSM.outbound; + xmpp.streamManagement.inbound = initialSM.inbound; + xmpp.streamManagement.outbound_q = (initialSM.outbound_q ?? []).map(XmppJsLtx.parse); + initialSM = null; + } - this.client = xmpp; - processPendingOnIq(); + this.client = xmpp; + processPendingOnIq(); - xmpp.on("online", function (jid) { - resumed = false; - this.jid = jid; - this.state.event("connection-success"); - }); + xmpp.on("online", function (jid) { + resumed = false; + this.jid = jid; + this.state.event("connection-success"); + }); - xmpp.on("offline", function (data) { - this.state.event("connection-closed"); - }); + xmpp.on("offline", function (data) { + this.state.event("connection-closed"); + }); - xmpp.on("stanza", function (stanza) { - if (xmpp.status == "online" && this.state.can("connection-success")) { - resumed = xmpp.streamManagement.enabled && xmpp.streamManagement.id != null && xmpp.streamManagement.id != ""; - if (xmpp.jid == null) { - xmpp.jid = this.jid; - } else { - this.jid = xmpp.jid; - } - this.state.event("connection-success"); + xmpp.on("stanza", function (stanza) { + if (xmpp.status == "online" && this.state.can("connection-success")) { + resumed = xmpp.streamManagement.enabled && xmpp.streamManagement.id != null && xmpp.streamManagement.id != ""; + if (xmpp.jid == null) { + xmpp.jid = this.jid; + } else { + this.jid = xmpp.jid; } - this.onStanza(convertToStanza(stanza)); - triggerSMupdate(); - }); + this.state.event("connection-success"); + } + this.onStanza(convertToStanza(stanza)); + triggerSMupdate(); + }); - xmpp.on("stream-management/ack", (stanza) -> { - if (stanza.name == "message" && stanza.attrs.id != null) this.trigger("sm/ack", { id: stanza.attrs.id }); - triggerSMupdate(); - }); + xmpp.on("stream-management/ack", (stanza) -> { + if (stanza.name == "message" && stanza.attrs.id != null) this.trigger("sm/ack", { id: stanza.attrs.id }); + triggerSMupdate(); + }); - xmpp.on("stream-management/fail", (stanza) -> { - if (stanza.name == "message" && stanza.attrs.id != null) this.trigger("sm/fail", { id: stanza.attrs.id }); - triggerSMupdate(); - }); + xmpp.on("stream-management/fail", (stanza) -> { + if (stanza.name == "message" && stanza.attrs.id != null) this.trigger("sm/fail", { id: stanza.attrs.id }); + triggerSMupdate(); + }); - resumed = false; - xmpp.start().catchError(function (err) { - trace(err); - }); - return EventHandled; + xmpp.on("fast-token", (tokenEl) -> { + this.trigger("fast-token", tokenEl.attrs); + }); + + resumed = false; + xmpp.start().catchError(function (err) { + trace(err); }); - this.trigger("auth/password-needed", {}); } public function connect(jid:String, sm:Null<{id:String,outbound:Int,inbound:Int,outbound_q:Array<String>}>) {