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..4f2b52f5 --- /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 stop(...args) { + entity.removeListener("nonza", listener); + resolve(...args); + } + + async function listener(element) { + try { + await handler(element, stop); + } catch (err) { + reject(err); + entity.removeListener("nonza", listener); + } + } + + stanza && + entity.send(stanza).catch((err) => { + entity.removeListener("nonza", listener); + reject(err); + }); + entity.on("nonza", listener); + }); +} diff --git a/packages/sasl/index.js b/packages/sasl/index.js index f4a4b596..ec0bb93c 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, stop) => { + 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 stop(); + } + }, + ); } 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..185b71b9 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,11 +37,16 @@ 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, stop) => { + if (element.getNS() !== NS) return; if (element.name === "challenge") { mech.challenge(decode(element.text())); @@ -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 stop(); } - - 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..ea8749d0 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, stop) => { + if (element.is("enabled", NS)) { + return stop(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, stop) => { + if (element.is("resumed", NS)) { + return stop(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..8616f2ce 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,8 +114,6 @@ test("enable - failed", async () => { expect(entity.streamManagement.outbound).toBe(0); entity.streamManagement.enabled = true; - await tick(); - entity.mockInput(); await tick();