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();