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.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +12 -0
- package/dist/BatchedOutgoingMessages.js +42 -0
- package/dist/BatchedOutgoingMessages.js.map +1 -0
- package/dist/index.js +36 -18
- package/dist/index.js.map +1 -1
- package/dist/serialization.js +22 -0
- package/dist/serialization.js.map +1 -0
- package/dist/tests/BatchedOutgoingMessages.test.js +82 -0
- package/dist/tests/BatchedOutgoingMessages.test.js.map +1 -0
- package/dist/tests/createWebSocketPeer.test.js +229 -53
- package/dist/tests/createWebSocketPeer.test.js.map +1 -1
- package/package.json +1 -1
- package/src/BatchedOutgoingMessages.ts +47 -0
- package/src/index.ts +63 -35
- package/src/serialization.ts +24 -0
- package/src/tests/BatchedOutgoingMessages.test.ts +114 -0
- package/src/tests/createWebSocketPeer.test.ts +329 -86
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
> cojson-transport-ws@0.8.
|
|
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.
|
|
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
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 &&
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAKH,eAAe,GAClB,MAAM,QAAQ,CAAC;
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC3E,OAAO,EAAE,uBAAuB,EAAE,iCAAiC,EAAE,MAAM,+BAA+B,CAAC;AAI3G,UAAU,CAAC,GAAG,EAAE;IACZ,EAAE,CAAC,aAAa,EAAE,CAAC;AACvB,CAAC,CAAC,CAAA;AAEF,SAAS,CAAC,GAAG,EAAE;IACX,EAAE,CAAC,aAAa,EAAE,CAAC;AACvB,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACrC,SAAS,KAAK;QACV,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,MAAM,eAAe,GAAG,IAAI,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAC9D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;IACzC,CAAC;IAED,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,KAAK,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAC5F,MAAM,QAAQ,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAE5F,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/B,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE/B,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAExC,EAAE,CAAC,YAAY,EAAE,CAAC;QAElB,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CACjC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAC7D,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,KAAK,EAAE,CAAC;QAC9C,MAAM,YAAY,GAAgB;YAC9B,MAAM,EAAE,OAAO;YACf,EAAE,EAAE,YAAY;YAChB,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE;gBACN,kEAAkE;gBAClE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,iCAAiC,CAAC;aACxB;SAErC,CAAC;QAEF,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAEnC,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,KAAK,EAAE,CAAC;QAC9C,MAAM,YAAY,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QACrG,MAAM,YAAY,GAAgB;YAC9B,MAAM,EAAE,OAAO;YACf,EAAE,EAAE,YAAY;YAChB,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE;gBACN,kEAAkE;gBAClE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,iCAAiC,CAAC;aACxB;SACrC,CAAC;QAEF,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACnC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAEnC,EAAE,CAAC,YAAY,EAAE,CAAC;QAElB,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,uBAAuB,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;QAC1E,MAAM,CAAC,QAAQ,CAAC,CAAC,uBAAuB,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,KAAK,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAE/F,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAExC,eAAe,CAAC,KAAK,EAAE,CAAC;QAExB,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,KAAK,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAC5F,MAAM,QAAQ,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAE5F,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE/B,MAAM,eAAe,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QAEzD,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE/B,MAAM,CAAC,eAAe,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAE3C,EAAE,CAAC,YAAY,EAAE,CAAC;QAElB,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CACjC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAC7D,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -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
|
-
|
|
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
|
|
79
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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(
|
|
126
|
-
expect(mockWebSocket.send).
|
|
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
|