Skip to content

Commit 16ccdd6

Browse files
authored
chore: Make ws dependency optional for node.js environments (#288)
* dynamically import ws when needed * set up mocks
1 parent 0fe43e5 commit 16ccdd6

File tree

5 files changed

+48
-27
lines changed

5 files changed

+48
-27
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"devDependencies": {
2828
"@types/node": "^22.13.9",
2929
"@types/uuid": "^10.0.0",
30+
"@types/ws": "^8.18.0",
3031
"@vitest/eslint-plugin": "^1.1.36",
3132
"dayjs": "^1.11.13",
3233
"eslint": "^9.21.0",
@@ -60,7 +61,6 @@
6061
},
6162
"homepage": "http://ftrack.com",
6263
"dependencies": {
63-
"isomorphic-ws": "^5.0.0",
6464
"loglevel": "^1.9.2",
6565
"moment": "^2.30.1",
6666
"uuid": "^11.1.0"

source/simple_socketio.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// :copyright: Copyright (c) 2023 ftrack
2-
import WebSocket from "isomorphic-ws";
32
import { Event } from "./event.js";
43
export const PACKET_TYPES = {
54
disconnect: "0",
@@ -42,7 +41,7 @@ interface Payload {
4241
*/
4342
export default class SimpleSocketIOClient {
4443
private static instances: Map<string, SimpleSocketIOClient> = new Map();
45-
private webSocket: WebSocket;
44+
private webSocket: WebSocket | undefined;
4645
private handlers: EventHandlers = {};
4746
private reconnectTimeout: ReturnType<typeof setTimeout> | undefined;
4847
private heartbeatTimeout: ReturnType<typeof setInterval> | undefined;
@@ -172,7 +171,13 @@ export default class SimpleSocketIOClient {
172171
this.reconnecting = false;
173172
}
174173
const urlWithQueryAndSession = `${this.webSocketUrl}/socket.io/1/websocket/${sessionId}?${this.query}`;
175-
this.webSocket = new WebSocket(urlWithQueryAndSession);
174+
const WebSocketImpl =
175+
typeof WebSocket === "undefined"
176+
? // Fallback on ws package if WebSocket is not available
177+
((await import("ws")).default as typeof WebSocket)
178+
: WebSocket;
179+
180+
this.webSocket = new WebSocketImpl(urlWithQueryAndSession);
176181
// Set transport.websocket property as a public alias of the websocket
177182
this.socket.transport.websocket = this.webSocket;
178183
this.addInitialEventListeners(this.webSocket);
@@ -195,7 +200,7 @@ export default class SimpleSocketIOClient {
195200
* Handles WebSocket errors
196201
* @private
197202
*/
198-
private handleError(event: Event): void {
203+
private handleError(event: WebSocketEventMap["error"]): void {
199204
this.handleClose();
200205
console.error("WebSocket error:", event);
201206
}
@@ -322,7 +327,7 @@ export default class SimpleSocketIOClient {
322327
const packet = `${PACKET_TYPES.event}${dataString}`;
323328

324329
if (this.isConnected()) {
325-
this.webSocket.send(packet);
330+
this.webSocket?.send(packet);
326331
} else {
327332
this.packetQueue.push(packet);
328333
this.reconnect();
@@ -436,9 +441,9 @@ export default class SimpleSocketIOClient {
436441
this.packetQueue = [];
437442

438443
if (this.webSocket) {
439-
this.webSocket.onclose = undefined;
440-
this.webSocket.onerror = undefined;
441-
this.webSocket?.close();
444+
this.webSocket.onclose = null;
445+
this.webSocket.onerror = null;
446+
this.webSocket.close();
442447
this.webSocket = undefined;
443448
}
444449
if (this.reconnectTimeout) {

test/server.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
// :copyright: Copyright (c) 2022 ftrack
2-
import { HttpResponse, type PathParams, http, HttpHandler } from "msw";
2+
import {
3+
HttpResponse,
4+
type PathParams,
5+
http,
6+
HttpHandler,
7+
ws,
8+
WebSocketHandler,
9+
} from "msw";
310
import fs from "fs/promises";
411
import querySchemas from "./fixtures/query_schemas.json" with { type: "json" };
512
import queryServerInformation from "./fixtures/query_server_information.json" with { type: "json" };
@@ -47,8 +54,10 @@ const handleSocketIORequest: Parameters<typeof http.get>[1] = (info) => {
4754
}
4855
return HttpResponse.text("1234567890:"); // The returned session ID has a colon and then some other information at the end. This only has the colon, to check that the colon is removed.
4956
};
57+
const eventServer = ws.link("ws://ftrack.test/*");
58+
const secureEventServer = ws.link("wss://ftrack.test/*");
5059

51-
export const handlers: HttpHandler[] = [
60+
export const handlers: (HttpHandler | WebSocketHandler)[] = [
5261
http.post<PathParams, any[]>("http://ftrack.test/api", async (info) => {
5362
if (!authenticate(info)) {
5463
return HttpResponse.json(InvalidCredentialsError);
@@ -145,6 +154,14 @@ export const handlers: HttpHandler[] = [
145154
http.get("http://ftrack.test:8080/*", () => {
146155
return new Response(null, { status: 200 });
147156
}),
157+
eventServer.addEventListener("connection", ({ client }) => {
158+
// Just catch the connection event and close it immediately
159+
client.close();
160+
}),
161+
secureEventServer.addEventListener("connection", ({ client }) => {
162+
// Just catch the connection event and close it immediately
163+
client.close();
164+
}),
148165
];
149166

150167
export const server = setupServer(...handlers);

vitest.setup.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ class MockXmlHttpRequest extends EventTarget {
2727
}
2828

2929
beforeAll(() => {
30-
server.listen({ onUnhandledRequest: "bypass" });
31-
global.fetch = fetch;
30+
server.listen({ onUnhandledRequest: "error" });
3231
global.XMLHttpRequest = MockXmlHttpRequest;
3332
});
3433

yarn.lock

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -394,12 +394,12 @@ __metadata:
394394
dependencies:
395395
"@types/node": "npm:^22.13.9"
396396
"@types/uuid": "npm:^10.0.0"
397+
"@types/ws": "npm:^8.18.0"
397398
"@vitest/eslint-plugin": "npm:^1.1.36"
398399
dayjs: "npm:^1.11.13"
399400
eslint: "npm:^9.21.0"
400401
globals: "npm:^16.0.0"
401402
husky: "npm:^9.1.7"
402-
isomorphic-ws: "npm:^5.0.0"
403403
jsdom: "npm:^26.0.0"
404404
lint-staged: "npm:^15.4.3"
405405
loglevel: "npm:^1.9.2"
@@ -900,12 +900,12 @@ __metadata:
900900
languageName: node
901901
linkType: hard
902902

903-
"@types/node@npm:^22.13.9":
904-
version: 22.13.9
905-
resolution: "@types/node@npm:22.13.9"
903+
"@types/node@npm:*, @types/node@npm:^22.13.9":
904+
version: 22.13.10
905+
resolution: "@types/node@npm:22.13.10"
906906
dependencies:
907907
undici-types: "npm:~6.20.0"
908-
checksum: 10/23560df3ee99c907179c688754486b969a72144f2e2bdefe974d320dddc5ca8f93365842966ecbd5c5bba34e919fc1a5a6627712beb8e7f71d71347dcf414a35
908+
checksum: 10/57dc6a5e0110ca9edea8d7047082e649fa7fa813f79e4a901653b9174141c622f4336435648baced5b38d9f39843f404fa2d8d7a10981610da26066bc8caab48
909909
languageName: node
910910
linkType: hard
911911

@@ -930,6 +930,15 @@ __metadata:
930930
languageName: node
931931
linkType: hard
932932

933+
"@types/ws@npm:^8.18.0":
934+
version: 8.18.0
935+
resolution: "@types/ws@npm:8.18.0"
936+
dependencies:
937+
"@types/node": "npm:*"
938+
checksum: 10/2a3bbf27690532627bfde8a215c0cf3a56680f339f972785b30d0b4665528275b9270c0a0839244610b0a3f2da4218c6dd741ceba1d173fde5c5091f2034b823
939+
languageName: node
940+
linkType: hard
941+
933942
"@typescript-eslint/eslint-plugin@npm:8.26.0":
934943
version: 8.26.0
935944
resolution: "@typescript-eslint/eslint-plugin@npm:8.26.0"
@@ -2839,15 +2848,6 @@ __metadata:
28392848
languageName: node
28402849
linkType: hard
28412850

2842-
"isomorphic-ws@npm:^5.0.0":
2843-
version: 5.0.0
2844-
resolution: "isomorphic-ws@npm:5.0.0"
2845-
peerDependencies:
2846-
ws: "*"
2847-
checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398
2848-
languageName: node
2849-
linkType: hard
2850-
28512851
"jackspeak@npm:^2.0.3":
28522852
version: 2.3.3
28532853
resolution: "jackspeak@npm:2.3.3"

0 commit comments

Comments
 (0)