cojson-transport-ws 0.8.5 → 0.8.7

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.8.3 build /Users/anselm/jazz/jazz/packages/cojson-transport-ws
2
+ > cojson-transport-ws@0.8.5 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.8.3 lint
6
+ > cojson-transport-ws@0.8.5 lint
7
7
  > eslint . --ext ts,tsx
8
8
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # cojson-transport-nodejs-ws
2
2
 
3
+ ## 0.8.7
4
+
5
+ ### Patch Changes
6
+
7
+ - e82cf3d: Enable WebSocket batching on client peers
8
+
9
+ ## 0.8.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 5542fdb: Added batching to the WebSocket messsages
14
+
3
15
  ## 0.8.5
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,42 @@
1
+ import { addMessageToBacklog } from "./serialization.js";
2
+ export const MAX_OUTGOING_MESSAGES_CHUNK_BYTES = 25000;
3
+ export class BatchedOutgoingMessages {
4
+ constructor(send) {
5
+ this.send = send;
6
+ this.backlog = '';
7
+ this.timeout = null;
8
+ }
9
+ push(msg) {
10
+ const payload = addMessageToBacklog(this.backlog, msg);
11
+ if (this.timeout) {
12
+ clearTimeout(this.timeout);
13
+ }
14
+ const maxChunkSizeReached = payload.length >= MAX_OUTGOING_MESSAGES_CHUNK_BYTES;
15
+ const backlogExists = this.backlog.length > 0;
16
+ if (maxChunkSizeReached && backlogExists) {
17
+ this.sendMessagesInBulk();
18
+ this.backlog = addMessageToBacklog('', msg);
19
+ this.timeout = setTimeout(() => {
20
+ this.sendMessagesInBulk();
21
+ }, 0);
22
+ }
23
+ else if (maxChunkSizeReached) {
24
+ this.backlog = payload;
25
+ this.sendMessagesInBulk();
26
+ }
27
+ else {
28
+ this.backlog = payload;
29
+ this.timeout = setTimeout(() => {
30
+ this.sendMessagesInBulk();
31
+ }, 0);
32
+ }
33
+ }
34
+ sendMessagesInBulk() {
35
+ this.send(this.backlog);
36
+ this.backlog = '';
37
+ }
38
+ close() {
39
+ this.sendMessagesInBulk();
40
+ }
41
+ }
42
+ //# sourceMappingURL=BatchedOutgoingMessages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BatchedOutgoingMessages.js","sourceRoot":"","sources":["../src/BatchedOutgoingMessages.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,MAAM,iCAAiC,GAAG,KAAM,CAAC;AAExD,MAAM,OAAO,uBAAuB;IAIhC,YAAoB,IAAgC;QAAhC,SAAI,GAAJ,IAAI,CAA4B;QAH5C,YAAO,GAAW,EAAE,CAAC;QACrB,YAAO,GAAyC,IAAI,CAAC;IAEL,CAAC;IAEzD,IAAI,CAAC,GAAgB;QACjB,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAEvD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,mBAAmB,GAAG,OAAO,CAAC,MAAM,IAAI,iCAAiC,CAAC;QAChF,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAE9C,IAAI,mBAAmB,IAAI,aAAa,EAAE,CAAC;YACvC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,IAAI,CAAC,OAAO,GAAG,mBAAmB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC3B,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,CAAC,EAAE,CAAC,CAAC,CAAC;QACV,CAAC;aAAM,IAAI,mBAAmB,EAAE,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;YACvB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC9B,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;YACvB,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC3B,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,CAAC,EAAE,CAAC,CAAC,CAAC;QACV,CAAC;IACL,CAAC;IAED,kBAAkB;QACd,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,KAAK;QACD,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC9B,CAAC;CACJ"}
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { cojsonInternals, } from "cojson";
2
- const g = globalThis;
2
+ import { BatchedOutgoingMessages } from "./BatchedOutgoingMessages.js";
3
+ import { deserializeMessages } from "./serialization.js";
3
4
  export const BUFFER_LIMIT = 100000;
4
5
  export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
