From 25cf82ca410058bb933e9f16e89cf25a42c18caa Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Wed, 11 Oct 2023 13:14:51 +0200 Subject: [PATCH 1/6] feat: trigger reconnect on emit if not connected --- source/event_hub.ts | 2 +- source/simple_socketio.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/source/event_hub.ts b/source/event_hub.ts index e72b3b5..4cbe451 100644 --- a/source/event_hub.ts +++ b/source/event_hub.ts @@ -189,7 +189,7 @@ export class EventHub { * @return {Boolean} */ isConnected(): boolean { - return this._socketIo?.isConnected() || false; + return this._socketIo?.socket.connected || false; } /** diff --git a/source/simple_socketio.ts b/source/simple_socketio.ts index 430514a..8731ae9 100644 --- a/source/simple_socketio.ts +++ b/source/simple_socketio.ts @@ -313,11 +313,14 @@ export default class SimpleSocketIOClient { const dataString = eventData ? `:::${JSON.stringify(payload)}` : ""; const packet = `${PACKET_TYPES.event}${dataString}`; - if (this.webSocket?.readyState === WebSocket.OPEN) { + if (this.isConnected()) { this.webSocket.send(packet); } else { this.packetQueue.push(packet); } + if (this.webSocket && !this.socket.connected && !this.reconnecting) { + this.reconnect(); + } } /** * Send the queued packets to the server From 3dfae090273af4a0638efce3e4267f5285de456e Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Wed, 11 Oct 2023 16:37:13 +0200 Subject: [PATCH 2/6] change order of setting connected = true and handling the connect event. --- source/simple_socketio.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/simple_socketio.ts b/source/simple_socketio.ts index 8731ae9..c9cb78d 100644 --- a/source/simple_socketio.ts +++ b/source/simple_socketio.ts @@ -235,10 +235,10 @@ export default class SimpleSocketIOClient { } this.reconnecting = false; this.reconnectionAttempts = 0; // Reset reconnection attempts - this.handleEvent("connect", {}); - this.flushPacketQueue(); // Set connected property to true this.socket.connected = true; + this.handleEvent("connect", {}); + this.flushPacketQueue(); } /** * Handles WebSocket closing From 44553cb7e2f6c63f46bba688de212dcbdd262d9d Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Tue, 24 Oct 2023 12:19:31 +0200 Subject: [PATCH 3/6] Await initializing method on reconnect --- source/simple_socketio.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/simple_socketio.ts b/source/simple_socketio.ts index c9cb78d..d3744ec 100644 --- a/source/simple_socketio.ts +++ b/source/simple_socketio.ts @@ -55,6 +55,7 @@ export default class SimpleSocketIOClient { private packetQueue: string[] = []; private reconnectionAttempts: number = 0; private reconnecting: boolean = false; + private initializingPromise: Promise; // Added socket object with connected, open reconnect and transport properties to match current API // The old socket-io client uses both a connected and open property that are interchangeable @@ -129,7 +130,7 @@ export default class SimpleSocketIOClient { this.heartbeatTimeoutMs = heartbeatTimeoutMs; this.apiUser = apiUser; this.apiKey = apiKey; - this.initializeWebSocket(); + this.initializingPromise = this.initializeWebSocket(); } /** * Fetches the session ID from the ftrack server. @@ -318,7 +319,7 @@ export default class SimpleSocketIOClient { } else { this.packetQueue.push(packet); } - if (this.webSocket && !this.socket.connected && !this.reconnecting) { + if (!this.socket.connected) { this.reconnect(); } } @@ -407,15 +408,16 @@ export default class SimpleSocketIOClient { * @private * @param randomizedDelay */ - private attemptReconnect(): void { + private async attemptReconnect(): Promise { + await this.initializingPromise; // Check if already connected or if active reconnection attempt ongoing. if (this.socket.connected || this.reconnecting) { return; } this.reconnecting = true; this.reconnectionAttempts++; - this.initializeWebSocket(); this.reconnectTimeout = undefined; + this.initializingPromise = this.initializeWebSocket(); this.reconnect(); } /** From 6327b50ff11b057106472300ea67909cea0976bc Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Tue, 24 Oct 2023 12:29:10 +0200 Subject: [PATCH 4/6] Asyncify the initialization tests --- source/simple_socketio.ts | 2 +- test/simple_socketio.test.js | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/source/simple_socketio.ts b/source/simple_socketio.ts index d3744ec..2c5080b 100644 --- a/source/simple_socketio.ts +++ b/source/simple_socketio.ts @@ -55,7 +55,7 @@ export default class SimpleSocketIOClient { private packetQueue: string[] = []; private reconnectionAttempts: number = 0; private reconnecting: boolean = false; - private initializingPromise: Promise; + initializingPromise: Promise; // Added socket object with connected, open reconnect and transport properties to match current API // The old socket-io client uses both a connected and open property that are interchangeable diff --git a/test/simple_socketio.test.js b/test/simple_socketio.test.js index 8473d1e..7fc6d8b 100644 --- a/test/simple_socketio.test.js +++ b/test/simple_socketio.test.js @@ -411,18 +411,18 @@ describe("Tests using SimpleSocketIOClient", () => { ); }); describe("Reconnection tests", () => { - test("attemptReconnect method initialises websocket again", () => { + test("attemptReconnect method initialises websocket again", async () => { client.initializeWebSocket = vi.fn(); - client.attemptReconnect(); + await client.attemptReconnect(); expect(client.initializeWebSocket).toHaveBeenCalledTimes(1); }); - test("attemptReconnect method increments attempts count", () => { + test("attemptReconnect method increments attempts count", async () => { const initialAttempts = client.reconnectionAttempts; - client.attemptReconnect(); + await client.attemptReconnect(); expect(client.reconnectionAttempts).toBe(initialAttempts + 1); }); @@ -456,7 +456,7 @@ describe("Tests using SimpleSocketIOClient", () => { // Reconnect should not be called yet expect(client.attemptReconnect).toHaveBeenCalledTimes(0); }); - test("reconnect method exponentially increase delay for every attempt, stopping at the max value", () => { + test("reconnect method exponentially increase delay for every attempt, stopping at the max value", async () => { const originalRandom = Math.random; Math.random = vi.fn().mockReturnValue(1); vi.useFakeTimers(); @@ -468,6 +468,7 @@ describe("Tests using SimpleSocketIOClient", () => { const expectedMaxDelay = expectedMinDelay * 1.5; client.reconnecting = false; // Since it never gets to the actual fail state triggered by client.reconnect(); + await client.initializingPromise; vi.advanceTimersByTime(expectedMaxDelay + 1); expect(client.attemptReconnect).toHaveBeenCalledTimes(i + 1); } From 992d395535263c23ace189580c4f63043f59ae32 Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Tue, 24 Oct 2023 12:40:39 +0200 Subject: [PATCH 5/6] Add test and simplify emit reconnect code --- source/simple_socketio.ts | 2 -- test/simple_socketio.test.js | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/source/simple_socketio.ts b/source/simple_socketio.ts index 2c5080b..5146640 100644 --- a/source/simple_socketio.ts +++ b/source/simple_socketio.ts @@ -318,8 +318,6 @@ export default class SimpleSocketIOClient { this.webSocket.send(packet); } else { this.packetQueue.push(packet); - } - if (!this.socket.connected) { this.reconnect(); } } diff --git a/test/simple_socketio.test.js b/test/simple_socketio.test.js index 7fc6d8b..bb8c5a7 100644 --- a/test/simple_socketio.test.js +++ b/test/simple_socketio.test.js @@ -410,6 +410,30 @@ describe("Tests using SimpleSocketIOClient", () => { `${PACKET_TYPES.event}${expectedDataString}`, ); }); + test("emit triggers a reconnect if not connected", () => { + client.webSocket = createWebSocketMock(); + client.webSocket.readyState = WebSocket.CLOSED; + + const reconnectSpy = vi.spyOn(client, "reconnect"); + + const eventName = "testEvent"; + const eventData = { foo: "bar" }; + + client.emit(eventName, eventData); + + expect(reconnectSpy).toHaveBeenCalledTimes(1); + + const expectedPayload = { + name: eventName, + args: [eventData], + }; + const expectedDataString = `:::${JSON.stringify(expectedPayload)}`; + expect(client.packetQueue).toContainEqual( + `${PACKET_TYPES.event}${expectedDataString}`, + ); + + reconnectSpy.mockRestore(); + }); describe("Reconnection tests", () => { test("attemptReconnect method initialises websocket again", async () => { client.initializeWebSocket = vi.fn(); From edc804e10c0356595219d3719bdc41e111df133d Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Tue, 24 Oct 2023 12:51:56 +0200 Subject: [PATCH 6/6] Rename initializingPromise to initializing --- source/simple_socketio.ts | 8 ++++---- test/simple_socketio.test.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/simple_socketio.ts b/source/simple_socketio.ts index 5146640..9e288b2 100644 --- a/source/simple_socketio.ts +++ b/source/simple_socketio.ts @@ -55,7 +55,7 @@ export default class SimpleSocketIOClient { private packetQueue: string[] = []; private reconnectionAttempts: number = 0; private reconnecting: boolean = false; - initializingPromise: Promise; + initializing: Promise; // Added socket object with connected, open reconnect and transport properties to match current API // The old socket-io client uses both a connected and open property that are interchangeable @@ -130,7 +130,7 @@ export default class SimpleSocketIOClient { this.heartbeatTimeoutMs = heartbeatTimeoutMs; this.apiUser = apiUser; this.apiKey = apiKey; - this.initializingPromise = this.initializeWebSocket(); + this.initializing = this.initializeWebSocket(); } /** * Fetches the session ID from the ftrack server. @@ -407,7 +407,7 @@ export default class SimpleSocketIOClient { * @param randomizedDelay */ private async attemptReconnect(): Promise { - await this.initializingPromise; + await this.initializing; // Check if already connected or if active reconnection attempt ongoing. if (this.socket.connected || this.reconnecting) { return; @@ -415,7 +415,7 @@ export default class SimpleSocketIOClient { this.reconnecting = true; this.reconnectionAttempts++; this.reconnectTimeout = undefined; - this.initializingPromise = this.initializeWebSocket(); + this.initializing = this.initializeWebSocket(); this.reconnect(); } /** diff --git a/test/simple_socketio.test.js b/test/simple_socketio.test.js index bb8c5a7..96a6e17 100644 --- a/test/simple_socketio.test.js +++ b/test/simple_socketio.test.js @@ -492,7 +492,7 @@ describe("Tests using SimpleSocketIOClient", () => { const expectedMaxDelay = expectedMinDelay * 1.5; client.reconnecting = false; // Since it never gets to the actual fail state triggered by client.reconnect(); - await client.initializingPromise; + await client.initializing; vi.advanceTimersByTime(expectedMaxDelay + 1); expect(client.attemptReconnect).toHaveBeenCalledTimes(i + 1); }