diff --git a/package-lock.json b/package-lock.json index d9e9792c..621c4955 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14761,6 +14761,7 @@ "dependencies": { "@xmpp/base64": "^0.14.0", "@xmpp/error": "^0.14.0", + "@xmpp/events": "^0.14.0", "@xmpp/xml": "^0.14.0" }, "engines": { @@ -14819,6 +14820,7 @@ "dependencies": { "@xmpp/base64": "^0.14.0", "@xmpp/error": "^0.14.0", + "@xmpp/events": "^0.14.0", "@xmpp/jid": "^0.14.0", "@xmpp/sasl": "^0.14.0", "@xmpp/xml": "^0.14.0" @@ -14864,6 +14866,8 @@ "version": "0.14.0", "license": "ISC", "dependencies": { + "@xmpp/error": "^0.14.0", + "@xmpp/events": "^0.14.0", "@xmpp/xml": "^0.14.0" }, "engines": { diff --git a/packages/events/index.js b/packages/events/index.js index a2571313..55f59a9a 100644 --- a/packages/events/index.js +++ b/packages/events/index.js @@ -5,5 +5,14 @@ import delay from "./lib/delay.js"; import TimeoutError from "./lib/TimeoutError.js"; import promise from "./lib/promise.js"; import Deferred from "./lib/Deferred.js"; +import procedure from "./lib/procedure.js"; -export { EventEmitter, timeout, delay, TimeoutError, promise, Deferred }; +export { + EventEmitter, + timeout, + delay, + TimeoutError, + promise, + Deferred, + procedure, +}; diff --git a/packages/events/lib/procedure.js b/packages/events/lib/procedure.js new file mode 100644 index 00000000..47614d3d --- /dev/null +++ b/packages/events/lib/procedure.js @@ -0,0 +1,24 @@ +export default function procedure(entity, stanza = null, handler) { + return new Promise((resolve, reject) => { + function onError(err) { + entity.removeListener("nonza", listener); + reject(err); + } + + function done(...args) { + entity.removeListener("nonza", listener); + resolve(...args); + } + + async function listener(element) { + try { + await handler(element, done); + } catch (err) { + onError(err); + } + } + + stanza && entity.send(stanza).catch(onError); + entity.on("nonza", listener); + }); +} diff --git a/packages/sasl/index.js b/packages/sasl/index.js index f4a4b596..162bbe81 100644 --- a/packages/sasl/index.js +++ b/packages/sasl/index.js @@ -1,6 +1,7 @@ import { encode, decode } from "@xmpp/base64"; import SASLError from "./lib/SASLError.js"; import xml from "@xmpp/xml"; +import { procedure } from "@xmpp/events"; // https://xmpp.org/rfcs/rfc6120.html#sasl @@ -28,16 +29,21 @@ async function authenticate({ saslFactory, entity, mechanism, credentials }) { ...credentials, }; - return new Promise((resolve, reject) => { - const handler = (element) => { - if (element.attrs.xmlns !== NS) { - return; - } + await procedure( + entity, + mech.clientFirst && + xml( + "auth", + { xmlns: NS, mechanism: mech.name }, + encode(mech.response(creds)), + ), + async (element, done) => { + if (element.getNS() !== NS) return; if (element.name === "challenge") { mech.challenge(decode(element.text())); const resp = mech.response(creds); - entity.send( + await entity.send( xml( "response", { xmlns: NS, mechanism: mech.name }, @@ -48,26 +54,14 @@ async function authenticate({ saslFactory, entity, mechanism, credentials }) { } if (element.name === "failure") { - reject(SASLError.fromElement(element)); - } else if (element.name === "success") { - resolve(); + throw SASLError.fromElement(element); } - entity.removeListener("nonza", handler); - }; - - entity.on("nonza", handler); - - if (mech.clientFirst) { - entity.send( - xml( - "auth", - { xmlns: NS, mechanism: mech.name }, - encode(mech.response(creds)), - ), - ); - } - }); + if (element.name === "success") { + return done(); + } + }, + ); } export default function sasl({ streamFeatures, saslFactory }, onAuthenticate) { diff --git a/packages/sasl/package.json b/packages/sasl/package.json index 93f8fc06..445c5b92 100644 --- a/packages/sasl/package.json +++ b/packages/sasl/package.json @@ -15,6 +15,7 @@ "dependencies": { "@xmpp/base64": "^0.14.0", "@xmpp/error": "^0.14.0", + "@xmpp/events": "^0.14.0", "@xmpp/xml": "^0.14.0" }, "engines": { diff --git a/packages/sasl2/index.js b/packages/sasl2/index.js index 1b93b46f..83641d29 100644 --- a/packages/sasl2/index.js +++ b/packages/sasl2/index.js @@ -1,6 +1,7 @@ import { encode, decode } from "@xmpp/base64"; import SASLError from "@xmpp/sasl/lib/SASLError.js"; import xml from "@xmpp/xml"; +import { procedure } from "@xmpp/events"; // https://xmpp.org/extensions/xep-0388.html @@ -36,16 +37,21 @@ async function authenticate({ ...credentials, }; - return new Promise((resolve, reject) => { - const handler = (element) => { - if (element.getNS() !== NS) { - return; - } + await procedure( + entity, + xml("authenticate", { xmlns: NS, mechanism: mech.name }, [ + mech.clientFirst && + xml("initial-response", {}, encode(mech.response(creds))), + userAgent, + ...streamFeatures, + ]), + async (element, done) => { + if (element.getNS() !== NS) return; if (element.name === "challenge") { mech.challenge(decode(element.text())); const resp = mech.response(creds); - entity.send( + await entity.send( xml( "response", { xmlns: NS, mechanism: mech.name }, @@ -56,13 +62,11 @@ async function authenticate({ } if (element.name === "failure") { - reject(SASLError.fromElement(element)); - return; + throw SASLError.fromElement(element); } if (element.name === "continue") { - reject(new Error("continue is not supported yet")); - return; + throw new Error("SASL continue is not supported yet"); } if (element.name === "success") { @@ -83,25 +87,10 @@ async function authenticate({ feature?.[1]?.(child); } - resolve(element); + return done(); } - - entity.removeListener("nonza", handler); - }; - - entity.on("nonza", handler); - - entity - .send( - xml("authenticate", { xmlns: NS, mechanism: mech.name }, [ - mech.clientFirst && - xml("initial-response", {}, encode(mech.response(creds))), - userAgent, - ...streamFeatures, - ]), - ) - .catch(reject); - }); + }, + ); } export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) { diff --git a/packages/sasl2/package.json b/packages/sasl2/package.json index 5a12666a..c37478f2 100644 --- a/packages/sasl2/package.json +++ b/packages/sasl2/package.json @@ -15,6 +15,7 @@ "dependencies": { "@xmpp/base64": "^0.14.0", "@xmpp/error": "^0.14.0", + "@xmpp/events": "^0.14.0", "@xmpp/jid": "^0.14.0", "@xmpp/sasl": "^0.14.0", "@xmpp/xml": "^0.14.0" diff --git a/packages/stream-management/index.js b/packages/stream-management/index.js index 74fb74eb..9f162ce3 100644 --- a/packages/stream-management/index.js +++ b/packages/stream-management/index.js @@ -1,3 +1,5 @@ +import XMPPError from "@xmpp/error"; +import { procedure } from "@xmpp/events"; import xml from "@xmpp/xml"; // https://xmpp.org/extensions/xep-0198.html @@ -16,34 +18,24 @@ function makeResumeElement({ sm }) { return xml("resume", { xmlns: NS, h: sm.inbound, previd: sm.id }); } -async function enable(entity, sm) { - await entity.send(makeEnableElement({ sm })); - - return new Promise((resolve, reject) => { - function listener(nonza) { - if (nonza.is("enabled", NS)) { - resolve(nonza); - } else if (nonza.is("failed", NS)) { - reject(nonza); - } else { - return; - } - - entity.removeListener("nonza", listener); +function enable(entity, sm) { + return procedure(entity, makeEnableElement({ sm }), (element, done) => { + if (element.is("enabled", NS)) { + return done(element); + } else if (element.is("failed", NS)) { + throw XMPPError.fromElement(element); } - - entity.on("nonza", listener); }); } async function resume(entity, sm) { - const response = await entity.sendReceive(makeResumeElement({ sm })); - - if (!response.is("resumed", NS)) { - throw response; - } - - return response; + return procedure(entity, makeResumeElement({ sm }), (element, done) => { + if (element.is("resumed", NS)) { + return done(element); + } else if (element.is("failed", NS)) { + throw XMPPError.fromElement(element); + } + }); } export default function streamManagement({ diff --git a/packages/stream-management/package.json b/packages/stream-management/package.json index 4e3b2d9f..e9818cbb 100644 --- a/packages/stream-management/package.json +++ b/packages/stream-management/package.json @@ -14,6 +14,8 @@ "management" ], "dependencies": { + "@xmpp/error": "^0.14.0", + "@xmpp/events": "^0.14.0", "@xmpp/xml": "^0.14.0" }, "engines": { diff --git a/packages/stream-management/stream-features.test.js b/packages/stream-management/stream-features.test.js index 7f2e81cf..a8edc437 100644 --- a/packages/stream-management/stream-features.test.js +++ b/packages/stream-management/stream-features.test.js @@ -21,8 +21,6 @@ test("enable - enabled", async () => { , ); - await tick(); - expect(entity.streamManagement.outbound).toBe(0); expect(entity.streamManagement.enabled).toBe(false); expect(entity.streamManagement.id).toBe(""); @@ -56,8 +54,6 @@ test("enable - send rejects", async () => { />, ); - await tick(); - expect(entity.streamManagement.enabled).toBe(false); }); @@ -80,8 +76,6 @@ test("enable - message - enabled", async () => { expect(entity.streamManagement.enabled).toBe(false); expect(entity.streamManagement.id).toBe(""); - await tick(); - entity.mockInput(); expect(entity.streamManagement.enabled).toBe(false); @@ -120,9 +114,11 @@ test("enable - failed", async () => { expect(entity.streamManagement.outbound).toBe(0); entity.streamManagement.enabled = true; - await tick(); - - entity.mockInput(); + entity.mockInput( + + + , + ); await tick(); @@ -177,7 +173,11 @@ test("resume - failed", async () => { , ); - entity.mockInput(); + entity.mockInput( + + + , + ); await tick();