cojson-transport-ws 0.8.13 → 0.8.16
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.
- package/CHANGELOG.md +155 -148
- package/dist/BatchedOutgoingMessages.js +3 -3
- package/dist/BatchedOutgoingMessages.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/serialization.js.map +1 -1
- package/dist/tests/BatchedOutgoingMessages.test.js +41 -11
- package/dist/tests/BatchedOutgoingMessages.test.js.map +1 -1
- package/dist/tests/createWebSocketPeer.test.js +9 -13
- package/dist/tests/createWebSocketPeer.test.js.map +1 -1
- package/package.json +5 -9
- package/src/BatchedOutgoingMessages.ts +36 -35
- package/src/index.ts +168 -166
- package/src/serialization.ts +25 -23
- package/src/tests/BatchedOutgoingMessages.test.ts +138 -106
- package/src/tests/createWebSocketPeer.test.ts +404 -411
- package/src/types.ts +19 -20
- package/tsconfig.json +2 -2
- package/.eslintrc.cjs +0 -24
- package/.prettierrc.js +0 -9
|
@@ -1,466 +1,459 @@
|
|
|
1
|
-
import { describe, test, expect, vi, Mocked } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
BUFFER_LIMIT,
|
|
4
|
-
BUFFER_LIMIT_POLLING_INTERVAL,
|
|
5
|
-
createWebSocketPeer,
|
|
6
|
-
CreateWebSocketPeerOpts,
|
|
7
|
-
} from "../index.js";
|
|
8
|
-
import { AnyWebSocket } from "../types.js";
|
|
9
1
|
import { SyncMessage } from "cojson";
|
|
10
2
|
import { Channel } from "cojson/src/streamUtils.ts";
|
|
3
|
+
import { Mocked, describe, expect, test, vi } from "vitest";
|
|
11
4
|
import { MAX_OUTGOING_MESSAGES_CHUNK_BYTES } from "../BatchedOutgoingMessages.js";
|
|
5
|
+
import {
|
|
6
|
+
BUFFER_LIMIT,
|
|
7
|
+
BUFFER_LIMIT_POLLING_INTERVAL,
|
|
8
|
+
CreateWebSocketPeerOpts,
|
|
9
|
+
createWebSocketPeer,
|
|
10
|
+
} from "../index.js";
|
|
11
|
+
import { AnyWebSocket } from "../types.js";
|
|
12
12
|
|
|
13
13
|
function setup(opts: Partial<CreateWebSocketPeerOpts> = {}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
14
|
+
const listeners = new Map<string, (event: MessageEvent) => void>();
|
|
15
|
+
|
|
16
|
+
const mockWebSocket = {
|
|
17
|
+
readyState: 1,
|
|
18
|
+
addEventListener: vi.fn().mockImplementation((type, listener) => {
|
|
19
|
+
listeners.set(type, listener);
|
|
20
|
+
}),
|
|
21
|
+
removeEventListener: vi.fn().mockImplementation((type) => {
|
|
22
|
+
listeners.delete(type);
|
|
23
|
+
}),
|
|
24
|
+
close: vi.fn(),
|
|
25
|
+
send: vi.fn(),
|
|
26
|
+
} as unknown as Mocked<AnyWebSocket>;
|
|
27
|
+
|
|
28
|
+
const peer = createWebSocketPeer({
|
|
29
|
+
id: "test-peer",
|
|
30
|
+
websocket: mockWebSocket,
|
|
31
|
+
role: "client",
|
|
32
|
+
batchingByDefault: true,
|
|
33
|
+
...opts,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return { mockWebSocket, peer, listeners };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function serializeMessages(messages: SyncMessage[]) {
|
|
40
|
-
|
|
40
|
+
return messages.map((msg) => JSON.stringify(msg)).join("\n");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
describe("createWebSocketPeer", () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
test("should create a peer with correct properties", () => {
|
|
45
|
+
const { peer } = setup();
|
|
46
|
+
|
|
47
|
+
expect(peer).toHaveProperty("id", "test-peer");
|
|
48
|
+
expect(peer).toHaveProperty("incoming");
|
|
49
|
+
expect(peer).toHaveProperty("outgoing");
|
|
50
|
+
expect(peer).toHaveProperty("role", "client");
|
|
51
|
+
expect(peer).toHaveProperty("crashOnClose", false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("should handle disconnection", async () => {
|
|
55
|
+
expect.assertions(1);
|
|
56
|
+
|
|
57
|
+
const { listeners, peer } = setup();
|
|
58
|
+
|
|
59
|
+
const incoming = peer.incoming as Channel<
|
|
60
|
+
SyncMessage | "Disconnected" | "PingTimeout"
|
|
61
|
+
>;
|
|
62
|
+
const pushSpy = vi.spyOn(incoming, "push");
|
|
63
|
+
|
|
64
|
+
const closeHandler = listeners.get("close");
|
|
65
|
+
|
|
66
|
+
closeHandler?.(new MessageEvent("close"));
|
|
67
|
+
|
|
68
|
+
expect(pushSpy).toHaveBeenCalledWith("Disconnected");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should handle ping timeout", async () => {
|
|
72
|
+
vi.useFakeTimers();
|
|
73
|
+
const { listeners, peer } = setup();
|
|
74
|
+
|
|
75
|
+
const incoming = peer.incoming as Channel<
|
|
76
|
+
SyncMessage | "Disconnected" | "PingTimeout"
|
|
77
|
+
>;
|
|
78
|
+
const pushSpy = vi.spyOn(incoming, "push");
|
|
79
|
+
|
|
80
|
+
const messageHandler = listeners.get("message");
|
|
81
|
+
|
|
82
|
+
messageHandler?.(new MessageEvent("message", { data: "{}" }));
|
|
83
|
+
|
|
84
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
85
|
+
|
|
86
|
+
expect(pushSpy).toHaveBeenCalledWith("PingTimeout");
|
|
87
|
+
|
|
88
|
+
vi.useRealTimers();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should send outgoing messages", async () => {
|
|
92
|
+
const { peer, mockWebSocket } = setup();
|
|
93
|
+
|
|
94
|
+
const testMessage: SyncMessage = {
|
|
95
|
+
action: "known",
|
|
96
|
+
id: "co_ztest",
|
|
97
|
+
header: false,
|
|
98
|
+
sessions: {},
|
|
99
|
+
};
|
|
100
|
+
const promise = peer.outgoing.push(testMessage);
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
104
|
+
JSON.stringify(testMessage),
|
|
105
|
+
);
|
|
52
106
|
});
|
|
53
107
|
|
|
54
|
-
|
|
55
|
-
|
|
108
|
+
await expect(promise).resolves.toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("should stop sending messages when the websocket is closed", async () => {
|
|
112
|
+
const { peer, mockWebSocket } = setup();
|
|
56
113
|
|
|
57
|
-
|
|
114
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
115
|
+
mockWebSocket.readyState = 0;
|
|
116
|
+
});
|
|
58
117
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
118
|
+
const message1: SyncMessage = {
|
|
119
|
+
action: "known",
|
|
120
|
+
id: "co_ztest",
|
|
121
|
+
header: false,
|
|
122
|
+
sessions: {},
|
|
123
|
+
};
|
|
63
124
|
|
|
64
|
-
|
|
125
|
+
const message2: SyncMessage = {
|
|
126
|
+
action: "content",
|
|
127
|
+
id: "co_zlow",
|
|
128
|
+
new: {},
|
|
129
|
+
priority: 1,
|
|
130
|
+
};
|
|
65
131
|
|
|
66
|
-
|
|
132
|
+
void peer.outgoing.push(message1);
|
|
67
133
|
|
|
68
|
-
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
69
136
|
});
|
|
70
137
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
138
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(message1));
|
|
139
|
+
|
|
140
|
+
mockWebSocket.send.mockClear();
|
|
141
|
+
void peer.outgoing.push(message2);
|
|
142
|
+
|
|
143
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
144
|
+
|
|
145
|
+
expect(mockWebSocket.send).not.toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("should close the websocket connection", () => {
|
|
149
|
+
const { mockWebSocket, peer } = setup();
|
|
74
150
|
|
|
75
|
-
|
|
76
|
-
SyncMessage | "Disconnected" | "PingTimeout"
|
|
77
|
-
>;
|
|
78
|
-
const pushSpy = vi.spyOn(incoming, "push");
|
|
151
|
+
peer.outgoing.close();
|
|
79
152
|
|
|
80
|
-
|
|
153
|
+
expect(mockWebSocket.close).toHaveBeenCalled();
|
|
154
|
+
});
|
|
81
155
|
|
|
82
|
-
|
|
156
|
+
describe("batchingByDefault = true", () => {
|
|
157
|
+
test("should batch outgoing messages", async () => {
|
|
158
|
+
const { peer, mockWebSocket } = setup();
|
|
83
159
|
|
|
84
|
-
|
|
160
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
161
|
+
mockWebSocket.readyState = 0;
|
|
162
|
+
});
|
|
85
163
|
|
|
86
|
-
|
|
164
|
+
const message1: SyncMessage = {
|
|
165
|
+
action: "known",
|
|
166
|
+
id: "co_ztest",
|
|
167
|
+
header: false,
|
|
168
|
+
sessions: {},
|
|
169
|
+
};
|
|
87
170
|
|
|
88
|
-
|
|
171
|
+
const message2: SyncMessage = {
|
|
172
|
+
action: "content",
|
|
173
|
+
id: "co_zlow",
|
|
174
|
+
new: {},
|
|
175
|
+
priority: 1,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
void peer.outgoing.push(message1);
|
|
179
|
+
void peer.outgoing.push(message2);
|
|
180
|
+
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
186
|
+
[message1, message2].map((msg) => JSON.stringify(msg)).join("\n"),
|
|
187
|
+
);
|
|
89
188
|
});
|
|
90
189
|
|
|
91
|
-
test("should send
|
|
92
|
-
|
|
190
|
+
test("should send all the pending messages when the websocket is closed", async () => {
|
|
191
|
+
const { peer, mockWebSocket } = setup();
|
|
93
192
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const promise = peer.outgoing.push(testMessage);
|
|
193
|
+
const message1: SyncMessage = {
|
|
194
|
+
action: "known",
|
|
195
|
+
id: "co_ztest",
|
|
196
|
+
header: false,
|
|
197
|
+
sessions: {},
|
|
198
|
+
};
|
|
101
199
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
200
|
+
const message2: SyncMessage = {
|
|
201
|
+
action: "content",
|
|
202
|
+
id: "co_zlow",
|
|
203
|
+
new: {},
|
|
204
|
+
priority: 1,
|
|
205
|
+
};
|
|
107
206
|
|
|
108
|
-
|
|
207
|
+
void peer.outgoing.push(message1);
|
|
208
|
+
void peer.outgoing.push(message2);
|
|
209
|
+
|
|
210
|
+
peer.outgoing.close();
|
|
211
|
+
|
|
212
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
213
|
+
[message1, message2].map((msg) => JSON.stringify(msg)).join("\n"),
|
|
214
|
+
);
|
|
109
215
|
});
|
|
110
216
|
|
|
111
|
-
test("should
|
|
112
|
-
|
|
217
|
+
test("should limit the chunk size to MAX_OUTGOING_MESSAGES_CHUNK_SIZE", async () => {
|
|
218
|
+
const { peer, mockWebSocket } = setup();
|
|
219
|
+
|
|
220
|
+
const message1: SyncMessage = {
|
|
221
|
+
action: "known",
|
|
222
|
+
id: "co_ztest",
|
|
223
|
+
header: false,
|
|
224
|
+
sessions: {},
|
|
225
|
+
};
|
|
226
|
+
const message2: SyncMessage = {
|
|
227
|
+
action: "content",
|
|
228
|
+
id: "co_zlow",
|
|
229
|
+
new: {},
|
|
230
|
+
priority: 1,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const stream: SyncMessage[] = [];
|
|
234
|
+
|
|
235
|
+
while (
|
|
236
|
+
serializeMessages(stream.concat(message1)).length <
|
|
237
|
+
MAX_OUTGOING_MESSAGES_CHUNK_BYTES
|
|
238
|
+
) {
|
|
239
|
+
stream.push(message1);
|
|
240
|
+
void peer.outgoing.push(message1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
void peer.outgoing.push(message2);
|
|
113
244
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
245
|
+
await waitFor(() => {
|
|
246
|
+
expect(mockWebSocket.send).toHaveBeenCalledTimes(2);
|
|
247
|
+
});
|
|
117
248
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
header: false,
|
|
122
|
-
sessions: {},
|
|
123
|
-
};
|
|
249
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
250
|
+
serializeMessages(stream),
|
|
251
|
+
);
|
|
124
252
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
253
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
254
|
+
2,
|
|
255
|
+
JSON.stringify(message2),
|
|
256
|
+
);
|
|
257
|
+
});
|
|
131
258
|
|
|
259
|
+
test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
|
|
260
|
+
vi.useFakeTimers();
|
|
261
|
+
const { peer, mockWebSocket } = setup();
|
|
262
|
+
|
|
263
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
264
|
+
mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const message1: SyncMessage = {
|
|
268
|
+
action: "known",
|
|
269
|
+
id: "co_ztest",
|
|
270
|
+
header: false,
|
|
271
|
+
sessions: {},
|
|
272
|
+
};
|
|
273
|
+
const message2: SyncMessage = {
|
|
274
|
+
action: "content",
|
|
275
|
+
id: "co_zlow",
|
|
276
|
+
new: {},
|
|
277
|
+
priority: 1,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const stream: SyncMessage[] = [];
|
|
281
|
+
|
|
282
|
+
while (
|
|
283
|
+
serializeMessages(stream.concat(message1)).length <
|
|
284
|
+
MAX_OUTGOING_MESSAGES_CHUNK_BYTES
|
|
285
|
+
) {
|
|
286
|
+
stream.push(message1);
|
|
132
287
|
void peer.outgoing.push(message1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
void peer.outgoing.push(message2);
|
|
291
|
+
|
|
292
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
133
293
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
294
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
295
|
+
serializeMessages(stream),
|
|
296
|
+
);
|
|
137
297
|
|
|
138
|
-
|
|
298
|
+
mockWebSocket.bufferedAmount = 0;
|
|
139
299
|
|
|
140
|
-
|
|
141
|
-
void peer.outgoing.push(message2);
|
|
300
|
+
await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
|
|
142
301
|
|
|
143
|
-
|
|
302
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
303
|
+
2,
|
|
304
|
+
JSON.stringify(message2),
|
|
305
|
+
);
|
|
144
306
|
|
|
145
|
-
|
|
307
|
+
vi.useRealTimers();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe("batchingByDefault = false", () => {
|
|
312
|
+
test("should not batch outgoing messages", async () => {
|
|
313
|
+
const { peer, mockWebSocket } = setup({ batchingByDefault: false });
|
|
314
|
+
|
|
315
|
+
const message1: SyncMessage = {
|
|
316
|
+
action: "known",
|
|
317
|
+
id: "co_ztest",
|
|
318
|
+
header: false,
|
|
319
|
+
sessions: {},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const message2: SyncMessage = {
|
|
323
|
+
action: "content",
|
|
324
|
+
id: "co_zlow",
|
|
325
|
+
new: {},
|
|
326
|
+
priority: 1,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
void peer.outgoing.push(message1);
|
|
330
|
+
void peer.outgoing.push(message2);
|
|
331
|
+
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
337
|
+
1,
|
|
338
|
+
JSON.stringify(message1),
|
|
339
|
+
);
|
|
340
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
341
|
+
2,
|
|
342
|
+
JSON.stringify(message2),
|
|
343
|
+
);
|
|
146
344
|
});
|
|
147
345
|
|
|
148
|
-
test("should
|
|
149
|
-
|
|
346
|
+
test("should start batching outgoing messages when reiceving a batched message", async () => {
|
|
347
|
+
const { peer, mockWebSocket, listeners } = setup({
|
|
348
|
+
batchingByDefault: false,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const message1: SyncMessage = {
|
|
352
|
+
action: "known",
|
|
353
|
+
id: "co_ztest",
|
|
354
|
+
header: false,
|
|
355
|
+
sessions: {},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const messageHandler = listeners.get("message");
|
|
359
|
+
|
|
360
|
+
messageHandler?.(
|
|
361
|
+
new MessageEvent("message", {
|
|
362
|
+
data: Array.from({ length: 5 }, () => message1)
|
|
363
|
+
.map((msg) => JSON.stringify(msg))
|
|
364
|
+
.join("\n"),
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
150
367
|
|
|
151
|
-
|
|
368
|
+
const message2: SyncMessage = {
|
|
369
|
+
action: "content",
|
|
370
|
+
id: "co_zlow",
|
|
371
|
+
new: {},
|
|
372
|
+
priority: 1,
|
|
373
|
+
};
|
|
152
374
|
|
|
153
|
-
|
|
154
|
-
|
|
375
|
+
void peer.outgoing.push(message1);
|
|
376
|
+
void peer.outgoing.push(message2);
|
|
155
377
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const message1: SyncMessage = {
|
|
165
|
-
action: "known",
|
|
166
|
-
id: "co_ztest",
|
|
167
|
-
header: false,
|
|
168
|
-
sessions: {},
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const message2: SyncMessage = {
|
|
172
|
-
action: "content",
|
|
173
|
-
id: "co_zlow",
|
|
174
|
-
new: {},
|
|
175
|
-
priority: 1,
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
void peer.outgoing.push(message1);
|
|
179
|
-
void peer.outgoing.push(message2);
|
|
180
|
-
|
|
181
|
-
await waitFor(() => {
|
|
182
|
-
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
186
|
-
[message1, message2]
|
|
187
|
-
.map((msg) => JSON.stringify(msg))
|
|
188
|
-
.join("\n"),
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("should send all the pending messages when the websocket is closed", async () => {
|
|
193
|
-
const { peer, mockWebSocket } = setup();
|
|
194
|
-
|
|
195
|
-
const message1: SyncMessage = {
|
|
196
|
-
action: "known",
|
|
197
|
-
id: "co_ztest",
|
|
198
|
-
header: false,
|
|
199
|
-
sessions: {},
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const message2: SyncMessage = {
|
|
203
|
-
action: "content",
|
|
204
|
-
id: "co_zlow",
|
|
205
|
-
new: {},
|
|
206
|
-
priority: 1,
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
void peer.outgoing.push(message1);
|
|
210
|
-
void peer.outgoing.push(message2);
|
|
211
|
-
|
|
212
|
-
peer.outgoing.close();
|
|
213
|
-
|
|
214
|
-
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
215
|
-
[message1, message2]
|
|
216
|
-
.map((msg) => JSON.stringify(msg))
|
|
217
|
-
.join("\n"),
|
|
218
|
-
);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test("should limit the chunk size to MAX_OUTGOING_MESSAGES_CHUNK_SIZE", async () => {
|
|
222
|
-
const { peer, mockWebSocket } = setup();
|
|
223
|
-
|
|
224
|
-
const message1: SyncMessage = {
|
|
225
|
-
action: "known",
|
|
226
|
-
id: "co_ztest",
|
|
227
|
-
header: false,
|
|
228
|
-
sessions: {},
|
|
229
|
-
};
|
|
230
|
-
const message2: SyncMessage = {
|
|
231
|
-
action: "content",
|
|
232
|
-
id: "co_zlow",
|
|
233
|
-
new: {},
|
|
234
|
-
priority: 1,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const stream: SyncMessage[] = [];
|
|
238
|
-
|
|
239
|
-
while (serializeMessages(stream.concat(message1)).length < MAX_OUTGOING_MESSAGES_CHUNK_BYTES) {
|
|
240
|
-
stream.push(message1);
|
|
241
|
-
void peer.outgoing.push(message1);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
void peer.outgoing.push(message2);
|
|
245
|
-
|
|
246
|
-
await waitFor(() => {
|
|
247
|
-
expect(mockWebSocket.send).toHaveBeenCalledTimes(2);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
251
|
-
serializeMessages(stream),
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
255
|
-
2,
|
|
256
|
-
JSON.stringify(message2),
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
|
|
261
|
-
vi.useFakeTimers();
|
|
262
|
-
const { peer, mockWebSocket } = setup();
|
|
263
|
-
|
|
264
|
-
mockWebSocket.send.mockImplementation(() => {
|
|
265
|
-
mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const message1: SyncMessage = {
|
|
269
|
-
action: "known",
|
|
270
|
-
id: "co_ztest",
|
|
271
|
-
header: false,
|
|
272
|
-
sessions: {},
|
|
273
|
-
};
|
|
274
|
-
const message2: SyncMessage = {
|
|
275
|
-
action: "content",
|
|
276
|
-
id: "co_zlow",
|
|
277
|
-
new: {},
|
|
278
|
-
priority: 1,
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const stream: SyncMessage[] = [];
|
|
282
|
-
|
|
283
|
-
while (serializeMessages(stream.concat(message1)).length < MAX_OUTGOING_MESSAGES_CHUNK_BYTES) {
|
|
284
|
-
stream.push(message1);
|
|
285
|
-
void peer.outgoing.push(message1);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
void peer.outgoing.push(message2);
|
|
289
|
-
|
|
290
|
-
await vi.advanceTimersByTimeAsync(100);
|
|
291
|
-
|
|
292
|
-
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
293
|
-
serializeMessages(stream),
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
mockWebSocket.bufferedAmount = 0;
|
|
297
|
-
|
|
298
|
-
await vi.advanceTimersByTimeAsync(
|
|
299
|
-
BUFFER_LIMIT_POLLING_INTERVAL + 1,
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
303
|
-
2,
|
|
304
|
-
JSON.stringify(message2),
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
vi.useRealTimers();
|
|
308
|
-
});
|
|
378
|
+
await waitFor(() => {
|
|
379
|
+
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
383
|
+
[message1, message2].map((msg) => JSON.stringify(msg)).join("\n"),
|
|
384
|
+
);
|
|
309
385
|
});
|
|
310
386
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
id: "co_ztest",
|
|
354
|
-
header: false,
|
|
355
|
-
sessions: {},
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
const messageHandler = listeners.get("message");
|
|
359
|
-
|
|
360
|
-
messageHandler?.(
|
|
361
|
-
new MessageEvent("message", {
|
|
362
|
-
data: Array.from(
|
|
363
|
-
{ length: 5 },
|
|
364
|
-
() => message1,
|
|
365
|
-
)
|
|
366
|
-
.map((msg) => JSON.stringify(msg))
|
|
367
|
-
.join("\n"),
|
|
368
|
-
}),
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const message2: SyncMessage = {
|
|
373
|
-
action: "content",
|
|
374
|
-
id: "co_zlow",
|
|
375
|
-
new: {},
|
|
376
|
-
priority: 1,
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
void peer.outgoing.push(message1);
|
|
380
|
-
void peer.outgoing.push(message2);
|
|
381
|
-
|
|
382
|
-
await waitFor(() => {
|
|
383
|
-
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
387
|
-
[message1, message2]
|
|
388
|
-
.map((msg) => JSON.stringify(msg))
|
|
389
|
-
.join("\n"),
|
|
390
|
-
);
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
test("should not start batching outgoing messages when reiceving non-batched message", async () => {
|
|
394
|
-
const { peer, mockWebSocket, listeners } = setup({
|
|
395
|
-
batchingByDefault: false,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
const message1: SyncMessage = {
|
|
399
|
-
action: "known",
|
|
400
|
-
id: "co_ztest",
|
|
401
|
-
header: false,
|
|
402
|
-
sessions: {},
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
const messageHandler = listeners.get("message");
|
|
406
|
-
|
|
407
|
-
messageHandler?.(
|
|
408
|
-
new MessageEvent("message", {
|
|
409
|
-
data: JSON.stringify(message1),
|
|
410
|
-
}),
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const message2: SyncMessage = {
|
|
415
|
-
action: "content",
|
|
416
|
-
id: "co_zlow",
|
|
417
|
-
new: {},
|
|
418
|
-
priority: 1,
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
void peer.outgoing.push(message1);
|
|
422
|
-
void peer.outgoing.push(message2);
|
|
423
|
-
|
|
424
|
-
await waitFor(() => {
|
|
425
|
-
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
429
|
-
1,
|
|
430
|
-
JSON.stringify(message1),
|
|
431
|
-
);
|
|
432
|
-
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
433
|
-
2,
|
|
434
|
-
JSON.stringify(message2),
|
|
435
|
-
);
|
|
436
|
-
});
|
|
387
|
+
test("should not start batching outgoing messages when reiceving non-batched message", async () => {
|
|
388
|
+
const { peer, mockWebSocket, listeners } = setup({
|
|
389
|
+
batchingByDefault: false,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const message1: SyncMessage = {
|
|
393
|
+
action: "known",
|
|
394
|
+
id: "co_ztest",
|
|
395
|
+
header: false,
|
|
396
|
+
sessions: {},
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const messageHandler = listeners.get("message");
|
|
400
|
+
|
|
401
|
+
messageHandler?.(
|
|
402
|
+
new MessageEvent("message", {
|
|
403
|
+
data: JSON.stringify(message1),
|
|
404
|
+
}),
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const message2: SyncMessage = {
|
|
408
|
+
action: "content",
|
|
409
|
+
id: "co_zlow",
|
|
410
|
+
new: {},
|
|
411
|
+
priority: 1,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
void peer.outgoing.push(message1);
|
|
415
|
+
void peer.outgoing.push(message2);
|
|
416
|
+
|
|
417
|
+
await waitFor(() => {
|
|
418
|
+
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
422
|
+
1,
|
|
423
|
+
JSON.stringify(message1),
|
|
424
|
+
);
|
|
425
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
426
|
+
2,
|
|
427
|
+
JSON.stringify(message2),
|
|
428
|
+
);
|
|
437
429
|
});
|
|
430
|
+
});
|
|
438
431
|
});
|
|
439
432
|
|
|
440
433
|
function waitFor(callback: () => boolean | void) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
434
|
+
return new Promise<void>((resolve, reject) => {
|
|
435
|
+
const checkPassed = () => {
|
|
436
|
+
try {
|
|
437
|
+
return { ok: callback(), error: null };
|
|
438
|
+
} catch (error) {
|
|
439
|
+
return { ok: false, error };
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
let retries = 0;
|
|
444
|
+
|
|
445
|
+
const interval = setInterval(() => {
|
|
446
|
+
const { ok, error } = checkPassed();
|
|
447
|
+
|
|
448
|
+
if (ok !== false) {
|
|
449
|
+
clearInterval(interval);
|
|
450
|
+
resolve();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (++retries > 10) {
|
|
454
|
+
clearInterval(interval);
|
|
455
|
+
reject(error);
|
|
456
|
+
}
|
|
457
|
+
}, 100);
|
|
458
|
+
});
|
|
466
459
|
}
|