cojson-transport-ws 0.7.35-guest-auth.5 → 0.7.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
 
2
- > cojson-transport-ws@0.7.35-unique.2 build /Users/anselm/jazz/jazz/packages/cojson-transport-ws
2
+ > cojson-transport-ws@0.7.34 build /Users/anselm/jazz/jazz/packages/cojson-transport-ws
3
3
  > npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist
4
4
 
5
5
 
6
- > cojson-transport-ws@0.7.35-unique.2 lint
6
+ > cojson-transport-ws@0.7.34 lint
7
7
  > eslint . --ext ts,tsx
8
8
 
package/CHANGELOG.md CHANGED
@@ -1,18 +1,13 @@
1
1
  # cojson-transport-nodejs-ws
2
2
 
3
- ## 0.7.35-guest-auth.5
3
+ ## 0.7.35
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies
8
- - cojson@0.7.35-guest-auth.5
9
-
10
- ## 0.7.35-unique.2
11
-
12
- ### Patch Changes
13
-
14
- - Updated dependencies
15
- - cojson@0.7.35-unique.2
7
+ - f350e90: Added a priority system for the sync messages
8
+ - Updated dependencies [35bbcd9]
9
+ - Updated dependencies [f350e90]
10
+ - cojson@0.7.35
16
11
 
17
12
  ## 0.7.34
18
13
 
