cojson-transport-ws 0.8.12 → 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 +160 -147
- package/dist/BatchedOutgoingMessages.js +3 -3
- package/dist/BatchedOutgoingMessages.js.map +1 -1
- package/dist/index.js +87 -40
- package/dist/index.js.map +1 -1
- package/dist/serialization.js +6 -0
- 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 +12 -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 +182 -122
- package/src/serialization.ts +26 -17
- package/src/tests/BatchedOutgoingMessages.test.ts +138 -106
- package/src/tests/createWebSocketPeer.test.ts +404 -408
- package/src/types.ts +19 -20
- package/tsconfig.json +2 -2
- package/.eslintrc.cjs +0 -24
- package/.prettierrc.js +0 -9
package/src/index.ts
CHANGED
|
@@ -1,151 +1,211 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
DisconnectedError,
|
|
3
|
+
Peer,
|
|
4
|
+
PingTimeoutError,
|
|
5
|
+
SyncMessage,
|
|
6
|
+
cojsonInternals,
|
|
7
7
|
} from "cojson";
|
|
8
|
-
import { AnyWebSocket } from "./types.js";
|
|
9
8
|
import { BatchedOutgoingMessages } from "./BatchedOutgoingMessages.js";
|
|
10
9
|
import { deserializeMessages } from "./serialization.js";
|
|
10
|
+
import { AnyWebSocket } from "./types.js";
|
|
11
11
|
|
|
12
12
|
export const BUFFER_LIMIT = 100_000;
|
|
13
13
|
export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
|
|
14
14
|
|
|
15
15
|
export type CreateWebSocketPeerOpts = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
id: string;
|
|
17
|
+
websocket: AnyWebSocket;
|
|
18
|
+
role: Peer["role"];
|
|
19
|
+
expectPings?: boolean;
|
|
20
|
+
batchingByDefault?: boolean;
|
|
21
|
+
onClose?: () => void;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
function createPingTimeoutListener(enabled: boolean, callback: () => void) {
|
|
25
|
+
if (!enabled) {
|
|
26
|
+
return {
|
|
27
|
+
reset() {},
|
|
28
|
+
clear() {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let pingTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
reset() {
|
|
36
|
+
pingTimeout && clearTimeout(pingTimeout);
|
|
37
|
+
pingTimeout = setTimeout(() => {
|
|
38
|
+
callback();
|
|
39
|
+
}, 10_000);
|
|
40
|
+
},
|
|
41
|
+
clear() {
|
|
42
|
+
pingTimeout && clearTimeout(pingTimeout);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
function waitForWebSocketOpen(websocket: AnyWebSocket) {
|
|
48
|
+
return new Promise<void>((resolve) => {
|
|
49
|
+
if (websocket.readyState === 1) {
|
|
50
|
+
resolve();
|
|
51
|
+
} else {
|
|
52
|
+
websocket.addEventListener("open", resolve, { once: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
function createOutgoingMessagesManager(
|
|
58
|
+
websocket: AnyWebSocket,
|
|
59
|
+
batchingByDefault: boolean,
|
|
60
|
+
) {
|
|
61
|
+
const outgoingMessages = new BatchedOutgoingMessages((messages) => {
|
|
62
|
+
if (websocket.readyState === 1) {
|
|
63
|
+
websocket.send(messages);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
const result = deserializeMessages(event.data as string);
|
|
67
|
+
let batchingEnabled = batchingByDefault;
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
async function sendMessage(msg: SyncMessage) {
|
|
70
|
+
if (websocket.readyState !== 1) {
|
|
71
|
+
await waitForWebSocketOpen(websocket);
|
|
72
|
+
}
|
|
53
73
|
|
|
54
|
-
|
|
74
|
+
while (
|
|
75
|
+
websocket.bufferedAmount > BUFFER_LIMIT &&
|
|
76
|
+
websocket.readyState === 1
|
|
77
|
+
) {
|
|
78
|
+
await new Promise<void>((resolve) =>
|
|
79
|
+
setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
83
|
+
if (websocket.readyState !== 1) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
if (!batchingEnabled) {
|
|
88
|
+
websocket.send(JSON.stringify(msg));
|
|
89
|
+
} else {
|
|
90
|
+
outgoingMessages.push(msg);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
sendMessage,
|
|
96
|
+
setBatchingEnabled(enabled: boolean) {
|
|
97
|
+
batchingEnabled = enabled;
|
|
98
|
+
},
|
|
99
|
+
close() {
|
|
100
|
+
outgoingMessages.close();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
71
104
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
incoming
|
|
75
|
-
.push(msg)
|
|
76
|
-
.catch((e) =>
|
|
77
|
-
console.error("Error while pushing incoming msg", e),
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
105
|
+
function createClosedEventEmitter(callback = () => {}) {
|
|
106
|
+
let disconnected = false;
|
|
82
107
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
108
|
+
return () => {
|
|
109
|
+
if (disconnected) return;
|
|
110
|
+
disconnected = true;
|
|
111
|
+
callback();
|
|
112
|
+
};
|
|
113
|
+
}
|
|
90
114
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
export function createWebSocketPeer({
|
|
116
|
+
id,
|
|
117
|
+
websocket,
|
|
118
|
+
role,
|
|
119
|
+
expectPings = true,
|
|
120
|
+
batchingByDefault = true,
|
|
121
|
+
onClose,
|
|
122
|
+
}: CreateWebSocketPeerOpts): Peer {
|
|
123
|
+
const incoming = new cojsonInternals.Channel<
|
|
124
|
+
SyncMessage | DisconnectedError | PingTimeoutError
|
|
125
|
+
>();
|
|
126
|
+
const emitClosedEvent = createClosedEventEmitter(onClose);
|
|
127
|
+
|
|
128
|
+
function handleClose() {
|
|
129
|
+
incoming
|
|
130
|
+
.push("Disconnected")
|
|
131
|
+
.catch((e) => console.error("Error while pushing disconnect msg", e));
|
|
132
|
+
emitClosedEvent();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
websocket.addEventListener("close", handleClose);
|
|
136
|
+
|
|
137
|
+
const pingTimeout = createPingTimeoutListener(expectPings, () => {
|
|
138
|
+
incoming
|
|
139
|
+
.push("PingTimeout")
|
|
140
|
+
.catch((e) => console.error("Error while pushing ping timeout", e));
|
|
141
|
+
emitClosedEvent();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const outgoingMessages = createOutgoingMessagesManager(
|
|
145
|
+
websocket,
|
|
146
|
+
batchingByDefault,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
function handleIncomingMsg(event: { data: unknown }) {
|
|
150
|
+
const result = deserializeMessages(event.data);
|
|
151
|
+
|
|
152
|
+
if (!result.ok) {
|
|
153
|
+
console.error(
|
|
154
|
+
"Error while deserializing messages",
|
|
155
|
+
event.data,
|
|
156
|
+
result.error,
|
|
157
|
+
);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
98
160
|
|
|
99
|
-
|
|
100
|
-
if (websocket.readyState !== 1) {
|
|
101
|
-
await websocketOpen;
|
|
102
|
-
}
|
|
161
|
+
const { messages } = result;
|
|
103
162
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
await new Promise<void>((resolve) =>
|
|
109
|
-
setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL),
|
|
110
|
-
);
|
|
111
|
-
}
|
|
163
|
+
if (messages.length > 1) {
|
|
164
|
+
// If more than one message is received, the other peer supports batching
|
|
165
|
+
outgoingMessages.setBatchingEnabled(true);
|
|
166
|
+
}
|
|
112
167
|
|
|
113
|
-
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
168
|
+
pingTimeout.reset();
|
|
116
169
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
170
|
+
for (const msg of messages) {
|
|
171
|
+
if (msg && "action" in msg) {
|
|
172
|
+
incoming
|
|
173
|
+
.push(msg)
|
|
174
|
+
.catch((e) => console.error("Error while pushing incoming msg", e));
|
|
175
|
+
}
|
|
122
176
|
}
|
|
177
|
+
}
|
|
123
178
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
179
|
+
websocket.addEventListener("message", handleIncomingMsg);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
id,
|
|
183
|
+
incoming,
|
|
184
|
+
outgoing: {
|
|
185
|
+
push: outgoingMessages.sendMessage,
|
|
186
|
+
close() {
|
|
187
|
+
console.log("Trying to close", id, websocket.readyState);
|
|
188
|
+
outgoingMessages.close();
|
|
189
|
+
|
|
190
|
+
websocket.removeEventListener("message", handleIncomingMsg);
|
|
191
|
+
websocket.removeEventListener("close", handleClose);
|
|
192
|
+
pingTimeout.clear();
|
|
193
|
+
emitClosedEvent();
|
|
194
|
+
|
|
195
|
+
if (websocket.readyState === 0) {
|
|
196
|
+
websocket.addEventListener(
|
|
197
|
+
"open",
|
|
198
|
+
function handleClose() {
|
|
199
|
+
websocket.close();
|
|
146
200
|
},
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
201
|
+
{ once: true },
|
|
202
|
+
);
|
|
203
|
+
} else if (websocket.readyState == 1) {
|
|
204
|
+
websocket.close();
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
role,
|
|
209
|
+
crashOnClose: false,
|
|
210
|
+
};
|
|
151
211
|
}
|
package/src/serialization.ts
CHANGED
|
@@ -2,23 +2,32 @@ import { SyncMessage } from "cojson";
|
|
|
2
2
|
import { PingMsg } from "./types.js";
|
|
3
3
|
|
|
4
4
|
export function addMessageToBacklog(backlog: string, message: SyncMessage) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
if (!backlog) {
|
|
6
|
+
return JSON.stringify(message);
|
|
7
|
+
}
|
|
8
|
+
return `${backlog}\n${JSON.stringify(message)}`;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function deserializeMessages(messages:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
export function deserializeMessages(messages: unknown) {
|
|
12
|
+
if (typeof messages !== "string") {
|
|
13
|
+
return {
|
|
14
|
+
ok: false,
|
|
15
|
+
error: new Error("Expected a string"),
|
|
16
|
+
} as const;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
messages: messages.split("\n").map((msg) => JSON.parse(msg)) as
|
|
23
|
+
| SyncMessage[]
|
|
24
|
+
| PingMsg[],
|
|
25
|
+
} as const;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error("Error while deserializing messages", e);
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: e,
|
|
31
|
+
} as const;
|
|
32
|
+
}
|
|
24
33
|
}
|
|
@@ -1,114 +1,146 @@
|
|
|
1
|
-
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { BatchedOutgoingMessages, MAX_OUTGOING_MESSAGES_CHUNK_BYTES } from "../BatchedOutgoingMessages.js";
|
|
3
1
|
import { SyncMessage } from "cojson";
|
|
4
2
|
import { CoValueKnownState } from "cojson/src/sync.js";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
BatchedOutgoingMessages,
|
|
6
|
+
MAX_OUTGOING_MESSAGES_CHUNK_BYTES,
|
|
7
|
+
} from "../BatchedOutgoingMessages.js";
|
|
5
8
|
|
|
6
9
|
beforeEach(() => {
|
|
7
|
-
|
|
8
|
-
})
|
|
10
|
+
vi.useFakeTimers();
|
|
11
|
+
});
|
|
9
12
|
|
|
10
13
|
afterEach(() => {
|
|
11
|
-
|
|
12
|
-
})
|
|
14
|
+
vi.useRealTimers();
|
|
15
|
+
});
|
|
13
16
|
|
|
14
17
|
describe("BatchedOutgoingMessages", () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
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 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 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
|
+
});
|