5
- export function createWebSocketPeer({ id, websocket, role, expectPings = true, }) {
6
+ export function createWebSocketPeer({ id, websocket, role, expectPings = true, batchingByDefault = true, }) {
6
7
  const incoming = new cojsonInternals.Channel();
7
8
  websocket.addEventListener("close", function handleClose() {
8
9
  incoming
@@ -10,30 +11,33 @@ export function createWebSocketPeer({ id, websocket, role, expectPings = true, }
10
11
  .catch((e) => console.error("Error while pushing disconnect msg", e));
11
12
  });
12
13
  let pingTimeout = null;
14
+ let supportsBatching = batchingByDefault;
13
15
  websocket.addEventListener("message", function handleIncomingMsg(event) {
14
- const msg = JSON.parse(event.data);
15
- pingTimeout && clearTimeout(pingTimeout);
16
- if (msg?.type === "ping") {
17
- const ping = msg;
18
- g.jazzPings || (g.jazzPings = []);
19
- g.jazzPings.push({
20
- received: Date.now(),
21
- sent: ping.time,
22
- dc: ping.dc,
23
- });
16
+ const result = deserializeMessages(event.data);
17
+ if (!result.ok) {
18
+ console.error("Error while deserializing messages", event.data, result.error);
19
+ return;
24
20
  }
25
- else {
26
- incoming
27
- .push(msg)
28
- .catch((e) => console.error("Error while pushing incoming msg", e));
21
+ const { messages } = result;
22
+ if (!supportsBatching && messages.length > 1) {
23
+ // If more than one message is received, the other peer supports batching
24
+ supportsBatching = true;
29
25
  }
30
26
  if (expectPings) {
27
+ pingTimeout && clearTimeout(pingTimeout);
31
28
  pingTimeout = setTimeout(() => {
32
29
  incoming
33
30
  .push("PingTimeout")
34
31
  .catch((e) => console.error("Error while pushing ping timeout", e));
35
32
  }, 10000);
36
33
  }
34
+ for (const msg of messages) {
35
+ if (msg && "action" in msg) {
36
+ incoming
37
+ .push(msg)
38
+ .catch((e) => console.error("Error while pushing incoming msg", e));
39
+ }
40
+ }
37
41
  });
38
42
  const websocketOpen = new Promise((resolve) => {
39
43
  if (websocket.readyState === 1) {
@@ -43,17 +47,28 @@ export function createWebSocketPeer({ id, websocket, role, expectPings = true, }
43
47
  websocket.addEventListener("open", resolve, { once: true });
44
48
  }
45
49
  });
50
+ const outgoingMessages = new BatchedOutgoingMessages((messages) => {
51
+ if (websocket.readyState === 1) {
52
+ websocket.send(messages);
53
+ }
54
+ });
46
55
  async function pushMessage(msg) {
47
56
  if (websocket.readyState !== 1) {
48
57
  await websocketOpen;
49
58
  }
50
- while (websocket.bufferedAmount > BUFFER_LIMIT && websocket.readyState === 1) {
59
+ while (websocket.bufferedAmount > BUFFER_LIMIT &&
60
+ websocket.readyState === 1) {
51
61
  await new Promise((resolve) => setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL));
52
62
  }
53
63
  if (websocket.readyState !== 1) {
54
64
  return;
55
65
  }
56
- websocket.send(JSON.stringify(msg));
66
+ if (!supportsBatching) {
67
+ websocket.send(JSON.stringify(msg));
68
+ }
69
+ else {
70
+ outgoingMessages.push(msg);
71
+ }
57
72
  }
58
73
  return {
59
74
  id,
@@ -62,6 +77,9 @@ export function createWebSocketPeer({ id, websocket, role, expectPings = true, }
62
77
  push: pushMessage,
63
78
  close() {
64
79
  console.log("Trying to close", id, websocket.readyState);
80
+ if (supportsBatching) {
81
+ outgoingMessages.close();
82
+ }
65
83
  if (websocket.readyState === 0) {
66
84
  websocket.addEventListener("open", function handleClose() {
67
85
  websocket.close();
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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"}
@@ -0,0 +1,22 @@
1
+ export function addMessageToBacklog(backlog, message) {
2
+ if (!backlog) {
3
+ return JSON.stringify(message);
4
+ }
5
+ return `${backlog}\n${JSON.stringify(message)}`;
6
+ }
7
+ export function deserializeMessages(messages) {
8
+ try {
9
+ return {
10
+ ok: true,
11
+ messages: messages.split("\n").map((msg) => JSON.parse(msg)),
12
+ };
13
+ }
14
+ catch (e) {
15
+ console.error("Error while deserializing messages", e);
16
+ return {
17
+ ok: false,
18
+ error: e,
19
+ };
20
+ }
21
+ }
22
+ //# sourceMappingURL=serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.js","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,OAAoB;IACrE,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,GAAG,OAAO,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAChD,IAAI,CAAC;QACD,OAAO;YACH,EAAE,EAAE,IAAI;YACR,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAA8B;SACnF,CAAC;IACf,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,CAAC,CAAC,CAAC;QACvD,OAAO;YACH,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,CAAC;SACF,CAAC;IACf,CAAC;AACL,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { describe, test, expect, vi, beforeEach, afterEach } 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 = { action: "known", id: "co_z1", header: false, sessions: {} };
18
+ const message2 = { action: "known", id: "co_z2", header: false, sessions: {} };
19
+ batchedMessages.push(message1);
20
+ batchedMessages.push(message2);
21
+ expect(sendMock).not.toHaveBeenCalled();
22
+ vi.runAllTimers();
23
+ expect(sendMock).toHaveBeenCalledTimes(1);
24
+ expect(sendMock).toHaveBeenCalledWith(`${JSON.stringify(message1)}\n${JSON.stringify(message2)}`);
25
+ });
26
+ test("should send messages immediately when reaching MAX_OUTGOING_MESSAGES_CHUNK_BYTES", () => {
27
+ const { sendMock, batchedMessages } = setup();
28
+ const largeMessage = {
29
+ action: "known",
30
+ id: "co_z_large",
31
+ header: false,
32
+ sessions: {
33
+ // Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
34
+ payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES)
35
+ },
36
+ };
37
+ batchedMessages.push(largeMessage);
38
+ expect(sendMock).toHaveBeenCalledTimes(1);
39
+ expect(sendMock).toHaveBeenCalledWith(JSON.stringify(largeMessage));
40
+ });
41
+ test("should send accumulated messages before a large message", () => {
42
+ const { sendMock, batchedMessages } = setup();
43
+ const smallMessage = { action: "known", id: "co_z_small", header: false, sessions: {} };
44
+ const largeMessage = {
45
+ action: "known",
46
+ id: "co_z_large",
47
+ header: false,
48
+ sessions: {
49
+ // Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
50
+ payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES)
51
+ },
52
+ };
53
+ batchedMessages.push(smallMessage);
54
+ batchedMessages.push(largeMessage);
55
+ vi.runAllTimers();
56
+ expect(sendMock).toHaveBeenCalledTimes(2);
57
+ expect(sendMock).toHaveBeenNthCalledWith(1, JSON.stringify(smallMessage));
58
+ expect(sendMock).toHaveBeenNthCalledWith(2, JSON.stringify(largeMessage));
59
+ });
60
+ test("should send remaining messages on close", () => {
61
+ const { sendMock, batchedMessages } = setup();
62
+ const message = { action: "known", id: "co_z_test", header: false, sessions: {} };
63
+ batchedMessages.push(message);
64
+ expect(sendMock).not.toHaveBeenCalled();
65
+ batchedMessages.close();
66
+ expect(sendMock).toHaveBeenCalledTimes(1);
67
+ expect(sendMock).toHaveBeenCalledWith(JSON.stringify(message));
68
+ });
69
+ test("should clear timeout when pushing new messages", () => {
70
+ const { sendMock, batchedMessages } = setup();
71
+ const message1 = { action: "known", id: "co_z1", header: false, sessions: {} };
72
+ const message2 = { action: "known", id: "co_z2", header: false, sessions: {} };
73
+ batchedMessages.push(message1);
74
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
75
+ batchedMessages.push(message2);
76
+ expect(clearTimeoutSpy).toHaveBeenCalled();
77
+ vi.runAllTimers();
78
+ expect(sendMock).toHaveBeenCalledTimes(1);
79
+ expect(sendMock).toHaveBeenCalledWith(`${JSON.stringify(message1)}\n${JSON.stringify(message2)}`);
80
+ });
81
+ });
82
+ //# sourceMappingURL=BatchedOutgoingMessages.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BatchedOutgoingMessages.test.js","sourceRoot":"","sources":["../../src/tests/BatchedOutgoingMessages.test.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { describe, test, expect, vi } from "vitest";
2
2
  import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL, createWebSocketPeer, } from "../index.js";
3
- const g = globalThis;
4
- function setup() {
3
+ import { MAX_OUTGOING_MESSAGES_CHUNK_BYTES } from "../BatchedOutgoingMessages.js";
4
+ function setup(opts = {}) {
5
5
  const listeners = new Map();
6
6
  const mockWebSocket = {
7
7
  readyState: 1,
@@ -15,9 +15,14 @@ function setup() {
15
15
  id: "test-peer",
16
16
  websocket: mockWebSocket,
17
17
  role: "client",
18
+ batchingByDefault: true,
19
+ ...opts,
18
20
  });
19
21
  return { mockWebSocket, peer, listeners };
20
22
  }
23
+ function serializeMessages(messages) {
24
+ return messages.map((msg) => JSON.stringify(msg)).join("\n");
25
+ }
21
26
  describe("createWebSocketPeer", () => {
22
27
  test("should create a peer with correct properties", () => {
23
28
  const { peer } = setup();
@@ -27,25 +32,6 @@ describe("createWebSocketPeer", () => {
27
32
  expect(peer).toHaveProperty("role", "client");
28
33
  expect(peer).toHaveProperty("crashOnClose", false);
29
34
  });
30
- test("should handle ping messages", async () => {
31
- const { listeners } = setup();
32
- const pingMessage = {
33
- type: "ping",
34
- time: Date.now(),
35
- dc: "test-dc",
36
- };
37
- const messageEvent = new MessageEvent("message", {
38
- data: JSON.stringify(pingMessage),
39
- });
40
- const messageHandler = listeners.get("message");
41
- messageHandler?.(messageEvent);
42
- // Check if the ping was recorded in the global jazzPings array
43
- expect(g.jazzPings?.length).toBeGreaterThan(0);
44
- expect(g.jazzPings?.at(-1)).toMatchObject({
45
- sent: pingMessage.time,
46
- dc: pingMessage.dc,
47
- });
48
- });
49
35
  test("should handle disconnection", async () => {
50
36
  expect.assertions(1);
51
37
  const { listeners, peer } = setup();
@@ -75,8 +61,9 @@ describe("createWebSocketPeer", () => {
75
61
  sessions: {},
76
62
  };
77
63
  const promise = peer.outgoing.push(testMessage);
78
- await new Promise(queueMicrotask);
79
- expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(testMessage));
64
+ await waitFor(() => {
65
+ expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(testMessage));
66
+ });
80
67
  await expect(promise).resolves.toBeUndefined();
81
68
  });
82
69
  test("should stop sending messages when the websocket is closed", async () => {
@@ -97,43 +84,232 @@ describe("createWebSocketPeer", () => {
97
84
  priority: 1,
98
85
  };
99
86
  void peer.outgoing.push(message1);
100
- void peer.outgoing.push(message2);
101
- await new Promise(queueMicrotask);
102
- expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
103
- expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
104
- });
105
- test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
106
- vi.useFakeTimers();
107
- const { peer, mockWebSocket } = setup();
108
- mockWebSocket.send.mockImplementation(() => {
109
- mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
87
+ await waitFor(() => {
88
+ expect(mockWebSocket.send).toHaveBeenCalled();
110
89
  });
111
- const message1 = {
112
- action: "known",
113
- id: "co_ztest",
114
- header: false,
115
- sessions: {},
116
- };
117
- const message2 = {
118
- action: "content",
119
- id: "co_zlow",
120
- new: {},
121
- priority: 1,
122
- };
123
- void peer.outgoing.push(message1);
90
+ expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(message1));
91
+ mockWebSocket.send.mockClear();
124
92
  void peer.outgoing.push(message2);
125
- await new Promise(queueMicrotask);
126
- expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
127
- expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
128
- mockWebSocket.bufferedAmount = 0;
129
- await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
130
- expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
131
- vi.useRealTimers();
93
+ await new Promise((resolve) => setTimeout(resolve, 100));
94
+ expect(mockWebSocket.send).not.toHaveBeenCalled();
132
95
  });
133
96
  test("should close the websocket connection", () => {
134
97
  const { mockWebSocket, peer } = setup();
135
98
  peer.outgoing.close();
136
99
  expect(mockWebSocket.close).toHaveBeenCalled();
137
100
  });
101
+ describe("batchingByDefault = true", () => {
102
+ test("should batch outgoing messages", async () => {
103
+ const { peer, mockWebSocket } = setup();
104
+ mockWebSocket.send.mockImplementation(() => {
105
+ mockWebSocket.readyState = 0;
106
+ });
107
+ const message1 = {
108
+ action: "known",
109
+ id: "co_ztest",
110
+ header: false,
111
+ sessions: {},
112
+ };
113
+ const message2 = {
114
+ action: "content",
115
+ id: "co_zlow",
116
+ new: {},
117
+ priority: 1,
118
+ };
119
+ void peer.outgoing.push(message1);
120
+ void peer.outgoing.push(message2);
121
+ await waitFor(() => {
122
+ expect(mockWebSocket.send).toHaveBeenCalled();
123
+ });
124
+ expect(mockWebSocket.send).toHaveBeenCalledWith([message1, message2]
125
+ .map((msg) => JSON.stringify(msg))
126
+ .join("\n"));
127
+ });
128
+ test("should send all the pending messages when the websocket is closed", async () => {
129
+ const { peer, mockWebSocket } = setup();
130
+ const message1 = {
131
+ action: "known",
132
+ id: "co_ztest",
133
+ header: false,
134
+ sessions: {},
135
+ };
136
+ const message2 = {
137
+ action: "content",
138
+ id: "co_zlow",
139
+ new: {},
140
+ priority: 1,
141
+ };
142
+ void peer.outgoing.push(message1);
143
+ void peer.outgoing.push(message2);
144
+ peer.outgoing.close();
145
+ expect(mockWebSocket.send).toHaveBeenCalledWith([message1, message2]
146
+ .map((msg) => JSON.stringify(msg))
147
+ .join("\n"));
148
+ });
149
+ test("should limit the chunk size to MAX_OUTGOING_MESSAGES_CHUNK_SIZE", async () => {
150
+ const { peer, mockWebSocket } = setup();
151
+ const message1 = {
152
+ action: "known",
153
+ id: "co_ztest",
154
+ header: false,
155
+ sessions: {},
156
+ };
157
+ const message2 = {
158
+ action: "content",
159
+ id: "co_zlow",
160
+ new: {},
161
+ priority: 1,
162
+ };
163
+ const stream = [];
164
+ while (serializeMessages(stream.concat(message1)).length < MAX_OUTGOING_MESSAGES_CHUNK_BYTES) {
165
+ stream.push(message1);
166
+ void peer.outgoing.push(message1);
167
+ }
168
+ void peer.outgoing.push(message2);
169
+ await waitFor(() => {
170
+ expect(mockWebSocket.send).toHaveBeenCalledTimes(2);
171
+ });
172
+ expect(mockWebSocket.send).toHaveBeenCalledWith(serializeMessages(stream));
173
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
174
+ });
175
+ test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
176
+ vi.useFakeTimers();
177
+ const { peer, mockWebSocket } = setup();
178
+ mockWebSocket.send.mockImplementation(() => {
179
+ mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
180
+ });
181
+ const message1 = {
182
+ action: "known",
183
+ id: "co_ztest",
184
+ header: false,
185
+ sessions: {},
186
+ };
187
+ const message2 = {
188
+ action: "content",
189
+ id: "co_zlow",
190
+ new: {},
191
+ priority: 1,
192
+ };
193
+ const stream = [];
194
+ while (serializeMessages(stream.concat(message1)).length < MAX_OUTGOING_MESSAGES_CHUNK_BYTES) {
195
+ stream.push(message1);
196
+ void peer.outgoing.push(message1);
197
+ }
198
+ void peer.outgoing.push(message2);
199
+ await vi.advanceTimersByTimeAsync(100);
200
+ expect(mockWebSocket.send).toHaveBeenCalledWith(serializeMessages(stream));
201
+ mockWebSocket.bufferedAmount = 0;
202
+ await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
203
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
204
+ vi.useRealTimers();
205
+ });
206
+ });
207
+ describe("batchingByDefault = false", () => {
208
+ test("should not batch outgoing messages", async () => {
209
+ const { peer, mockWebSocket } = setup({ batchingByDefault: false });
210
+ const message1 = {
211
+ action: "known",
212
+ id: "co_ztest",
213
+ header: false,
214
+ sessions: {},
215
+ };
216
+ const message2 = {
217
+ action: "content",
218
+ id: "co_zlow",
219
+ new: {},
220
+ priority: 1,
221
+ };
222
+ void peer.outgoing.push(message1);
223
+ void peer.outgoing.push(message2);
224
+ await waitFor(() => {
225
+ expect(mockWebSocket.send).toHaveBeenCalled();
226
+ });
227
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
228
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
229
+ });
230
+ test("should start batching outgoing messages when reiceving a batched message", async () => {
231
+ const { peer, mockWebSocket, listeners } = setup({
232
+ batchingByDefault: false,
233
+ });
234
+ const message1 = {
235
+ action: "known",
236
+ id: "co_ztest",
237
+ header: false,
238
+ sessions: {},
239
+ };
240
+ const messageHandler = listeners.get("message");
241
+ messageHandler?.(new MessageEvent("message", {
242
+ data: Array.from({ length: 5 }, () => message1)
243
+ .map((msg) => JSON.stringify(msg))
244
+ .join("\n"),
245
+ }));
246
+ const message2 = {
247
+ action: "content",
248
+ id: "co_zlow",
249
+ new: {},
250
+ priority: 1,
251
+ };
252
+ void peer.outgoing.push(message1);
253
+ void peer.outgoing.push(message2);
254
+ await waitFor(() => {
255
+ expect(mockWebSocket.send).toHaveBeenCalled();
256
+ });
257
+ expect(mockWebSocket.send).toHaveBeenCalledWith([message1, message2]
258
+ .map((msg) => JSON.stringify(msg))
259
+ .join("\n"));
260
+ });
261
+ test("should not start batching outgoing messages when reiceving non-batched message", async () => {
262
+ const { peer, mockWebSocket, listeners } = setup({
263
+ batchingByDefault: false,
264
+ });
265
+ const message1 = {
266
+ action: "known",
267
+ id: "co_ztest",
268
+ header: false,
269
+ sessions: {},
270
+ };
271
+ const messageHandler = listeners.get("message");
272
+ messageHandler?.(new MessageEvent("message", {
273
+ data: JSON.stringify(message1),
274
+ }));
275
+ const message2 = {
276
+ action: "content",
277
+ id: "co_zlow",
278
+ new: {},
279
+ priority: 1,
280
+ };
281
+ void peer.outgoing.push(message1);
282
+ void peer.outgoing.push(message2);
283
+ await waitFor(() => {
284
+ expect(mockWebSocket.send).toHaveBeenCalled();
285
+ });
286
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
287
+ expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
288
+ });
289
+ });
138
290
  });
291
+ function waitFor(callback) {
292
+ return new Promise((resolve, reject) => {
293
+ const checkPassed = () => {
294
+ try {
295
+ return { ok: callback(), error: null };
296
+ }
297
+ catch (error) {
298
+ return { ok: false, error };
299
+ }
300
+ };
301
+ let retries = 0;
302
+ const interval = setInterval(() => {
303
+ const { ok, error } = checkPassed();
304
+ if (ok !== false) {
305
+ clearInterval(interval);
306
+ resolve();
307
+ }
308
+ if (++retries > 10) {
309
+ clearInterval(interval);
310
+ reject(error);
311
+ }
312
+ }, 100);
313
+ });
314
+ }
139
315
  //# sourceMappingURL=createWebSocketPeer.test.js.map