From 532204ca3e9db5aa5d2437a57e0c406e7c6f51b6 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Wed, 1 Jan 2025 15:45:36 +0100 Subject: [PATCH 1/2] Add @xmpp/events procedure to simplify code --- package-lock.json | 4 ++ packages/events/index.js | 11 ++++- packages/events/lib/procedure.js | 24 +++++++++++ packages/sasl/index.js | 42 ++++++++---------- packages/sasl/package.json | 1 + packages/sasl2/index.js | 43 +++++++------------ packages/sasl2/package.json | 1 + packages/stream-management/index.js | 38 +++++++--------- packages/stream-management/package.json | 2 + .../stream-management/stream-features.test.js | 8 ---- 10 files changed, 91 insertions(+), 83 deletions(-) create mode 100644 packages/events/lib/procedure.js 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(); From cf9927214a0d7285fe819781485eddce5a1da8cb Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Wed, 1 Jan 2025 15:55:22 +0100 Subject: [PATCH 2/2] f --- packages/events/lib/procedure.js | 18 +++++++++--------- packages/sasl/index.js | 4 ++-- packages/sasl2/index.js | 6 +++--- packages/stream-management/index.js | 8 ++++---- .../stream-management/stream-features.test.js | 12 ++++++++++-- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/events/lib/procedure.js b/packages/events/lib/procedure.js index 4f2b52f5..47614d3d 100644 --- a/packages/events/lib/procedure.js +++ b/packages/events/lib/procedure.js @@ -1,24 +1,24 @@ export default function procedure(entity, stanza = null, handler) { return new Promise((resolve, reject) => { - function stop(...args) { + 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, stop); + await handler(element, done); } catch (err) { - reject(err); - entity.removeListener("nonza", listener); + onError(err); } } - stanza && - entity.send(stanza).catch((err) => { - entity.removeListener("nonza", listener); - reject(err); - }); + stanza && entity.send(stanza).catch(onError); entity.on("nonza", listener); }); } diff --git a/packages/sasl/index.js b/packages/sasl/index.js index ec0bb93c..162bbe81 100644 --- a/packages/sasl/index.js +++ b/packages/sasl/index.js @@ -37,7 +37,7 @@ async function authenticate({ saslFactory, entity, mechanism, credentials }) { { xmlns: NS, mechanism: mech.name }, encode(mech.response(creds)), ), - async (element, stop) => { + async (element, done) => { if (element.getNS() !== NS) return; if (element.name === "challenge") { @@ -58,7 +58,7 @@ async function authenticate({ saslFactory, entity, mechanism, credentials }) { } if (element.name === "success") { - return stop(); + return done(); } }, ); diff --git a/packages/sasl2/index.js b/packages/sasl2/index.js index 185b71b9..83641d29 100644 --- a/packages/sasl2/index.js +++ b/packages/sasl2/index.js @@ -45,13 +45,13 @@ async function authenticate({ userAgent, ...streamFeatures, ]), - async (element, stop) => { + 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 }, @@ -87,7 +87,7 @@ async function authenticate({ feature?.[1]?.(child); } - return stop(); + return done(); } }, ); diff --git a/packages/stream-management/index.js b/packages/stream-management/index.js index ea8749d0..9f162ce3 100644 --- a/packages/stream-management/index.js +++ b/packages/stream-management/index.js @@ -19,9 +19,9 @@ function makeResumeElement({ sm }) { } function enable(entity, sm) { - return procedure(entity, makeEnableElement({ sm }), (element, stop) => { + return procedure(entity, makeEnableElement({ sm }), (element, done) => { if (element.is("enabled", NS)) { - return stop(element); + return done(element); } else if (element.is("failed", NS)) { throw XMPPError.fromElement(element); } @@ -29,9 +29,9 @@ function enable(entity, sm) { } async function resume(entity, sm) { - return procedure(entity, makeResumeElement({ sm }), (element, stop) => { + return procedure(entity, makeResumeElement({ sm }), (element, done) => { if (element.is("resumed", NS)) { - return stop(element); + return done(element); } else if (element.is("failed", NS)) { throw XMPPError.fromElement(element); } diff --git a/packages/stream-management/stream-features.test.js b/packages/stream-management/stream-features.test.js index 8616f2ce..a8edc437 100644 --- a/packages/stream-management/stream-features.test.js +++ b/packages/stream-management/stream-features.test.js @@ -114,7 +114,11 @@ test("enable - failed", async () => { expect(entity.streamManagement.outbound).toBe(0); entity.streamManagement.enabled = true; - entity.mockInput(); + entity.mockInput( + + + , + ); await tick(); @@ -169,7 +173,11 @@ test("resume - failed", async () => { , ); - entity.mockInput(); + entity.mockInput( + + + , + ); await tick();