Skip to content

Commit ee361dc

Browse files
authored
stream-management: Request ack on connect (#1056)
1 parent 9ecdf1e commit ee361dc

File tree

7 files changed

+52
-141
lines changed

7 files changed

+52
-141
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
"selfsigned": "^2.4.1"
3636
},
3737
"scripts": {
38-
"test": "node --experimental-websocket ./node_modules/.bin/jest",
39-
"e2e": "NODE_TLS_REJECT_UNAUTHORIZED=0 node --experimental-websocket ./node_modules/.bin/jest --runInBand --config e2e.config.cjs",
38+
"test": "node --experimental-websocket ./node_modules/.bin/jest --forceExit",
39+
"e2e": "NODE_TLS_REJECT_UNAUTHORIZED=0 node --experimental-websocket ./node_modules/.bin/jest --forceExit --runInBand --config e2e.config.cjs",
4040
"preversion": "make bundle"
4141
},
4242
"engines": {

packages/client-core/src/fast/fast.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,8 @@ export default function fast({ sasl2, entity }) {
6060
}
6161

6262
const { token } = credentials;
63-
// Invalid or unavailable token
6463
if (!isTokenValid(token, fast.mechanisms)) {
65-
requestToken(streamFeatures);
66-
return false;
64+
return onInvalidToken();
6765
}
6866

6967
try {
@@ -90,13 +88,17 @@ export default function fast({ sasl2, entity }) {
9088
err instanceof SASLError &&
9189
["not-authorized", "credentials-expired"].includes(err.condition)
9290
) {
93-
await this.delete();
94-
requestToken(streamFeatures);
95-
return false;
91+
return onInvalidToken();
9692
}
9793
entity.emit("error", err);
9894
return false;
9995
}
96+
97+
async function onInvalidToken() {
98+
await fast.delete();
99+
requestToken(streamFeatures);
100+
return false;
101+
}
100102
},
101103
});
102104

