Skip to content

chore: Make ws dependency optional for node.js environments #288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"devDependencies": {
"@types/node": "^22.13.9",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.0",
"@vitest/eslint-plugin": "^1.1.36",
"dayjs": "^1.11.13",
"eslint": "^9.21.0",
Expand Down Expand Up @@ -60,7 +61,6 @@
},
"homepage": "http://ftrack.com",
"dependencies": {
"isomorphic-ws": "^5.0.0",
"loglevel": "^1.9.2",
"moment": "^2.30.1",
"uuid": "^11.1.0"
Expand Down
21 changes: 13 additions & 8 deletions source/simple_socketio.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// :copyright: Copyright (c) 2023 ftrack
import WebSocket from "isomorphic-ws";
import { Event } from "./event.js";
export const PACKET_TYPES = {
disconnect: "0",
Expand Down Expand Up @@ -42,7 +41,7 @@ interface Payload {
*/
export default class SimpleSocketIOClient {
private static instances: Map<string, SimpleSocketIOClient> = new Map();
private webSocket: WebSocket;
private webSocket: WebSocket | undefined;
private handlers: EventHandlers = {};
private reconnectTimeout: ReturnType<typeof setTimeout> | undefined;
private heartbeatTimeout: ReturnType<typeof setInterval> | undefined;
Expand Down Expand Up @@ -172,7 +171,13 @@ export default class SimpleSocketIOClient {
this.reconnecting = false;
}
const urlWithQueryAndSession = `${this.webSocketUrl}/socket.io/1/websocket/${sessionId}?${this.query}`;
this.webSocket = new WebSocket(urlWithQueryAndSession);
const WebSocketImpl =
typeof WebSocket === "undefined"
? // Fallback on ws package if WebSocket is not available
((await import("ws")).default as typeof WebSocket)
: WebSocket;

this.webSocket = new WebSocketImpl(urlWithQueryAndSession);
// Set transport.websocket property as a public alias of the websocket
this.socket.transport.websocket = this.webSocket;
this.addInitialEventListeners(this.webSocket);
Expand All @@ -195,7 +200,7 @@ export default class SimpleSocketIOClient {
* Handles WebSocket errors
* @private
*/
private handleError(event: Event): void {
private handleError(event: WebSocketEventMap["error"]): void {
this.handleClose();
console.error("WebSocket error:", event);
}
Expand Down Expand Up @@ -322,7 +327,7 @@ export default class SimpleSocketIOClient {
const packet = `${PACKET_TYPES.event}${dataString}`;

if (this.isConnected()) {
this.webSocket.send(packet);
this.webSocket?.send(packet);
} else {
this.packetQueue.push(packet);
this.reconnect();
Expand Down Expand Up @@ -436,9 +441,9 @@ export default class SimpleSocketIOClient {
this.packetQueue = [];

if (this.webSocket) {
this.webSocket.onclose = undefined;
this.webSocket.onerror = undefined;
this.webSocket?.close();
this.webSocket.onclose = null;
this.webSocket.onerror = null;
this.webSocket.close();
this.webSocket = undefined;
}
if (this.reconnectTimeout) {
Expand Down
21 changes: 19 additions & 2 deletions test/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
// :copyright: Copyright (c) 2022 ftrack
import { HttpResponse, type PathParams, http, HttpHandler } from "msw";
import {
HttpResponse,
type PathParams,
http,
HttpHandler,
ws,
WebSocketHandler,
} from "msw";
import fs from "fs/promises";
import querySchemas from "./fixtures/query_schemas.json" with { type: "json" };
import queryServerInformation from "./fixtures/query_server_information.json" with { type: "json" };
Expand Down Expand Up @@ -47,8 +54,10 @@ const handleSocketIORequest: Parameters<typeof http.get>[1] = (info) => {
}
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.
};
const eventServer = ws.link("ws://ftrack.test/*");
const secureEventServer = ws.link("wss://ftrack.test/*");

export const handlers: HttpHandler[] = [
export const handlers: (HttpHandler | WebSocketHandler)[] = [
http.post<PathParams, any[]>("http://ftrack.test/api", async (info) => {
if (!authenticate(info)) {
return HttpResponse.json(InvalidCredentialsError);
Expand Down Expand Up @@ -145,6 +154,14 @@ export const handlers: HttpHandler[] = [
http.get("http://ftrack.test:8080/*", () => {
return new Response(null, { status: 200 });
}),
eventServer.addEventListener("connection", ({ client }) => {
// Just catch the connection event and close it immediately
client.close();
}),
secureEventServer.addEventListener("connection", ({ client }) => {
// Just catch the connection event and close it immediately
client.close();
}),
];

export const server = setupServer(...handlers);
3 changes: 1 addition & 2 deletions vitest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ class MockXmlHttpRequest extends EventTarget {
}

beforeAll(() => {
server.listen({ onUnhandledRequest: "bypass" });
global.fetch = fetch;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftovers from when we removed cross-fetch

server.listen({ onUnhandledRequest: "error" });
Copy link
Contributor Author

@jimmycallin jimmycallin Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now when websocket updates are handled natively, we can do "error" again 🥳

global.XMLHttpRequest = MockXmlHttpRequest;
});

Expand Down
28 changes: 14 additions & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,12 @@ __metadata:
dependencies:
"@types/node": "npm:^22.13.9"
"@types/uuid": "npm:^10.0.0"
"@types/ws": "npm:^8.18.0"
"@vitest/eslint-plugin": "npm:^1.1.36"
dayjs: "npm:^1.11.13"
eslint: "npm:^9.21.0"
globals: "npm:^16.0.0"
husky: "npm:^9.1.7"
isomorphic-ws: "npm:^5.0.0"
jsdom: "npm:^26.0.0"
lint-staged: "npm:^15.4.3"
loglevel: "npm:^1.9.2"
Expand Down Expand Up @@ -900,12 +900,12 @@ __metadata:
languageName: node
linkType: hard

"@types/node@npm:^22.13.9":
version: 22.13.9
resolution: "@types/node@npm:22.13.9"
"@types/node@npm:*, @types/node@npm:^22.13.9":
version: 22.13.10
resolution: "@types/node@npm:22.13.10"
dependencies:
undici-types: "npm:~6.20.0"
checksum: 10/23560df3ee99c907179c688754486b969a72144f2e2bdefe974d320dddc5ca8f93365842966ecbd5c5bba34e919fc1a5a6627712beb8e7f71d71347dcf414a35
checksum: 10/57dc6a5e0110ca9edea8d7047082e649fa7fa813f79e4a901653b9174141c622f4336435648baced5b38d9f39843f404fa2d8d7a10981610da26066bc8caab48
languageName: node
linkType: hard

Expand All @@ -930,6 +930,15 @@ __metadata:
languageName: node
linkType: hard

"@types/ws@npm:^8.18.0":
version: 8.18.0
resolution: "@types/ws@npm:8.18.0"
dependencies:
"@types/node": "npm:*"
checksum: 10/2a3bbf27690532627bfde8a215c0cf3a56680f339f972785b30d0b4665528275b9270c0a0839244610b0a3f2da4218c6dd741ceba1d173fde5c5091f2034b823
languageName: node
linkType: hard

"@typescript-eslint/eslint-plugin@npm:8.26.0":
version: 8.26.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.26.0"
Expand Down Expand Up @@ -2839,15 +2848,6 @@ __metadata:
languageName: node
linkType: hard

"isomorphic-ws@npm:^5.0.0":
version: 5.0.0
resolution: "isomorphic-ws@npm:5.0.0"
peerDependencies:
ws: "*"
checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398
languageName: node
linkType: hard

"jackspeak@npm:^2.0.3":
version: 2.3.3
resolution: "jackspeak@npm:2.3.3"
Expand Down