cojson-transport-ws 0.15.8 → 0.15.10
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 +14 -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.10",
|
|
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.10"
|
|
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
|
}
|