diff --git a/packages/connection/index.js b/packages/connection/index.js index 20318d66..5c1cfcbb 100644 --- a/packages/connection/index.js +++ b/packages/connection/index.js @@ -245,21 +245,6 @@ class Connection extends EventEmitter { await promise(this.socket, "close", "error", timeout); } - /** - * Forcibly disconnects the socket - * https://xmpp.org/rfcs/rfc6120.html#streams-close - * https://tools.ietf.org/html/rfc7395#section-3.6 - */ - async forceDisconnect(timeout = this.timeout) { - if (!this.socket) return; - - this._status("disconnecting"); - this.socket.destroy(); - - // The 'disconnect' status is set by the socket 'close' listener - await promise(this.socket, "close", "error", timeout); - } - /** * Opens the stream */ diff --git a/packages/stream-management/index.js b/packages/stream-management/index.js index 26eb9f37..78d76f23 100644 --- a/packages/stream-management/index.js +++ b/packages/stream-management/index.js @@ -154,7 +154,7 @@ export default function streamManagement({ clearTimeout(timeoutTimeout); if (sm.timeout) { timeoutTimeout = setTimeout( - () => entity.forceDisconnect().catch(), + () => entity.disconnect().catch(() => {}), sm.timeout, ); } diff --git a/packages/tls/lib/Socket.js b/packages/tls/lib/Socket.js index 93eef98d..f96ac449 100644 --- a/packages/tls/lib/Socket.js +++ b/packages/tls/lib/Socket.js @@ -50,10 +50,6 @@ class Socket extends EventEmitter { this.socket.end(); } - destroy() { - this.socket.destroy(); - } - write(data, fn) { this.socket.write(data, fn); } diff --git a/packages/websocket/lib/Socket.js b/packages/websocket/lib/Socket.js index 63b0f8d9..84a8ba04 100644 --- a/packages/websocket/lib/Socket.js +++ b/packages/websocket/lib/Socket.js @@ -62,10 +62,6 @@ export default class Socket extends EventEmitter { this.socket.close(); } - destroy() { - this.socket.close(); - } - write(data, fn) { function done(err) { if (!fn) return; diff --git a/test/stream-management.js b/test/stream-management.js index 5d8aacca..ef66faed 100644 --- a/test/stream-management.js +++ b/test/stream-management.js @@ -46,10 +46,12 @@ test("client fail stanzas", async () => { await xmpp.start(); // Expect send but don't actually send to server, so it will fail await xmpp.streamManagement.outbound_q.push({ - stanza: - - , - stamp: datetime() + stanza: ( + + + + ), + stamp: datetime(), }); await xmpp.stop(); @@ -68,10 +70,12 @@ test("client retry stanzas", async () => { await xmpp.start(); // Add to queue but don't actually send so it can retry after disconnect await xmpp.streamManagement.outbound_q.push({ - stanza: - - , - stamp: datetime() + stanza: ( + + + + ), + stamp: datetime(), }); await xmpp.disconnect(); @@ -79,7 +83,7 @@ test("client retry stanzas", async () => { expect(el.attrs.id).toEqual("ping"); }); -test("client reconnect automatically", async () => { +test("client reconnects when server fails to ack", async () => { await server.enableModules(["smacks"]); await server.restart(); diff --git a/test/stream-management.test.js b/test/stream-management.test.js new file mode 100644 index 00000000..fa5e055a --- /dev/null +++ b/test/stream-management.test.js @@ -0,0 +1,112 @@ +import { client } from "../packages/client/index.js"; +import { promise } from "../packages/events/index.js"; +import { datetime } from "../packages/time/index.js"; +import debug from "../packages/debug/index.js"; +import server from "../server/index.js"; + +const username = "client"; +const password = "foobar"; +const credentials = { username, password }; +const domain = "localhost"; + +let xmpp; + +afterEach(async () => { + await xmpp?.stop(); + await server.reset(); +}); + +test("client ack stanzas", async () => { + await server.enableModules(["smacks"]); + await server.restart(); + + xmpp = client({ credentials, service: domain }); + debug(xmpp); + + const promise_ack = promise(xmpp.streamManagement, "ack"); + await xmpp.start(); + await xmpp.send( + + + , + ); + + const el = await promise_ack; + expect(el.attrs.id).toEqual("ping"); +}); + +test("client fail stanzas", async () => { + await server.enableModules(["smacks"]); + await server.restart(); + + xmpp = client({ credentials, service: domain }); + debug(xmpp); + + const promise_fail = promise(xmpp.streamManagement, "fail"); + await xmpp.start(); + // Expect send but don't actually send to server, so it will fail + await xmpp.streamManagement.outbound_q.push({ + stanza: ( + + + + ), + stamp: datetime(), + }); + await xmpp.stop(); + + const el = await promise_fail; + expect(el.attrs.id).toEqual("ping"); +}); + +test("client retry stanzas", async () => { + await server.enableModules(["smacks"]); + await server.restart(); + + xmpp = client({ credentials, service: domain }); + debug(xmpp); + + const promise_ack = promise(xmpp.streamManagement, "ack"); + await xmpp.start(); + // Add to queue but don't actually send so it can retry after disconnect + await xmpp.streamManagement.outbound_q.push({ + stanza: ( + + + + ), + stamp: datetime(), + }); + await xmpp.disconnect(); + + const el = await promise_ack; + expect(el.attrs.id).toEqual("ping"); +}); + +test( + "client reconnects when server fails to ack stanza", + async () => { + await server.enableModules(["smacks"]); + await server.restart(); + + xmpp = client({ credentials, service: domain }); + xmpp.streamManagement.timeout = 10; + xmpp.streamManagement.debounceAckRequest = 1; + debug(xmpp, true); + + const promise_resumed = promise(xmpp.streamManagement, "resumed"); + await xmpp.start(); + xmpp.send( + + + , + ); + // Pretend we don't receive the ack by removing event listeners + // on the socket + xmpp._detachSocket(); + + await promise_resumed; + expect().pass(); + }, + 1000 * 10, +);