cojson-transport-ws 0.15.8 → 0.15.10

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.
Files changed (39) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/dist/BatchedOutgoingMessages.d.ts +17 -6
  4. package/dist/BatchedOutgoingMessages.d.ts.map +1 -1
  5. package/dist/BatchedOutgoingMessages.js +77 -14
  6. package/dist/BatchedOutgoingMessages.js.map +1 -1
  7. package/dist/createWebSocketPeer.d.ts +0 -2
  8. package/dist/createWebSocketPeer.d.ts.map +1 -1
  9. package/dist/createWebSocketPeer.js +27 -84
  10. package/dist/createWebSocketPeer.js.map +1 -1
  11. package/dist/tests/createWebSocketPeer.test.js +64 -22
  12. package/dist/tests/createWebSocketPeer.test.js.map +1 -1
  13. package/dist/tests/syncServer.d.ts +1 -0
  14. package/dist/tests/syncServer.d.ts.map +1 -1
  15. package/dist/tests/syncServer.js +1 -0
  16. package/dist/tests/syncServer.js.map +1 -1
  17. package/dist/tests/webSocket.integration.test.d.ts +2 -0
  18. package/dist/tests/webSocket.integration.test.d.ts.map +1 -0
  19. package/dist/tests/{integration.test.js → webSocket.integration.test.js} +28 -2
  20. package/dist/tests/webSocket.integration.test.js.map +1 -0
  21. package/dist/utils.d.ts +8 -0
  22. package/dist/utils.d.ts.map +1 -0
  23. package/dist/utils.js +24 -0
  24. package/dist/utils.js.map +1 -0
  25. package/package.json +2 -2
  26. package/src/BatchedOutgoingMessages.ts +124 -16
  27. package/src/createWebSocketPeer.ts +33 -118
  28. package/src/tests/createWebSocketPeer.test.ts +87 -37
  29. package/src/tests/syncServer.ts +1 -0
  30. package/src/tests/{integration.test.ts → webSocket.integration.test.ts} +37 -2
  31. package/src/utils.ts +30 -0
  32. package/dist/tests/BatchedOutgoingMessages.test.d.ts +0 -2
  33. package/dist/tests/BatchedOutgoingMessages.test.d.ts.map +0 -1
  34. package/dist/tests/BatchedOutgoingMessages.test.js +0 -112
  35. package/dist/tests/BatchedOutgoingMessages.test.js.map +0 -1
  36. package/dist/tests/integration.test.d.ts +0 -2
  37. package/dist/tests/integration.test.d.ts.map +0 -1
  38. package/dist/tests/integration.test.js.map +0 -1
  39. package/src/tests/BatchedOutgoingMessages.test.ts +0 -146
@@ -1,14 +1,15 @@
1
- import type { SyncMessage } from "cojson";
2
- import type { Channel } from "queueueue";
1
+ import type { CojsonInternalTypes, SyncMessage } from "cojson";
2
+ import { cojsonInternals } from "cojson";
3
3
  import { type Mocked, describe, expect, test, vi } from "vitest";
4
4
  import { MAX_OUTGOING_MESSAGES_CHUNK_BYTES } from "../BatchedOutgoingMessages.js";
5
5
  import {
6
- BUFFER_LIMIT,
7
- BUFFER_LIMIT_POLLING_INTERVAL,
8
6
  type CreateWebSocketPeerOpts,
9
7
  createWebSocketPeer,
10
8
  } from "../createWebSocketPeer.js";
11
9
  import type { AnyWebSocket } from "../types.js";
10
+ import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL } from "../utils.js";
11
+
12
+ const { CO_VALUE_PRIORITY } = cojsonInternals;
12
13
 