@@ -0,0 +1,108 @@
1
+ import { describe, test, expect, vi } from "vitest";
2
+ import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL, createWebSocketPeer } from "./index.js";
3
+ const g = globalThis;
4
+ function setup() {
5
+ const listeners = new Map();
6
+ const mockWebSocket = {
7
+ readyState: 1,
8
+ addEventListener: vi.fn().mockImplementation((type, listener) => {
9
+ listeners.set(type, listener);
10
+ }),
11
+ close: vi.fn(),
12
+ send: vi.fn(),
13
+ };
14
+ const peer = createWebSocketPeer({
15
+ id: "test-peer",
16
+ websocket: mockWebSocket,
17
+ role: "client",
18
+ });
19
+ return { mockWebSocket, peer, listeners };
20
+ }
21
+ describe("createWebSocketPeer", () => {
22
+ test("should create a peer with correct properties", () => {
23
+ const { peer } = setup();
24
+ expect(peer).toHaveProperty("id", "test-peer");
25
+ expect(peer).toHaveProperty("incoming");
26
+ expect(peer).toHaveProperty("outgoing");
27
+ expect(peer).toHaveProperty("role", "client");
28
+ expect(peer).toHaveProperty("crashOnClose", false);
29
+ });
30
+ test("should handle ping messages", async () => {
31
+ const { listeners } = setup();
32
+ const pingMessage = { type: "ping", time: Date.now(), dc: "test-dc" };
33
+ const messageEvent = new MessageEvent("message", { data: JSON.stringify(pingMessage) });
34
+ const messageHandler = listeners.get("message");
35
+ messageHandler?.(messageEvent);
36
+ // Check if the ping was recorded in the global jazzPings array
37
+ expect(g.jazzPings?.length).toBeGreaterThan(0);
38
+ expect(g.jazzPings?.at(-1)).toMatchObject({
39
+ sent: pingMessage.time,
40
+ dc: pingMessage.dc,
41
+ });
42
+ });
43
+ test("should handle disconnection", async () => {
44
+ expect.assertions(1);
45
+ const { listeners, peer } = setup();
46
+ const incoming = peer.incoming;
47
+ const pushSpy = vi.spyOn(incoming, "push");
48
+ const closeHandler = listeners.get("close");
49
+ closeHandler?.(new MessageEvent("close"));
50
+ expect(pushSpy).toHaveBeenCalledWith("Disconnected");
51
+ });
52
+ test("should handle ping timeout", async () => {
53
+ vi.useFakeTimers();
54
+ const { listeners, peer } = setup();
55
+ const incoming = peer.incoming;
56
+ const pushSpy = vi.spyOn(incoming, "push");
57
+ const messageHandler = listeners.get("message");
58
+ messageHandler?.(new MessageEvent("message", { data: "{}" }));
59
+ await vi.advanceTimersByTimeAsync(10000);
60
+ expect(pushSpy).toHaveBeenCalledWith("PingTimeout");
61
+ vi.useRealTimers();
62
+ });
63
+ test("should send outgoing messages", async () => {
64
+ const { peer, mockWebSocket } = setup();
65
+ const testMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
66
+ const promise = peer.outgoing.push(testMessage);
67
+ await new Promise(queueMicrotask);
68
+ expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(testMessage));
69
+ await expect(promise).resolves.toBeUndefined();
70
+ });
71
+ test("should stop sending messages when the websocket is closed", async () => {
72
+ const { peer, mockWebSocket } = setup();
73
+ mockWebSocket.send.mockImplementation(() => {
74
+ mockWebSocket.readyState = 0;
75
+ });
76
+ const message1 = { action: "known", id: "co_ztest", header: false, sessions: {} };
77
+ const message2 = { action: "content", id: "co_zlow", new: {}, priority: 1 };
78
+ void peer.outgoing.push(message1);
79
+ void peer.outgoing.push(message2);
80
+ await new Promise(queueMicrotask);
81
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
82
+ expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
83
+ });
84
+ test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
85
+ vi.useFakeTimers();
86
+ const { peer, mockWebSocket } = setup();
87
+ mockWebSocket.send.mockImplementation(() => {
88
+ mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
89
+ });
90
+ const message1 = { action: "known", id: "co_ztest", header: false, sessions: {} };
91
+ const message2 = { action: "content", id: "co_zlow", new: {}, priority: 1 };
92
+ void peer.outgoing.push(message1);
93
+ void peer.outgoing.push(message2);
94
+ await new Promise(queueMicrotask);
95
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
96
+ expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
97
+ mockWebSocket.bufferedAmount = 0;
98
+ await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
99
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
100
+ vi.useRealTimers();
101
+ });
102
+ test("should close the websocket connection", () => {
103
+ const { mockWebSocket, peer } = setup();
104
+ peer.outgoing.close();
105
+ expect(mockWebSocket.close).toHaveBeenCalled();
106
+ });
107
+ });
108
+ //# sourceMappingURL=createWebSocketPeer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWebSocketPeer.test.js","sourceRoot":"","sources":["../src/createWebSocketPeer.test.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { cojsonInternals, } from "cojson";
2
2
  const g = globalThis;
