Compare commits

...

1 Commits

Author SHA1 Message Date
Guido D'Orsi
3a7ab9b3a1 feat: remove the ws dependency and use native WebSocket 2025-01-30 11:43:10 +01:00
6 changed files with 49 additions and 32 deletions

View File

@@ -0,0 +1,15 @@
---
"jazz-nodejs": minor
---
Remove ws dependency to use native WebSocket.
NodeJS versions prior to v22 will need to provide a WebSocket constructor from ws:
```ts
import { WebSocket } from "ws"
const { worker } = await startWorker({ WebSocket, synServer });
```
This makes it easier to run workers on every JS runtime.

View File

@@ -9,11 +9,9 @@
"dependencies": {
"cojson": "workspace:0.9.19",
"cojson-transport-ws": "workspace:0.9.22",
"jazz-tools": "workspace:0.9.21",
"ws": "^8.14.2"
"jazz-tools": "workspace:0.9.21"
},
"devDependencies": {
"@types/ws": "8.5.10",
"jazz-run": "workspace:*",
"typescript": "~5.6.2"
},

View File

@@ -14,6 +14,7 @@ type WorkerOptions<Acc extends Account> = {
accountID?: string;
accountSecret?: string;
syncServer?: string;
WebSocket?: typeof WebSocket;
AccountSchema?: AccountClass<Acc>;
};
@@ -29,9 +30,13 @@ export async function startWorker<Acc extends Account>(
} = options;
let node: LocalNode | undefined = undefined;
const wsPeer = webSocketWithReconnection(syncServer, (peer) => {
node?.syncManager.addPeer(peer);
});
const wsPeer = webSocketWithReconnection(
syncServer,
(peer) => {
node?.syncManager.addPeer(peer);
},
options.WebSocket,
);
if (!accountID) {
throw new Error("No accountID provided");

View File

@@ -1,6 +1,5 @@
import { createWebSocketPeer } from "cojson-transport-ws";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { WebSocket } from "ws";
import { webSocketWithReconnection } from "../webSocketWithReconnection";
// Mock dependencies
@@ -13,14 +12,12 @@ vi.mock("cojson-transport-ws", () => ({
})),
}));
vi.mock("ws", () => ({
WebSocket: vi.fn().mockImplementation(() => ({
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
close: vi.fn(),
readyState: 1,
})),
}));
const WebSocketMock = vi.fn().mockImplementation(() => ({
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
close: vi.fn(),
readyState: 1,
})) as unknown as typeof WebSocket;
describe("webSocketWithReconnection", () => {
beforeEach(() => {
@@ -37,9 +34,10 @@ describe("webSocketWithReconnection", () => {
const { peer } = webSocketWithReconnection(
"ws://localhost:8080",
addPeerMock,
WebSocketMock,
);
expect(WebSocket).toHaveBeenCalledWith("ws://localhost:8080");
expect(WebSocketMock).toHaveBeenCalledWith("ws://localhost:8080");
expect(createWebSocketPeer).toHaveBeenCalledWith(
expect.objectContaining({
id: "upstream",
@@ -51,7 +49,11 @@ describe("webSocketWithReconnection", () => {
test("should attempt reconnection when websocket closes", async () => {
const addPeerMock = vi.fn();
webSocketWithReconnection("ws://localhost:8080", addPeerMock);
webSocketWithReconnection(
"ws://localhost:8080",
addPeerMock,
WebSocketMock,
);
// Get the onClose handler from the first createWebSocketPeer call
const initialPeer = vi.mocked(createWebSocketPeer).mock.results[0]!.value;
@@ -62,7 +64,7 @@ describe("webSocketWithReconnection", () => {
// Fast-forward timer to trigger reconnection
await vi.advanceTimersByTimeAsync(1000);
expect(WebSocket).toHaveBeenCalledTimes(2);
expect(WebSocketMock).toHaveBeenCalledTimes(2);
expect(createWebSocketPeer).toHaveBeenCalledTimes(2);
expect(addPeerMock).toHaveBeenCalledWith(
expect.objectContaining({
@@ -76,6 +78,7 @@ describe("webSocketWithReconnection", () => {
const { done } = webSocketWithReconnection(
"ws://localhost:8080",
addPeerMock,
WebSocketMock,
);
// Get the onClose handler
@@ -90,7 +93,7 @@ describe("webSocketWithReconnection", () => {
vi.advanceTimersByTime(1000);
// Should not attempt reconnection
expect(WebSocket).toHaveBeenCalledTimes(1);
expect(WebSocketMock).toHaveBeenCalledTimes(1);
expect(createWebSocketPeer).toHaveBeenCalledTimes(1);
});
@@ -99,6 +102,7 @@ describe("webSocketWithReconnection", () => {
const { done } = webSocketWithReconnection(
"ws://localhost:8080",
addPeerMock,
WebSocketMock,
);
// Get the onClose handler
@@ -108,7 +112,7 @@ describe("webSocketWithReconnection", () => {
initialPeer.onClose();
await vi.advanceTimersByTimeAsync(1000);
expect(WebSocket).toHaveBeenCalledTimes(2);
expect(WebSocketMock).toHaveBeenCalledTimes(2);
// Call done
done();
@@ -118,6 +122,6 @@ describe("webSocketWithReconnection", () => {
await vi.advanceTimersByTimeAsync(1000);
// Should not create another connection
expect(WebSocket).toHaveBeenCalledTimes(2);
expect(WebSocketMock).toHaveBeenCalledTimes(2);
});
});

View File

@@ -1,14 +1,17 @@
import { Peer } from "cojson";
import { createWebSocketPeer } from "cojson-transport-ws";
import { WebSocket } from "ws";
export function webSocketWithReconnection(
peer: string,
addPeer: (peer: Peer) => void,
ws?: typeof WebSocket,
) {
let done = false;
const WebSocketConstructor = ws ?? WebSocket;
const wsPeer = createWebSocketPeer({
websocket: new WebSocket(peer),
websocket: new WebSocketConstructor(peer),
id: "upstream",
role: "server",
onClose: handleClose,
@@ -20,11 +23,9 @@ export function webSocketWithReconnection(
clearTimeout(timer);
timer = setTimeout(() => {
console.log(new Date(), "Reconnecting to upstream " + peer);
const wsPeer: Peer = createWebSocketPeer({
id: "upstream",
websocket: new WebSocket(peer) as any,
websocket: new WebSocketConstructor(peer) as any,
role: "server",
onClose: handleClose,
});

6
pnpm-lock.yaml generated
View File

@@ -1684,13 +1684,7 @@ importers:
jazz-tools:
specifier: workspace:0.9.21
version: link:../jazz-tools
ws:
specifier: ^8.14.2
version: 8.18.0
devDependencies:
'@types/ws':
specifier: 8.5.10
version: 8.5.10
jazz-run:
specifier: workspace:*
version: link:../jazz-run