13
14
  function setup(opts: Partial<CreateWebSocketPeerOpts> = {}) {
14
15
  const listeners = new Map<string, (event: MessageEvent) => void>();
@@ -48,34 +49,28 @@ describe("createWebSocketPeer", () => {
48
49
  expect(peer).toHaveProperty("incoming");
49
50
  expect(peer).toHaveProperty("outgoing");
50
51
  expect(peer).toHaveProperty("role", "client");
51
- expect(peer).toHaveProperty("crashOnClose", false);
52
52
  });
53
53
 
54
54
  test("should handle disconnection", async () => {
55
- expect.assertions(1);
56
-
57
55
  const { listeners, peer } = setup();
58
56
 
59
- const incoming = peer.incoming as Channel<
60
- SyncMessage | "Disconnected" | "PingTimeout"
61
- >;
62
- const pushSpy = vi.spyOn(incoming, "push");
57
+ const onMessageSpy = vi.fn();
58
+ peer.incoming.onMessage(onMessageSpy);
63
59
 
64
60
  const closeHandler = listeners.get("close");
65
61
 
66
62
  closeHandler?.(new MessageEvent("close"));
67
63
 
68
- expect(pushSpy).toHaveBeenCalledWith("Disconnected");
64
+ expect(onMessageSpy).toHaveBeenCalledWith("Disconnected");
69
65
  });
70
66
 
71
67
  test("should handle ping timeout", async () => {
72
68
  vi.useFakeTimers();
73
69
  const { listeners, peer } = setup();
74
70
 
75
- const incoming = peer.incoming as Channel<
76
- SyncMessage | "Disconnected" | "PingTimeout"
77
- >;
78
- const pushSpy = vi.spyOn(incoming, "push");
71
+ const onMessageSpy = vi.fn();
72
+
73
+ peer.incoming.onMessage(onMessageSpy);
79
74
 
80
75
  const messageHandler = listeners.get("message");
81
76
 
@@ -83,7 +78,7 @@ describe("createWebSocketPeer", () => {
83
78
 
84
79
  await vi.advanceTimersByTimeAsync(10_000);
85
80
 
86
- expect(pushSpy).toHaveBeenCalledWith("PingTimeout");
81
+ expect(onMessageSpy).toHaveBeenCalledWith("Disconnected");
87
82
 
88
83
  vi.useRealTimers();
89
84
  });
@@ -97,15 +92,14 @@ describe("createWebSocketPeer", () => {
97
92
  header: false,
98
93
  sessions: {},
99
94
  };
100
- const promise = peer.outgoing.push(testMessage);
95
+
96
+ peer.outgoing.push(testMessage);
101
97
 
102
98
  await waitFor(() => {
103
99
  expect(mockWebSocket.send).toHaveBeenCalledWith(
104
100
  JSON.stringify(testMessage),
105
101
  );
106
102
  });
107
-
108
- await expect(promise).resolves.toBeUndefined();
109
103
  });
110
104
 
111
105
  test("should stop sending messages when the websocket is closed", async () => {
@@ -153,23 +147,6 @@ describe("createWebSocketPeer", () => {
153
147
  expect(mockWebSocket.close).toHaveBeenCalled();
154
148
  });
155
149
 
156
- test("should return a rejection if a message is sent after the peer is closed", async () => {
157
- const { peer } = setup();
158
-
159
- peer.outgoing.close();
160
-
161
- const message: SyncMessage = {
162
- action: "known",
163
- id: "co_ztest",
164
- header: false,
165
- sessions: {},
166
- };
167
-
168
- await expect(peer.outgoing.push(message)).rejects.toThrow(
169
- "WebSocket closed",
170
- );
171
- });
172
-
173
150
  test("should call onSuccess handler after receiving first message", () => {
174
151
  const onSuccess = vi.fn();
175
152
  const { listeners } = setup({ onSuccess });
@@ -229,6 +206,42 @@ describe("createWebSocketPeer", () => {
229
206
  );
230
207
  });
231
208
 
209
+ test("should sort outgoing messages by priority", async () => {
210
+ const { peer, mockWebSocket } = setup();
211
+
212
+ mockWebSocket.send.mockImplementation(() => {
213
+ mockWebSocket.readyState = 0;
214
+ });
215
+
216
+ const message1: SyncMessage = {
217
+ action: "content",
218
+ id: "co_zlow",
219
+ new: {},
220
+ priority: CO_VALUE_PRIORITY.LOW,
221
+ };
222
+
223
+ const message2: SyncMessage = {
224
+ action: "content",
225
+ id: "co_zhigh",
226
+ new: {},
227
+ priority: CO_VALUE_PRIORITY.HIGH,
228
+ };
229
+
230
+ void peer.outgoing.push(message1);
231
+ void peer.outgoing.push(message2);
232
+ void peer.outgoing.push(message2);
233
+
234
+ await waitFor(() => {
235
+ expect(mockWebSocket.send).toHaveBeenCalled();
236
+ });
237
+
238
+ expect(mockWebSocket.send).toHaveBeenCalledWith(
239
+ [message2, message2, message1]
240
+ .map((msg) => JSON.stringify(msg))
241
+ .join("\n"),
242
+ );
243
+ });
244
+
232
245
  test("should send all the pending messages when the websocket is closed", async () => {
233
246
  const { peer, mockWebSocket } = setup();
234
247
 
@@ -298,6 +311,43 @@ describe("createWebSocketPeer", () => {
298
311
  );
299
312
  });
300
313
 
314
+ test("should send accumulated messages before a large message", async () => {
315
+ const { peer, mockWebSocket } = setup();
316
+
317
+ const smallMessage: SyncMessage = {
318
+ action: "known",
319
+ id: "co_z_small",
320
+ header: false,
321
+ sessions: {},
322
+ };
323
+ const largeMessage: SyncMessage = {
324
+ action: "known",
325
+ id: "co_z_large",
326
+ header: false,
327
+ sessions: {
328
+ // Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
329
+ payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES),
330
+ } as CojsonInternalTypes.CoValueKnownState["sessions"],
331
+ };
332
+
333
+ void peer.outgoing.push(smallMessage);
334
+ void peer.outgoing.push(largeMessage);
335
+
336
+ await waitFor(() => {
337
+ expect(mockWebSocket.send).toHaveBeenCalledTimes(2);
338
+ });
339
+
340
+ expect(mockWebSocket.send).toHaveBeenCalledTimes(2);
341
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(
342
+ 1,
343
+ JSON.stringify(smallMessage),
344
+ );
345
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(
346
+ 2,
347
+ JSON.stringify(largeMessage),
348
+ );
349
+ });
350
+
301
351
  test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
302
352
  vi.useFakeTimers();
303
353
  const { peer, mockWebSocket } = setup();
@@ -88,5 +88,6 @@ export const startSyncServer = async (port?: number) => {
88
88
  syncServer,
89
89
  port: actualPort,
90
90
  localNode,
91
+ wss,
91
92
  };
92
93
  };
@@ -1,3 +1,4 @@
1
+ import { assert } from "node:console";
1
2
  import { ControlledAgent, type CryptoProvider, LocalNode } from "cojson";
2
3
  import { WasmCrypto } from "cojson/crypto/WasmCrypto";
3
4
  import { afterEach, beforeEach, describe, expect, test } from "vitest";
@@ -7,7 +8,7 @@ import { startSyncServer } from "./syncServer";
7
8
  import { waitFor } from "./utils";
8
9
 
9
10
  describe("WebSocket Peer Integration", () => {
10
- let server: any;
11
+ let server: Awaited<ReturnType<typeof startSyncServer>>;
11
12
  let syncServerUrl: string;
12
13
  let crypto: CryptoProvider;
13
14
 
@@ -93,7 +94,11 @@ describe("WebSocket Peer Integration", () => {
93
94
  const serverNode = server.localNode;
94
95
  const serverMap = await serverNode.load(map.id);
95
96
 
96
- expect(serverMap.get("testKey")).toBe("testValue");
97
+ if (serverMap === "unavailable") {
98
+ throw new Error("Server map is unavailable");
99
+ }
100
+
101
+ expect(serverMap.get("testKey")?.toString()).toBe("testValue");
97
102
  });
98
103
 
99
104
  test("should handle disconnection and cleanup", async () => {
@@ -161,4 +166,34 @@ describe("WebSocket Peer Integration", () => {
161
166
 
162
167
  expect(ws.readyState).toBe(WebSocket.CLOSED);
163
168
  });
169
+
170
+ test("calling terminate on the server should close the connection", async () => {
171
+ const ws = new WebSocket(syncServerUrl);
172
+ let disconnectCalled = false;
173
+
174
+ createWebSocketPeer({
175
+ id: "test-client",
176
+ websocket: ws,
177
+ role: "server",
178
+ onClose: () => {
179
+ disconnectCalled = true;
180
+ },
181
+ });
182
+
183
+ await waitFor(() => {
184
+ expect(server.wss.clients.size).toBe(1);
185
+ });
186
+
187
+ const peerOnServer = server.localNode.syncManager.getPeers()[0];
188
+
189
+ for (const client of server.wss.clients) {
190
+ client.terminate();
191
+ }
192
+
193
+ await waitFor(() => {
194
+ expect(disconnectCalled).toBe(true);
195
+ });
196
+
197
+ expect(peerOnServer?.closed).toBe(true);
198
+ });
164
199
  });
package/src/utils.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { AnyWebSocket } from "./types.js";
2
+
3
+ export const BUFFER_LIMIT = 100_000;
4
+ export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
5
+
6
+ export function isWebSocketOpen(websocket: AnyWebSocket) {
7
+ return websocket.readyState === 1;
8
+ }
9
+
10
+ export function hasWebSocketTooMuchBufferedData(websocket: AnyWebSocket) {
11
+ return websocket.bufferedAmount > BUFFER_LIMIT && isWebSocketOpen(websocket);
12
+ }
13
+
14
+ export function waitForWebSocketOpen(websocket: AnyWebSocket) {
15
+ return new Promise<void>((resolve) => {
16
+ if (websocket.readyState === 1) {
17
+ resolve();
18
+ } else {
19
+ websocket.addEventListener("open", () => resolve(), { once: true });
20
+ }
21
+ });
22
+ }
23
+
24
+ export async function waitForWebSocketBufferedAmount(websocket: AnyWebSocket) {
25
+ while (hasWebSocketTooMuchBufferedData(websocket)) {
26
+ await new Promise<void>((resolve) =>
27
+ setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL),
28
+ );
29
+ }
30
+ }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=BatchedOutgoingMessages.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BatchedOutgoingMessages.test.d.ts","sourceRoot":"","sources":["../../src/tests/BatchedOutgoingMessages.test.ts"],"names":[],"mappings":""}
@@ -1,112 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
- import { BatchedOutgoingMessages, MAX_OUTGOING_MESSAGES_CHUNK_BYTES, } from "../BatchedOutgoingMessages.js";
3
- beforeEach(() => {
4
- vi.useFakeTimers();
5
- });
6
- afterEach(() => {
7
- vi.useRealTimers();
8
- });
9
- describe("BatchedOutgoingMessages", () => {
10
- function setup() {
11
- const sendMock = vi.fn();
12
- const batchedMessages = new BatchedOutgoingMessages(sendMock);
13
- return { sendMock, batchedMessages };
14
- }
15
- test("should batch messages and send them after a timeout", () => {
16
- const { sendMock, batchedMessages } = setup();
17
- const message1 = {
18
- action: "known",
19
- id: "co_z1",
20
- header: false,
21
- sessions: {},
22
- };
23
- const message2 = {
24
- action: "known",
25
- id: "co_z2",
26
- header: false,
27
- sessions: {},
28
- };
29
- batchedMessages.push(message1);
30
- batchedMessages.push(message2);
31
- expect(sendMock).not.toHaveBeenCalled();
32
- vi.runAllTimers();
33
- expect(sendMock).toHaveBeenCalledTimes(1);
34
- expect(sendMock).toHaveBeenCalledWith(`${JSON.stringify(message1)}\n${JSON.stringify(message2)}`);
35
- });
36
- test("should send messages immediately when reaching MAX_OUTGOING_MESSAGES_CHUNK_BYTES", () => {
37
- const { sendMock, batchedMessages } = setup();
38
- const largeMessage = {
39
- action: "known",
40
- id: "co_z_large",
41
- header: false,
42
- sessions: {
43
- // Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
44
- payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES),
45
- },
46
- };
47
- batchedMessages.push(largeMessage);
48
- expect(sendMock).toHaveBeenCalledTimes(1);
49
- expect(sendMock).toHaveBeenCalledWith(JSON.stringify(largeMessage));
50
- });
51
- test("should send accumulated messages before a large message", () => {
52
- const { sendMock, batchedMessages } = setup();
53
- const smallMessage = {
54
- action: "known",
55
- id: "co_z_small",
56
- header: false,
57
- sessions: {},
58
- };
59
- const largeMessage = {
60
- action: "known",
61
- id: "co_z_large",
62
- header: false,
63
- sessions: {
64
- // Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
65
- payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES),
66
- },
67
- };
68
- batchedMessages.push(smallMessage);
69
- batchedMessages.push(largeMessage);
70
- vi.runAllTimers();
71
- expect(sendMock).toHaveBeenCalledTimes(2);
72
- expect(sendMock).toHaveBeenNthCalledWith(1, JSON.stringify(smallMessage));
73
- expect(sendMock).toHaveBeenNthCalledWith(2, JSON.stringify(largeMessage));
74
- });
75
- test("should send remaining messages on close", () => {
76
- const { sendMock, batchedMessages } = setup();
77
- const message = {
78
- action: "known",
79
- id: "co_z_test",
80
- header: false,
81
- sessions: {},
82
- };
83
- batchedMessages.push(message);
84
- expect(sendMock).not.toHaveBeenCalled();
85
- batchedMessages.close();
86
- expect(sendMock).toHaveBeenCalledTimes(1);
87
- expect(sendMock).toHaveBeenCalledWith(JSON.stringify(message));
88
- });
89
- test("should clear timeout when pushing new messages", () => {
90
- const { sendMock, batchedMessages } = setup();
91
- const message1 = {
92
- action: "known",
93
- id: "co_z1",
94
- header: false,
95
- sessions: {},
96
- };
97
- const message2 = {
98
- action: "known",
99
- id: "co_z2",
100
- header: false,
101
- sessions: {},
102
- };
103
- batchedMessages.push(message1);
104
- const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
105
- batchedMessages.push(message2);
106
- expect(clearTimeoutSpy).toHaveBeenCalled();
107
- vi.runAllTimers();
108
- expect(sendMock).toHaveBeenCalledTimes(1);
109
- expect(sendMock).toHaveBeenCalledWith(`${JSON.stringify(message1)}\n${JSON.stringify(message2)}`);
110
- });
111
- });
112
- //# sourceMappingURL=BatchedOutgoingMessages.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BatchedOutgoingMessages.test.js","sourceRoot":"","sources":["../../src/tests/BatchedOutgoingMessages.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../../src/tests/integration.test.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"integration.test.js","sourceRoot":"","sources":["../../src/tests/integration.test.ts"],"names":[],"mappings":"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"}
@@ -1,146 +0,0 @@
1
- import type { SyncMessage } from "cojson";
2
- import type { CojsonInternalTypes } from "cojson";
3
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
- import {
5
- BatchedOutgoingMessages,
6
- MAX_OUTGOING_MESSAGES_CHUNK_BYTES,
7
- } from "../BatchedOutgoingMessages.js";
8
-
9
- beforeEach(() => {
10
- vi.useFakeTimers();
11
- });
12
-
13
- afterEach(() => {
14
- vi.useRealTimers();
15
- });
16
-
17
- describe("BatchedOutgoingMessages", () => {
18
- function setup() {
19
- const sendMock = vi.fn();
20
- const batchedMessages = new BatchedOutgoingMessages(sendMock);
21
- return { sendMock, batchedMessages };
22
- }
23
-
24
- test("should batch messages and send them after a timeout", () => {
25
- const { sendMock, batchedMessages } = setup();
26
- const message1: SyncMessage = {
27
- action: "known",
28
- id: "co_z1",
29
- header: false,
30
- sessions: {},
31
- };
32
- const message2: SyncMessage = {
33
- action: "known",
34
- id: "co_z2",
35
- header: false,
36
- sessions: {},
37
- };
38
-
39
- batchedMessages.push(message1);
40
- batchedMessages.push(message2);
41
-
42
- expect(sendMock).not.toHaveBeenCalled();
43
-
44
- vi.runAllTimers();
45
-
46
- expect(sendMock).toHaveBeenCalledTimes(1);
47
- expect(sendMock).toHaveBeenCalledWith(
48
- `${JSON.stringify(message1)}\n${JSON.stringify(message2)}`,
49
- );
50
- });
51
-
52
- test("should send messages immediately when reaching MAX_OUTGOING_MESSAGES_CHUNK_BYTES", () => {
53
- const { sendMock, batchedMessages } = setup();
54
- const largeMessage: SyncMessage = {
55
- action: "known",
56
- id: "co_z_large",
57
- header: false,
58
- sessions: {
59
- // Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
60
- payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES),
61
- } as CojsonInternalTypes.CoValueKnownState["sessions"],
62
- };
63
-
64
- batchedMessages.push(largeMessage);
65
-
66
- expect(sendMock).toHaveBeenCalledTimes(1);
67
- expect(sendMock).toHaveBeenCalledWith(JSON.stringify(largeMessage));
68
- });
69
-
70
- test("should send accumulated messages before a large message", () => {
71
- const { sendMock, batchedMessages } = setup();
72
- const smallMessage: SyncMessage = {
73
- action: "known",
74
- id: "co_z_small",
75
- header: false,
76
- sessions: {},
77
- };
78
- const largeMessage: SyncMessage = {
79
- action: "known",
80
- id: "co_z_large",
81
- header: false,
82
- sessions: {
83
- // Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
84
- payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES),
85
- } as CojsonInternalTypes.CoValueKnownState["sessions"],
86
- };
87
-
88
- batchedMessages.push(smallMessage);
89
- batchedMessages.push(largeMessage);
90
-
91
- vi.runAllTimers();
92
-
93
- expect(sendMock).toHaveBeenCalledTimes(2);
94
- expect(sendMock).toHaveBeenNthCalledWith(1, JSON.stringify(smallMessage));
95
- expect(sendMock).toHaveBeenNthCalledWith(2, JSON.stringify(largeMessage));
96
- });
97
-
98
- test("should send remaining messages on close", () => {
99
- const { sendMock, batchedMessages } = setup();
100
- const message: SyncMessage = {
101
- action: "known",
102
- id: "co_z_test",
103
- header: false,
104
- sessions: {},
105
- };
106
-
107
- batchedMessages.push(message);
108
- expect(sendMock).not.toHaveBeenCalled();
109
-
110
- batchedMessages.close();
111
-
112
- expect(sendMock).toHaveBeenCalledTimes(1);
113
- expect(sendMock).toHaveBeenCalledWith(JSON.stringify(message));
114
- });
115
-
116
- test("should clear timeout when pushing new messages", () => {
117
- const { sendMock, batchedMessages } = setup();
118
- const message1: SyncMessage = {
119
- action: "known",
120
- id: "co_z1",
121
- header: false,
122
- sessions: {},
123
- };
124
- const message2: SyncMessage = {
125
- action: "known",
126
- id: "co_z2",
127
- header: false,
128
- sessions: {},
129
- };
130
-
131
- batchedMessages.push(message1);
132
-
133
- const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
134
-
135
- batchedMessages.push(message2);
136
-
137
- expect(clearTimeoutSpy).toHaveBeenCalled();
138
-
139
- vi.runAllTimers();
140
-
141
- expect(sendMock).toHaveBeenCalledTimes(1);
142
- expect(sendMock).toHaveBeenCalledWith(
143
- `${JSON.stringify(message1)}\n${JSON.stringify(message2)}`,
144
- );
145
- });
146
- });