3
+ export const BUFFER_LIMIT = 100000;
4
+ export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
3
5
  export function createWebSocketPeer({ id, websocket, role, expectPings = true, }) {
4
6
  const incoming = new cojsonInternals.Channel();
5
7
  websocket.addEventListener("close", function handleClose() {
@@ -41,23 +43,23 @@ export function createWebSocketPeer({ id, websocket, role, expectPings = true, }
41
43
  websocket.addEventListener("open", resolve, { once: true });
42
44
  }
43
45
  });
46
+ async function pushMessage(msg) {
47
+ if (websocket.readyState !== 1) {
48
+ await websocketOpen;
49
+ }
50
+ while (websocket.bufferedAmount > BUFFER_LIMIT && websocket.readyState === 1) {
51
+ await new Promise((resolve) => setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL));
52
+ }
53
+ if (websocket.readyState !== 1) {
54
+ return;
55
+ }
56
+ websocket.send(JSON.stringify(msg));
57
+ }
44
58
  return {
45
59
  id,
46
60
  incoming,
47
61
  outgoing: {
48
- async push(msg) {
49
- await websocketOpen;
50
- if (websocket.readyState === 1) {
51
- while (websocket.bufferedAmount > 1000000) {
52
- await new Promise((resolve) => setTimeout(resolve, 100));
53
- if (websocket.readyState !== 1) {
54
- console.log("WebSocket closed while buffering", id, websocket.bufferedAmount);
55
- return;
56
- }
57
- }
58
- websocket.send(JSON.stringify(msg));
59
- }
60
- },
62
+ push: pushMessage,
61
63
  close() {
62
64
  console.log("Trying to close", id, websocket.readyState);
63
65
  if (websocket.readyState === 0) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "cojson-transport-ws",
3
3
  "type": "module",
4
- "version": "0.7.35-guest-auth.5",
4
+ "version": "0.7.35",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.ts",
7
7
  "license": "MIT",
8
8
  "dependencies": {
9
- "typescript": "^5.1.6",
10
- "cojson": "0.7.35-guest-auth.5"
9
+ "typescript": "^5.3.3",
10
+ "cojson": "0.7.35"
11
11
  },
12
12
  "devDependencies": {
13
13
  "@types/ws": "^8.5.5"
@@ -0,0 +1,166 @@
1
+ import { describe, test, expect, vi, Mocked } from "vitest";
2
+ import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL, createWebSocketPeer } from "./index.js";
3
+ import { AnyWebSocket, PingMsg } from "./types.js";
4
+ import { SyncMessage } from "cojson";
5
+ import { Channel } from "cojson/src/streamUtils";
6
+
7
+ const g: typeof globalThis & {
8
+ jazzPings?: {
9
+ received: number;
10
+ sent: number;
11
+ dc: string;
12
+ }[];
13
+ } = globalThis;
14
+
15
+ function setup() {
16
+ const listeners = new Map<string, (event: MessageEvent) => void>();
17
+
18
+ const mockWebSocket = {
19
+ readyState: 1,
20
+ addEventListener: vi.fn().mockImplementation((type, listener) => {
21
+ listeners.set(type, listener);
22
+ }),
23
+ close: vi.fn(),
24
+ send: vi.fn(),
25
+ } as unknown as Mocked<AnyWebSocket>;
26
+
27
+ const peer = createWebSocketPeer({
28
+ id: "test-peer",
29
+ websocket: mockWebSocket,
30
+ role: "client",
31
+ });
32
+
33
+ return { mockWebSocket, peer, listeners };
34
+ }
35
+
36
+ describe("createWebSocketPeer", () => {
37
+ test("should create a peer with correct properties", () => {
38
+ const { peer } = setup();
39
+
40
+ expect(peer).toHaveProperty("id", "test-peer");
41
+ expect(peer).toHaveProperty("incoming");
42
+ expect(peer).toHaveProperty("outgoing");
43
+ expect(peer).toHaveProperty("role", "client");
44
+ expect(peer).toHaveProperty("crashOnClose", false);
45
+ });
46
+
47
+ test("should handle ping messages", async () => {
48
+ const { listeners } = setup();
49
+
50
+ const pingMessage: PingMsg = { type: "ping", time: Date.now(), dc: "test-dc" };
51
+ const messageEvent = new MessageEvent("message", { data: JSON.stringify(pingMessage) });
52
+
53
+ const messageHandler = listeners.get("message");
54
+
55
+ messageHandler?.(messageEvent);
56
+
57
+ // Check if the ping was recorded in the global jazzPings array
58
+ expect(g.jazzPings?.length).toBeGreaterThan(0);
59
+ expect(g.jazzPings?.at(-1)).toMatchObject({
60
+ sent: pingMessage.time,
61
+ dc: pingMessage.dc,
62
+ });
63
+ });
64
+
65
+ test("should handle disconnection", async () => {
66
+ expect.assertions(1);
67
+
68
+ const { listeners, peer } = setup();
69
+
70
+ const incoming = peer.incoming as Channel<SyncMessage | "Disconnected" | "PingTimeout">;
71
+ const pushSpy = vi.spyOn(incoming, "push");
72
+
73
+ const closeHandler = listeners.get("close");
74
+
75
+ closeHandler?.(new MessageEvent("close"));
76
+
77
+ expect(pushSpy).toHaveBeenCalledWith("Disconnected");
78
+ });
79
+
80
+ test("should handle ping timeout", async () => {
81
+ vi.useFakeTimers();
82
+ const { listeners, peer } = setup();
83
+
84
+ const incoming = peer.incoming as Channel<SyncMessage | "Disconnected" | "PingTimeout">;
85
+ const pushSpy = vi.spyOn(incoming, "push");
86
+
87
+ const messageHandler = listeners.get("message");
88
+
89
+ messageHandler?.(new MessageEvent("message", { data: "{}" }));
90
+
91
+ await vi.advanceTimersByTimeAsync(10_000);
92
+
93
+ expect(pushSpy).toHaveBeenCalledWith("PingTimeout");
94
+
95
+ vi.useRealTimers();
96
+ });
97
+
98
+ test("should send outgoing messages", async () => {
99
+ const { peer, mockWebSocket } = setup();
100
+
101
+ const testMessage: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
102
+ const promise = peer.outgoing.push(testMessage);
103
+
104
+ await new Promise<void>(queueMicrotask);
105
+
106
+ expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(testMessage));
107
+ await expect(promise).resolves.toBeUndefined();
108
+ });
109
+
110
+
111
+
112
+ test("should stop sending messages when the websocket is closed", async () => {
113
+ const { peer, mockWebSocket } = setup();
114
+
115
+ mockWebSocket.send.mockImplementation(() => {
116
+ mockWebSocket.readyState = 0;
117
+ });
118
+
119
+ const message1: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
120
+ const message2: SyncMessage = { action: "content", id: "co_zlow", new: {}, priority: 1 };
121
+
122
+ void peer.outgoing.push(message1);
123
+ void peer.outgoing.push(message2);
124
+
125
+ await new Promise<void>(queueMicrotask);
126
+
127
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
128
+ expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
129
+ });
130
+
131
+ test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
132
+ vi.useFakeTimers();
133
+ const { peer, mockWebSocket } = setup();
134
+
135
+ mockWebSocket.send.mockImplementation(() => {
136
+ mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
137
+ });
138
+
139
+ const message1: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
140
+ const message2: SyncMessage = { action: "content", id: "co_zlow", new: {}, priority: 1 };
141
+
142
+ void peer.outgoing.push(message1);
143
+ void peer.outgoing.push(message2);
144
+
145
+ await new Promise<void>(queueMicrotask);
146
+
147
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
148
+ expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
149
+
150
+ mockWebSocket.bufferedAmount = 0;
151
+
152
+ await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
153
+
154
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
155
+
156
+ vi.useRealTimers();
157
+ });
158
+
159
+ test("should close the websocket connection", () => {
160
+ const { mockWebSocket, peer } = setup();
161
+
162
+ peer.outgoing.close();
163
+
164
+ expect(mockWebSocket.close).toHaveBeenCalled();
165
+ });
166
+ });
package/src/index.ts CHANGED
@@ -5,34 +5,9 @@ import {
5
5
  SyncMessage,
6
6
  cojsonInternals,
7
7
  } from "cojson";