packages/client/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ xmpp.on("offline", () => {
5858
xmpp.on("stanza", onStanza);
5959
async function onStanza(stanza) {
6060
if (stanza.is("message")) {
61-
xmpp.off("stanza", onStanza);
61+
xmpp.removeListener("stanza", onStanza);
6262
await xmpp.send(xml("presence", { type: "unavailable" }));
6363
await xmpp.stop();
6464
}

packages/client/example.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ xmpp.on("offline", () => {
2828
xmpp.on("stanza", onStanza);
2929
async function onStanza(stanza) {
3030
if (stanza.is("message")) {
31-
xmpp.off("stanza", onStanza);
31+
xmpp.removeListener("stanza", onStanza);
3232
await xmpp.send(xml("presence", { type: "unavailable" }));
3333
await xmpp.stop();
3434
}

packages/stream-management/index.js

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ export default function streamManagement({
6060
inbound: 0,
6161
max: null,
6262
timeout: 60_000,
63-
requestAckInterval: 300_000,
64-
debounceAckRequest: 100,
63+
requestAckInterval: 30_000,
6564
});
6665

6766
entity.on("disconnect", () => {
@@ -71,23 +70,32 @@ export default function streamManagement({
7170

7271
async function resumed(resumed) {
7372
sm.enabled = true;
74-
const oldOutbound = sm.outbound;
75-
for (let i = 0; i < resumed.attrs.h - oldOutbound; i++) {
76-
let item = sm.outbound_q.shift();
77-
sm.outbound++;
78-
sm.emit("ack", item.stanza);
79-
}
73+
ackQueue(+resumed.attrs.h);
8074
let q = sm.outbound_q;
8175
sm.outbound_q = [];
8276
// This will trigger the middleware and re-add to the queue
8377
await entity.sendMany(q.map((item) => queueToStanza({ entity, item })));
8478
sm.emit("resumed");
8579
entity._ready(true);
80+
scheduleRequestAck();
8681
}
8782

8883
function failed() {
8984
sm.enabled = false;
9085
sm.id = "";
86+
failQueue();
87+
}
88+
89+
function ackQueue(n) {
90+
const oldOutbound = sm.outbound;
91+
for (let i = 0; i < +n - oldOutbound; i++) {
92+
const item = sm.outbound_q.shift();
93+
sm.outbound++;
94+
sm.emit("ack", item.stanza);
95+
}
96+
}
97+
98+
function failQueue() {
9199
let item;
92100
while ((item = sm.outbound_q.shift())) {
93101
sm.emit("fail", item.stanza);
@@ -99,6 +107,7 @@ export default function streamManagement({
99107
sm.enabled = true;
100108
sm.id = id;
101109
sm.max = max;
110+
scheduleRequestAck();
102111
}
103112

104113
entity.on("online", () => {
@@ -112,11 +121,7 @@ export default function streamManagement({
112121
});
113122

114123
entity.on("offline", () => {
115-
let item;
116-
while ((item = sm.outbound_q.shift())) {
117-
sm.emit("fail", item.stanza);
118-
}
119-
sm.outbound = 0;
124+
failQueue();
120125
sm.inbound = 0;
121126
sm.enabled = false;
122127
sm.id = "";
@@ -132,14 +137,11 @@ export default function streamManagement({
132137
entity.send(xml("a", { xmlns: NS, h: sm.inbound })).catch(() => {});
133138
} else if (stanza.is("a", NS)) {
134139
// > When a party receives an <a/> element, it SHOULD keep a record of the 'h' value returned as the sequence number of the last handled outbound stanza for the current stream (and discard the previous value).
135-
const oldOutbound = sm.outbound;
136-
for (let i = 0; i < stanza.attrs.h - oldOutbound; i++) {
137-
let item = sm.outbound_q.shift();
138-
sm.outbound++;
139-
sm.emit("ack", item.stanza);
140-
}
140+
ackQueue(+stanza.attrs.h);
141141
}
142142

143+
scheduleRequestAck();
144+
143145
return next();
144146
});
145147

@@ -150,18 +152,31 @@ export default function streamManagement({
150152
setupSasl2({ sasl2, sm, failed, resumed });
151153
}
152154

155+
// Periodically send r to check the connection
156+
// If a stanza goes out it will cancel this and set a sooner timer
157+
function scheduleRequestAck(timeout = sm.requestAckInterval) {
158+
clearTimeout(requestAckTimeout);
159+
160+
if (!sm.enabled) return;
161+
162+
requestAckTimeout = setTimeout(requestAck, timeout);
163+
}
164+
153165
function requestAck() {
154166
clearTimeout(timeoutTimeout);
167+
clearTimeout(requestAckTimeout);
168+
169+
if (!sm.enabled) return;
170+
155171
if (sm.timeout) {
156172
timeoutTimeout = setTimeout(
157173
() => entity.disconnect().catch(() => {}),
158174
sm.timeout,
159175
);
160176
}
161177
entity.send(xml("r", { xmlns: NS })).catch(() => {});
162-
// Periodically send r to check the connection
163-
// If a stanza goes out it will cancel this and set a sooner timer
164-
requestAckTimeout = setTimeout(requestAck, sm.requestAckInterval);
178+
179+
scheduleRequestAck();
165180
}
166181

167182
middleware.filter((context, next) => {
@@ -171,8 +186,8 @@ export default function streamManagement({
171186

172187
sm.outbound_q.push({ stanza, stamp: datetime() });
173188
// Debounce requests so we send only one after a big run of stanza together
174-
clearTimeout(requestAckTimeout);
175-
requestAckTimeout = setTimeout(requestAck, sm.debounceAckRequest);
189+
queueMicrotask(requestAck);
190+
176191
return next();
177192
});
178193

test/stream-management.js

Lines changed: 0 additions & 106 deletions
This file was deleted.

test/stream-management.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ test(
9494
xmpp = client({ credentials, service: domain });
9595
xmpp.streamManagement.timeout = 10;
9696
xmpp.streamManagement.debounceAckRequest = 1;
97-
debug(xmpp, true);
97+
debug(xmpp);
9898

9999
const promise_resumed = promise(xmpp.streamManagement, "resumed");
100100
await xmpp.start();

0 commit comments

Comments
 (0)