cojson-transport-ws 0.15.8 → 0.15.9
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 +1 -1
- package/CHANGELOG.md +8 -0
- package/dist/BatchedOutgoingMessages.d.ts +17 -6
- package/dist/BatchedOutgoingMessages.d.ts.map +1 -1
- package/dist/BatchedOutgoingMessages.js +77 -14
- package/dist/BatchedOutgoingMessages.js.map +1 -1
- package/dist/createWebSocketPeer.d.ts +0 -2
- package/dist/createWebSocketPeer.d.ts.map +1 -1
- package/dist/createWebSocketPeer.js +27 -84
- package/dist/createWebSocketPeer.js.map +1 -1
- package/dist/tests/createWebSocketPeer.test.js +64 -22
- package/dist/tests/createWebSocketPeer.test.js.map +1 -1
- package/dist/tests/syncServer.d.ts +1 -0
- package/dist/tests/syncServer.d.ts.map +1 -1
- package/dist/tests/syncServer.js +1 -0
- package/dist/tests/syncServer.js.map +1 -1
- package/dist/tests/webSocket.integration.test.d.ts +2 -0
- package/dist/tests/webSocket.integration.test.d.ts.map +1 -0
- package/dist/tests/{integration.test.js → webSocket.integration.test.js} +28 -2
- package/dist/tests/webSocket.integration.test.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +24 -0
- package/dist/utils.js.map +1 -0
- package/package.json +2 -2
- package/src/BatchedOutgoingMessages.ts +124 -16
- package/src/createWebSocketPeer.ts +33 -118
- package/src/tests/createWebSocketPeer.test.ts +87 -37
- package/src/tests/syncServer.ts +1 -0
- package/src/tests/{integration.test.ts → webSocket.integration.test.ts} +37 -2
- package/src/utils.ts +30 -0
- package/dist/tests/BatchedOutgoingMessages.test.d.ts +0 -2
- package/dist/tests/BatchedOutgoingMessages.test.d.ts.map +0 -1
- package/dist/tests/BatchedOutgoingMessages.test.js +0 -112
- package/dist/tests/BatchedOutgoingMessages.test.js.map +0 -1
- package/dist/tests/integration.test.d.ts +0 -2
- package/dist/tests/integration.test.d.ts.map +0 -1
- package/dist/tests/integration.test.js.map +0 -1
- package/src/tests/BatchedOutgoingMessages.test.ts +0 -146
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webSocket.integration.test.d.ts","sourceRoot":"","sources":["../../src/tests/webSocket.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -67,7 +67,10 @@ describe("WebSocket Peer Integration", () => {
|
|
|
67
67
|
// Verify data reached the server
|
|
68
68
|
const serverNode = server.localNode;
|
|
69
69
|
const serverMap = await serverNode.load(map.id);
|
|
70
|
-
|
|
70
|
+
if (serverMap === "unavailable") {
|
|
71
|
+
throw new Error("Server map is unavailable");
|
|
72
|
+
}
|
|
73
|
+
expect(serverMap.get("testKey")?.toString()).toBe("testValue");
|
|
71
74
|
});
|
|
72
75
|
test("should handle disconnection and cleanup", async () => {
|
|
73
76
|
const clientAgent = crypto.newRandomAgentSecret();
|
|
@@ -113,5 +116,28 @@ describe("WebSocket Peer Integration", () => {
|
|
|
113
116
|
});
|
|
114
117
|
expect(ws.readyState).toBe(WebSocket.CLOSED);
|
|
115
118
|
});
|
|
119
|
+
test("calling terminate on the server should close the connection", async () => {
|
|
120
|
+
const ws = new WebSocket(syncServerUrl);
|
|
121
|
+
let disconnectCalled = false;
|
|
122
|
+
createWebSocketPeer({
|
|
123
|
+
id: "test-client",
|
|
124
|
+
websocket: ws,
|
|
125
|
+
role: "server",
|
|
126
|
+
onClose: () => {
|
|
127
|
+
disconnectCalled = true;
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(server.wss.clients.size).toBe(1);
|
|
132
|
+
});
|
|
133
|
+
const peerOnServer = server.localNode.syncManager.getPeers()[0];
|
|
134
|
+
for (const client of server.wss.clients) {
|
|
135
|
+
client.terminate();
|
|
136
|
+
}
|
|
137
|
+
await waitFor(() => {
|
|
138
|
+
expect(disconnectCalled).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
expect(peerOnServer?.closed).toBe(true);
|
|
141
|
+
});
|
|
116
142
|
});
|
|
117
|
-
//# sourceMappingURL=integration.test.js.map
|
|
143
|
+
//# sourceMappingURL=webSocket.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webSocket.integration.test.js","sourceRoot":"","sources":["../../src/tests/webSocket.integration.test.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AnyWebSocket } from "./types.js";
|
|
2
|
+
export declare const BUFFER_LIMIT = 100000;
|
|
3
|
+
export declare const BUFFER_LIMIT_POLLING_INTERVAL = 10;
|
|
4
|
+
export declare function isWebSocketOpen(websocket: AnyWebSocket): boolean;
|
|
5
|
+
export declare function hasWebSocketTooMuchBufferedData(websocket: AnyWebSocket): boolean;
|
|
6
|
+
export declare function waitForWebSocketOpen(websocket: AnyWebSocket): Promise<void>;
|
|
7
|
+
export declare function waitForWebSocketBufferedAmount(websocket: AnyWebSocket): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,eAAO,MAAM,YAAY,SAAU,CAAC;AACpC,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,wBAAgB,eAAe,CAAC,SAAS,EAAE,YAAY,WAEtD;AAED,wBAAgB,+BAA+B,CAAC,SAAS,EAAE,YAAY,WAEtE;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,YAAY,iBAQ3D;AAED,wBAAsB,8BAA8B,CAAC,SAAS,EAAE,YAAY,iBAM3E"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const BUFFER_LIMIT = 100000;
|
|
2
|
+
export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
|
|
3
|
+
export function isWebSocketOpen(websocket) {
|
|
4
|
+
return websocket.readyState === 1;
|
|
5
|
+
}
|
|
6
|
+
export function hasWebSocketTooMuchBufferedData(websocket) {
|
|
7
|
+
return websocket.bufferedAmount > BUFFER_LIMIT && isWebSocketOpen(websocket);
|
|
8
|
+
}
|
|
9
|
+
export function waitForWebSocketOpen(websocket) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
if (websocket.readyState === 1) {
|
|
12
|
+
resolve();
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
websocket.addEventListener("open", () => resolve(), { once: true });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export async function waitForWebSocketBufferedAmount(websocket) {
|
|
20
|
+
while (hasWebSocketTooMuchBufferedData(websocket)) {
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAG,MAAO,CAAC;AACpC,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAEhD,MAAM,UAAU,eAAe,CAAC,SAAuB;IACrD,OAAO,SAAS,CAAC,UAAU,KAAK,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,SAAuB;IACrE,OAAO,SAAS,CAAC,cAAc,GAAG,YAAY,IAAI,eAAe,CAAC,SAAS,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAuB;IAC1D,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,IAAI,SAAS,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,EAAE,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,SAAuB;IAC1E,OAAO,+BAA+B,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAClC,UAAU,CAAC,OAAO,EAAE,6BAA6B,CAAC,CACnD,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cojson-transport-ws",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.15.
|
|
4
|
+
"version": "0.15.9",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"cojson": "0.15.
|
|
9
|
+
"cojson": "0.15.9"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"typescript": "5.6.2",
|
|
@@ -1,21 +1,106 @@
|
|
|
1
|
-
import type { SyncMessage } from "cojson";
|
|
1
|
+
import type { DisconnectedError, SyncMessage } from "cojson";
|
|
2
|
+
import type { Peer } from "cojson";
|
|
3
|
+
import {
|
|
4
|
+
type CojsonInternalTypes,
|
|
5
|
+
PriorityBasedMessageQueue,
|
|
6
|
+
cojsonInternals,
|
|
7
|
+
logger,
|
|
8
|
+
} from "cojson";
|
|
2
9
|
import { addMessageToBacklog } from "./serialization.js";
|
|
10
|
+
import type { AnyWebSocket } from "./types.js";
|
|
11
|
+
import {
|
|
12
|
+
hasWebSocketTooMuchBufferedData,
|
|
13
|
+
isWebSocketOpen,
|
|
14
|
+
waitForWebSocketBufferedAmount,
|
|
15
|
+
waitForWebSocketOpen,
|
|
16
|
+
} from "./utils.js";
|
|
17
|
+
|
|
18
|
+
const { CO_VALUE_PRIORITY } = cojsonInternals;
|
|
3
19
|
|
|
4
20
|
export const MAX_OUTGOING_MESSAGES_CHUNK_BYTES = 25_000;
|
|
5
21
|
|
|
6
|
-
export class BatchedOutgoingMessages
|
|
22
|
+
export class BatchedOutgoingMessages
|
|
23
|
+
implements CojsonInternalTypes.OutgoingPeerChannel
|
|
24
|
+
{
|
|
7
25
|
private backlog = "";
|
|
8
|
-
private
|
|
26
|
+
private queue: PriorityBasedMessageQueue;
|
|
27
|
+
private processing = false;
|
|
28
|
+
private closed = false;
|
|
9
29
|
|
|
10
|
-
constructor(
|
|
30
|
+
constructor(
|
|
31
|
+
private websocket: AnyWebSocket,
|
|
32
|
+
private batching: boolean,
|
|
33
|
+
peerRole: Peer["role"],
|
|
34
|
+
) {
|
|
35
|
+
this.queue = new PriorityBasedMessageQueue(
|
|
36
|
+
CO_VALUE_PRIORITY.HIGH,
|
|
37
|
+
"outgoing",
|
|
38
|
+
{
|
|
39
|
+
peerRole: peerRole,
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
}
|
|
11
43
|
|
|
12
|
-
push(msg: SyncMessage) {
|
|
13
|
-
|
|
44
|
+
push(msg: SyncMessage | DisconnectedError) {
|
|
45
|
+
if (msg === "Disconnected") {
|
|
46
|
+
this.close();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.queue.push(msg);
|
|
14
51
|
|
|
15
|
-
if (this.
|
|
16
|
-
|
|
52
|
+
if (this.processing) {
|
|
53
|
+
return;
|
|
17
54
|
}
|
|
18
55
|
|
|
56
|
+
this.processQueue().catch((e) => {
|
|
57
|
+
logger.error("Error while processing sendMessage queue", { err: e });
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async processQueue() {
|
|
62
|
+
const { websocket } = this;
|
|
63
|
+
|
|
64
|
+
this.processing = true;
|
|
65
|
+
|
|
66
|
+
// Delay the initiation of the queue processing to accumulate messages
|
|
67
|
+
// before sending them, in order to do prioritization and batching
|
|
68
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 5));
|
|
69
|
+
|
|
70
|
+
let msg = this.queue.pull();
|
|
71
|
+
|
|
72
|
+
while (msg) {
|
|
73
|
+
if (this.closed) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!isWebSocketOpen(websocket)) {
|
|
78
|
+
await waitForWebSocketOpen(websocket);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (hasWebSocketTooMuchBufferedData(websocket)) {
|
|
82
|
+
await waitForWebSocketBufferedAmount(websocket);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isWebSocketOpen(websocket)) {
|
|
86
|
+
this.processMessage(msg);
|
|
87
|
+
|
|
88
|
+
msg = this.queue.pull();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.sendMessagesInBulk();
|
|
93
|
+
this.processing = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
processMessage(msg: SyncMessage) {
|
|
97
|
+
if (!this.batching) {
|
|
98
|
+
this.websocket.send(JSON.stringify(msg));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const payload = addMessageToBacklog(this.backlog, msg);
|
|
103
|
+
|
|
19
104
|
const maxChunkSizeReached =
|
|
20
105
|
payload.length >= MAX_OUTGOING_MESSAGES_CHUNK_BYTES;
|
|
21
106
|
const backlogExists = this.backlog.length > 0;
|
|
@@ -23,26 +108,49 @@ export class BatchedOutgoingMessages {
|
|
|
23
108
|
if (maxChunkSizeReached && backlogExists) {
|
|
24
109
|
this.sendMessagesInBulk();
|
|
25
110
|
this.backlog = addMessageToBacklog("", msg);
|
|
26
|
-
this.timeout = setTimeout(() => {
|
|
27
|
-
this.sendMessagesInBulk();
|
|
28
|
-
}, 0);
|
|
29
111
|
} else if (maxChunkSizeReached) {
|
|
30
112
|
this.backlog = payload;
|
|
31
113
|
this.sendMessagesInBulk();
|
|
32
114
|
} else {
|
|
33
115
|
this.backlog = payload;
|
|
34
|
-
this.timeout = setTimeout(() => {
|
|
35
|
-
this.sendMessagesInBulk();
|
|
36
|
-
}, 0);
|
|
37
116
|
}
|
|
38
117
|
}
|
|
39
118
|
|
|
40
119
|
sendMessagesInBulk() {
|
|
41
|
-
this.
|
|
42
|
-
|
|
120
|
+
if (this.backlog.length > 0 && isWebSocketOpen(this.websocket)) {
|
|
121
|
+
this.websocket.send(this.backlog);
|
|
122
|
+
this.backlog = "";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setBatching(enabled: boolean) {
|
|
127
|
+
this.batching = enabled;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private closeListeners = new Set<() => void>();
|
|
131
|
+
onClose(callback: () => void) {
|
|
132
|
+
this.closeListeners.add(callback);
|
|
43
133
|
}
|
|
44
134
|
|
|
45
135
|
close() {
|
|
136
|
+
if (this.closed) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let msg = this.queue.pull();
|
|
141
|
+
|
|
142
|
+
while (msg) {
|
|
143
|
+
this.processMessage(msg);
|
|
144
|
+
msg = this.queue.pull();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.closed = true;
|
|
46
148
|
this.sendMessagesInBulk();
|
|
149
|
+
|
|
150
|
+
for (const listener of this.closeListeners) {
|
|
151
|
+
listener();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.closeListeners.clear();
|
|
47
155
|
}
|
|
48
156
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type DisconnectedError,
|
|
3
|
-
type Peer,
|
|
4
|
-
type PingTimeoutError,
|
|
5
|
-
type SyncMessage,
|
|
6
|
-
cojsonInternals,
|
|
7
|
-
logger,
|
|
8
|
-
} from "cojson";
|
|
1
|
+
import { type Peer, type SyncMessage, cojsonInternals, logger } from "cojson";
|
|
9
2
|
import { BatchedOutgoingMessages } from "./BatchedOutgoingMessages.js";
|
|
10
3
|
import { deserializeMessages } from "./serialization.js";
|
|
11
4
|
import type { AnyWebSocket } from "./types.js";
|
|
12
5
|
|
|
13
|
-
|
|
14
|
-
export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
|
|
6
|
+
const { ConnectedPeerChannel } = cojsonInternals;
|
|
15
7
|
|
|
16
8
|
export type CreateWebSocketPeerOpts = {
|
|
17
9
|
id: string;
|
|
@@ -52,70 +44,6 @@ function createPingTimeoutListener(
|
|
|
52
44
|
};
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
function waitForWebSocketOpen(websocket: AnyWebSocket) {
|
|
56
|
-
return new Promise<void>((resolve) => {
|
|
57
|
-
if (websocket.readyState === 1) {
|
|
58
|
-
resolve();
|
|
59
|
-
} else {
|
|
60
|
-
websocket.addEventListener("open", () => resolve(), { once: true });
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function createOutgoingMessagesManager(
|
|
66
|
-
websocket: AnyWebSocket,
|
|
67
|
-
batchingByDefault: boolean,
|
|
68
|
-
) {
|
|
69
|
-
let closed = false;
|
|
70
|
-
const outgoingMessages = new BatchedOutgoingMessages((messages) => {
|
|
71
|
-
if (websocket.readyState === 1) {
|
|
72
|
-
websocket.send(messages);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
let batchingEnabled = batchingByDefault;
|
|
77
|
-
|
|
78
|
-
async function sendMessage(msg: SyncMessage) {
|
|
79
|
-
if (closed) {
|
|
80
|
-
return Promise.reject(new Error("WebSocket closed"));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (websocket.readyState !== 1) {
|
|
84
|
-
await waitForWebSocketOpen(websocket);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
while (
|
|
88
|
-
websocket.bufferedAmount > BUFFER_LIMIT &&
|
|
89
|
-
websocket.readyState === 1
|
|
90
|
-
) {
|
|
91
|
-
await new Promise<void>((resolve) =>
|
|
92
|
-
setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL),
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (websocket.readyState !== 1) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (!batchingEnabled) {
|
|
101
|
-
websocket.send(JSON.stringify(msg));
|
|
102
|
-
} else {
|
|
103
|
-
outgoingMessages.push(msg);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
sendMessage,
|
|
109
|
-
setBatchingEnabled(enabled: boolean) {
|
|
110
|
-
batchingEnabled = enabled;
|
|
111
|
-
},
|
|
112
|
-
close() {
|
|
113
|
-
closed = true;
|
|
114
|
-
outgoingMessages.close();
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
47
|
function createClosedEventEmitter(callback = () => {}) {
|
|
120
48
|
let disconnected = false;
|
|
121
49
|
|
|
@@ -137,17 +65,11 @@ export function createWebSocketPeer({
|
|
|
137
65
|
onSuccess,
|
|
138
66
|
onClose,
|
|
139
67
|
}: CreateWebSocketPeerOpts): Peer {
|
|
140
|
-
const incoming = new
|
|
141
|
-
SyncMessage | DisconnectedError | PingTimeoutError
|
|
142
|
-
>();
|
|
68
|
+
const incoming = new ConnectedPeerChannel();
|
|
143
69
|
const emitClosedEvent = createClosedEventEmitter(onClose);
|
|
144
70
|
|
|
145
71
|
function handleClose() {
|
|
146
|
-
incoming
|
|
147
|
-
.push("Disconnected")
|
|
148
|
-
.catch((e) =>
|
|
149
|
-
logger.error("Error while pushing disconnect msg", { err: e }),
|
|
150
|
-
);
|
|
72
|
+
incoming.push("Disconnected");
|
|
151
73
|
emitClosedEvent();
|
|
152
74
|
}
|
|
153
75
|
|
|
@@ -166,18 +88,19 @@ export function createWebSocketPeer({
|
|
|
166
88
|
expectPings,
|
|
167
89
|
pingTimeout,
|
|
168
90
|
() => {
|
|
169
|
-
incoming
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
91
|
+
incoming.push("Disconnected");
|
|
92
|
+
logger.error("Ping timeout from peer", {
|
|
93
|
+
peerId: id,
|
|
94
|
+
peerRole: role,
|
|
95
|
+
});
|
|
174
96
|
emitClosedEvent();
|
|
175
97
|
},
|
|
176
98
|
);
|
|
177
99
|
|
|
178
|
-
const
|
|
100
|
+
const outgoing = new BatchedOutgoingMessages(
|
|
179
101
|
websocket,
|
|
180
102
|
batchingByDefault,
|
|
103
|
+
role,
|
|
181
104
|
);
|
|
182
105
|
let isFirstMessage = true;
|
|
183
106
|
|
|
@@ -206,50 +129,42 @@ export function createWebSocketPeer({
|
|
|
206
129
|
|
|
207
130
|
if (messages.length > 1) {
|
|
208
131
|
// If more than one message is received, the other peer supports batching
|
|
209
|
-
|
|
132
|
+
outgoing.setBatching(true);
|
|
210
133
|
}
|
|
211
134
|
|
|
212
135
|
for (const msg of messages) {
|
|
213
136
|
if (msg && "action" in msg) {
|
|
214
|
-
incoming
|
|
215
|
-
.push(msg)
|
|
216
|
-
.catch((e) =>
|
|
217
|
-
logger.error("Error while pushing incoming msg", { err: e }),
|
|
218
|
-
);
|
|
137
|
+
incoming.push(msg);
|
|
219
138
|
}
|
|
220
139
|
}
|
|
221
140
|
}
|
|
222
141
|
|
|
223
142
|
websocket.addEventListener("message", handleIncomingMsg);
|
|
224
143
|
|
|
144
|
+
outgoing.onClose(() => {
|
|
145
|
+
websocket.removeEventListener("message", handleIncomingMsg);
|
|
146
|
+
websocket.removeEventListener("close", handleClose);
|
|
147
|
+
pingTimeoutListener.clear();
|
|
148
|
+
emitClosedEvent();
|
|
149
|
+
|
|
150
|
+
if (websocket.readyState === 0) {
|
|
151
|
+
websocket.addEventListener(
|
|
152
|
+
"open",
|
|
153
|
+
function handleClose() {
|
|
154
|
+
websocket.close();
|
|
155
|
+
},
|
|
156
|
+
{ once: true },
|
|
157
|
+
);
|
|
158
|
+
} else if (websocket.readyState === 1) {
|
|
159
|
+
websocket.close();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
225
163
|
return {
|
|
226
164
|
id,
|
|
227
165
|
incoming,
|
|
228
|
-
outgoing
|
|
229
|
-
push: outgoingMessages.sendMessage,
|
|
230
|
-
close() {
|
|
231
|
-
outgoingMessages.close();
|
|
232
|
-
|
|
233
|
-
websocket.removeEventListener("message", handleIncomingMsg);
|
|
234
|
-
websocket.removeEventListener("close", handleClose);
|
|
235
|
-
pingTimeoutListener.clear();
|
|
236
|
-
emitClosedEvent();
|
|
237
|
-
|
|
238
|
-
if (websocket.readyState === 0) {
|
|
239
|
-
websocket.addEventListener(
|
|
240
|
-
"open",
|
|
241
|
-
function handleClose() {
|
|
242
|
-
websocket.close();
|
|
243
|
-
},
|
|
244
|
-
{ once: true },
|
|
245
|
-
);
|
|
246
|
-
} else if (websocket.readyState === 1) {
|
|
247
|
-
websocket.close();
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
},
|
|
166
|
+
outgoing,
|
|
251
167
|
role,
|
|
252
|
-
crashOnClose: false,
|
|
253
168
|
deletePeerStateOnClose,
|
|
254
169
|
};
|
|
255
170
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import type { SyncMessage } from "cojson";
|
|
2
|
-
import
|
|
1
|
+
import type { CojsonInternalTypes, SyncMessage } from "cojson";
|
|
2
|
+
import { cojsonInternals } from "cojson";
|
|
3
3
|
import { type Mocked, describe, expect, test, vi } from "vitest";
|
|
4
4
|
import { MAX_OUTGOING_MESSAGES_CHUNK_BYTES } from "../BatchedOutgoingMessages.js";
|
|
5
5
|
import {
|
|
6
|
-
BUFFER_LIMIT,
|
|
7
|
-
BUFFER_LIMIT_POLLING_INTERVAL,
|
|
8
6
|
type CreateWebSocketPeerOpts,
|
|
9
7
|
createWebSocketPeer,
|
|
10
8
|
} from "../createWebSocketPeer.js";
|
|
11
9
|
import type { AnyWebSocket } from "../types.js";
|
|
10
|
+
import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL } from "../utils.js";
|
|
11
|
+
|
|
12
|
+
const { CO_VALUE_PRIORITY } = cojsonInternals;
|
|
12
13
|
|
|
13
14
|
function setup(opts: Partial<CreateWebSocketPeerOpts> = {}) {
|
|
14
15
|
const listeners = new Map<string, (event: MessageEvent) => void>();
|
|
@@ -48,34 +49,28 @@ describe("createWebSocketPeer", () => {
|
|
|
48
49
|
expect(peer).toHaveProperty("incoming");
|
|
49
50
|
expect(peer).toHaveProperty("outgoing");
|
|
50
51
|
expect(peer).toHaveProperty("role", "client");
|
|
51
|
-
expect(peer).toHaveProperty("crashOnClose", false);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
test("should handle disconnection", async () => {
|
|
55
|
-
expect.assertions(1);
|
|
56
|
-
|
|
57
55
|
const { listeners, peer } = setup();
|
|
58
56
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
>;
|
|
62
|
-
const pushSpy = vi.spyOn(incoming, "push");
|
|
57
|
+
const onMessageSpy = vi.fn();
|
|
58
|
+
peer.incoming.onMessage(onMessageSpy);
|
|
63
59
|
|
|
64
60
|
const closeHandler = listeners.get("close");
|
|
65
61
|
|
|
66
62
|
closeHandler?.(new MessageEvent("close"));
|
|
67
63
|
|
|
68
|
-
expect(
|
|
64
|
+
expect(onMessageSpy).toHaveBeenCalledWith("Disconnected");
|
|
69
65
|
});
|
|
70
66
|
|
|
71
67
|
test("should handle ping timeout", async () => {
|
|
72
68
|
vi.useFakeTimers();
|
|
73
69
|
const { listeners, peer } = setup();
|
|
74
70
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const pushSpy = vi.spyOn(incoming, "push");
|
|
71
|
+
const onMessageSpy = vi.fn();
|
|
72
|
+
|
|
73
|
+
peer.incoming.onMessage(onMessageSpy);
|
|
79
74
|
|
|
80
75
|
const messageHandler = listeners.get("message");
|
|
81
76
|
|
|
@@ -83,7 +78,7 @@ describe("createWebSocketPeer", () => {
|
|
|
83
78
|
|
|
84
79
|
await vi.advanceTimersByTimeAsync(10_000);
|
|
85
80
|
|
|
86
|
-
expect(
|
|
81
|
+
expect(onMessageSpy).toHaveBeenCalledWith("Disconnected");
|
|
87
82
|
|
|
88
83
|
vi.useRealTimers();
|
|
89
84
|
});
|
|
@@ -97,15 +92,14 @@ describe("createWebSocketPeer", () => {
|
|
|
97
92
|
header: false,
|
|
98
93
|
sessions: {},
|
|
99
94
|
};
|
|
100
|
-
|
|
95
|
+
|
|
96
|
+
peer.outgoing.push(testMessage);
|
|
101
97
|
|
|
102
98
|
await waitFor(() => {
|
|
103
99
|
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
104
100
|
JSON.stringify(testMessage),
|
|
105
101
|
);
|
|
106
102
|
});
|
|
107
|
-
|
|
108
|
-
await expect(promise).resolves.toBeUndefined();
|
|
109
103
|
});
|
|
110
104
|
|
|
111
105
|
test("should stop sending messages when the websocket is closed", async () => {
|
|
@@ -153,23 +147,6 @@ describe("createWebSocketPeer", () => {
|
|
|
153
147
|
expect(mockWebSocket.close).toHaveBeenCalled();
|
|
154
148
|
});
|
|
155
149
|
|
|
156
|
-
test("should return a rejection if a message is sent after the peer is closed", async () => {
|
|
157
|
-
const { peer } = setup();
|
|
158
|
-
|
|
159
|
-
peer.outgoing.close();
|
|
160
|
-
|
|
161
|
-
const message: SyncMessage = {
|
|
162
|
-
action: "known",
|
|
163
|
-
id: "co_ztest",
|
|
164
|
-
header: false,
|
|
165
|
-
sessions: {},
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
await expect(peer.outgoing.push(message)).rejects.toThrow(
|
|
169
|
-
"WebSocket closed",
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
150
|
test("should call onSuccess handler after receiving first message", () => {
|
|
174
151
|
const onSuccess = vi.fn();
|
|
175
152
|
const { listeners } = setup({ onSuccess });
|
|
@@ -229,6 +206,42 @@ describe("createWebSocketPeer", () => {
|
|
|
229
206
|
);
|
|
230
207
|
});
|
|
231
208
|
|
|
209
|
+
test("should sort outgoing messages by priority", async () => {
|
|
210
|
+
const { peer, mockWebSocket } = setup();
|
|
211
|
+
|
|
212
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
213
|
+
mockWebSocket.readyState = 0;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const message1: SyncMessage = {
|
|
217
|
+
action: "content",
|
|
218
|
+
id: "co_zlow",
|
|
219
|
+
new: {},
|
|
220
|
+
priority: CO_VALUE_PRIORITY.LOW,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const message2: SyncMessage = {
|
|
224
|
+
action: "content",
|
|
225
|
+
id: "co_zhigh",
|
|
226
|
+
new: {},
|
|
227
|
+
priority: CO_VALUE_PRIORITY.HIGH,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
void peer.outgoing.push(message1);
|
|
231
|
+
void peer.outgoing.push(message2);
|
|
232
|
+
void peer.outgoing.push(message2);
|
|
233
|
+
|
|
234
|
+
await waitFor(() => {
|
|
235
|
+
expect(mockWebSocket.send).toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(
|
|
239
|
+
[message2, message2, message1]
|
|
240
|
+
.map((msg) => JSON.stringify(msg))
|
|
241
|
+
.join("\n"),
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
232
245
|
test("should send all the pending messages when the websocket is closed", async () => {
|
|
233
246
|
const { peer, mockWebSocket } = setup();
|
|
234
247
|
|
|
@@ -298,6 +311,43 @@ describe("createWebSocketPeer", () => {
|
|
|
298
311
|
);
|
|
299
312
|
});
|
|
300
313
|
|
|
314
|
+
test("should send accumulated messages before a large message", async () => {
|
|
315
|
+
const { peer, mockWebSocket } = setup();
|
|
316
|
+
|
|
317
|
+
const smallMessage: SyncMessage = {
|
|
318
|
+
action: "known",
|
|
319
|
+
id: "co_z_small",
|
|
320
|
+
header: false,
|
|
321
|
+
sessions: {},
|
|
322
|
+
};
|
|
323
|
+
const largeMessage: SyncMessage = {
|
|
324
|
+
action: "known",
|
|
325
|
+
id: "co_z_large",
|
|
326
|
+
header: false,
|
|
327
|
+
sessions: {
|
|
328
|
+
// Add a large payload to exceed MAX_OUTGOING_MESSAGES_CHUNK_BYTES
|
|
329
|
+
payload: "x".repeat(MAX_OUTGOING_MESSAGES_CHUNK_BYTES),
|
|
330
|
+
} as CojsonInternalTypes.CoValueKnownState["sessions"],
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
void peer.outgoing.push(smallMessage);
|
|
334
|
+
void peer.outgoing.push(largeMessage);
|
|
335
|
+
|
|
336
|
+
await waitFor(() => {
|
|
337
|
+
expect(mockWebSocket.send).toHaveBeenCalledTimes(2);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
expect(mockWebSocket.send).toHaveBeenCalledTimes(2);
|
|
341
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
342
|
+
1,
|
|
343
|
+
JSON.stringify(smallMessage),
|
|
344
|
+
);
|
|
345
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(
|
|
346
|
+
2,
|
|
347
|
+
JSON.stringify(largeMessage),
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
301
351
|
test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
|
|
302
352
|
vi.useFakeTimers();
|
|
303
353
|
const { peer, mockWebSocket } = setup();
|