8
+ import { AnyWebSocket, PingMsg } from "./types.js";
8
9
 
9
- interface WebsocketEvents {
10
- close: { code: number; reason: string };
11
- message: { data: unknown };
12
- open: void;
13
- }
14
- interface PingMsg {
15
- time: number;
16
- dc: string;
17
- }
18
-
19
- interface AnyWebSocket {
20
- addEventListener<K extends keyof WebsocketEvents>(
21
- type: K,
22
- listener: (event: WebsocketEvents[K]) => void,
23
- options?: { once: boolean },
24
- ): void;
25
- removeEventListener<K extends keyof WebsocketEvents>(
26
- type: K,
27
- listener: (event: WebsocketEvents[K]) => void,
28
- ): void;
29
- close(): void;
30
- send(data: string): void;
31
- readyState: number;
32
- bufferedAmount: number;
33
- }
34
-
35
- const g: typeof globalThis & {
10
+ const g: typeof globalThis & {
36
11
  jazzPings?: {
37
12
  received: number;
38
13
  sent: number;
@@ -40,6 +15,9 @@ const g: typeof globalThis & {
40
15
  }[];
41
16
  } = globalThis;
42
17
 
18
+ export const BUFFER_LIMIT = 100_000;
19
+ export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
20
+
43
21
  export function createWebSocketPeer({
44
22
  id,
45
23
  websocket,
@@ -68,6 +46,7 @@ export function createWebSocketPeer({
68
46
  websocket.addEventListener("message", function handleIncomingMsg(event) {
69
47
  const msg = JSON.parse(event.data as string);
70
48
  pingTimeout && clearTimeout(pingTimeout);
49
+
71
50
  if (msg?.type === "ping") {
72
51
  const ping = msg as PingMsg;
73
52
  g.jazzPings ||= [];
@@ -102,31 +81,37 @@ export function createWebSocketPeer({
102
81
  }
103
82
  });
104
83
 
84
+ async function pushMessage(msg: SyncMessage) {
85
+ if (websocket.readyState !== 1) {
86
+ await websocketOpen;
87
+ }
88
+
89
+ while (websocket.bufferedAmount > BUFFER_LIMIT && websocket.readyState === 1) {
90
+ await new Promise<void>((resolve) => setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL));
91
+ }
92
+
93
+ if (websocket.readyState !== 1) {
94
+ return;
95
+ }
96
+
97
+ websocket.send(JSON.stringify(msg));
98
+ }
99
+
105
100
  return {
106
101
  id,
107
102
  incoming,
108
103
  outgoing: {
109
- async push(msg) {
110
- await websocketOpen;
111
- if (websocket.readyState === 1) {
112
- while (websocket.bufferedAmount > 1_000_000) {
113
- await new Promise((resolve) =>
114
- setTimeout(resolve, 100),
115
- );
116
- if (websocket.readyState !== 1) {
117
- console.log("WebSocket closed while buffering", id, websocket.bufferedAmount);
118
- return;
119
- }
120
- }
121
- websocket.send(JSON.stringify(msg));
122
- }
123
- },
104
+ push: pushMessage,
124
105
  close() {
125
- console.log("Trying to close", id, websocket.readyState)
106
+ console.log("Trying to close", id, websocket.readyState);
126
107
  if (websocket.readyState === 0) {
127
- websocket.addEventListener("open", function handleClose() {
128
- websocket.close();
129
- }, { once: true });
108
+ websocket.addEventListener(
109
+ "open",
110
+ function handleClose() {
111
+ websocket.close();
112
+ },
113
+ { once: true },
114
+ );
130
115
  } else if (websocket.readyState == 1) {
131
116
  websocket.close();
132
117
  }
package/src/types.ts ADDED
@@ -0,0 +1,28 @@
1
+
2
+ export interface WebsocketEvents {
3
+ close: { code: number; reason: string; };
4
+ message: { data: unknown; };
5
+ open: void;
6
+ }
7
+
8
+ export interface PingMsg {
9
+ type: "ping";
10
+ time: number;
11
+ dc: string;
12
+ }
13
+
14
+ export interface AnyWebSocket {
15
+ addEventListener<K extends keyof WebsocketEvents>(
16
+ type: K,
17
+ listener: (event: WebsocketEvents[K]) => void,
18
+ options?: { once: boolean; }
19
+ ): void;
20
+ removeEventListener<K extends keyof WebsocketEvents>(
21
+ type: K,
22
+ listener: (event: WebsocketEvents[K]) => void
23
+ ): void;
24
+ close(): void;
25
+ send(data: string): void;
26
+ readyState: number;
27
+ bufferedAmount: number;
28
+ }