cojson-transport-ws 0.7.35-unique.2 → 0.8.0
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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +13 -3
- package/dist/createWebSocketPeer.test.js +108 -0
- package/dist/createWebSocketPeer.test.js.map +1 -0
- package/dist/index.js +15 -13
- package/dist/index.js.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +3 -3
- package/src/createWebSocketPeer.test.ts +166 -0
- package/src/index.ts +31 -46
- package/src/types.ts +28 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
> cojson-transport-ws@0.7.
|
|
2
|
+
> cojson-transport-ws@0.7.35-guest-auth.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.7.
|
|
6
|
+
> cojson-transport-ws@0.7.35-guest-auth.5 lint
|
|
7
7
|
> eslint . --ext ts,tsx
|
|
8
8
|
|
package/.turbo/turbo-lint.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
# cojson-transport-nodejs-ws
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.8.0
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
- Updated dependencies
|
|
8
|
-
|
|
7
|
+
- Updated dependencies [6a147c2]
|
|
8
|
+
- Updated dependencies [ad40b88]
|
|
9
|
+
- cojson@0.8.0
|
|
10
|
+
|
|
11
|
+
## 0.7.35
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- f350e90: Added a priority system for the sync messages
|
|
16
|
+
- Updated dependencies [35bbcd9]
|
|
17
|
+
- Updated dependencies [f350e90]
|
|
18
|
+
- cojson@0.7.35
|
|
9
19
|
|
|
10
20
|
## 0.7.34
|
|
11
21
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from "vitest";
|
|
2
|
+
import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL, createWebSocketPeer } from "./index.js";
|
|
3
|
+
const g = globalThis;
|
|
4
|
+
function setup() {
|
|
5
|
+
const listeners = new Map();
|
|
6
|
+
const mockWebSocket = {
|
|
7
|
+
readyState: 1,
|
|
8
|
+
addEventListener: vi.fn().mockImplementation((type, listener) => {
|
|
9
|
+
listeners.set(type, listener);
|
|
10
|
+
}),
|
|
11
|
+
close: vi.fn(),
|
|
12
|
+
send: vi.fn(),
|
|
13
|
+
};
|
|
14
|
+
const peer = createWebSocketPeer({
|
|
15
|
+
id: "test-peer",
|
|
16
|
+
websocket: mockWebSocket,
|
|
17
|
+
role: "client",
|
|
18
|
+
});
|
|
19
|
+
return { mockWebSocket, peer, listeners };
|
|
20
|
+
}
|
|
21
|
+
describe("createWebSocketPeer", () => {
|
|
22
|
+
test("should create a peer with correct properties", () => {
|
|
23
|
+
const { peer } = setup();
|
|
24
|
+
expect(peer).toHaveProperty("id", "test-peer");
|
|
25
|
+
expect(peer).toHaveProperty("incoming");
|
|
26
|
+
expect(peer).toHaveProperty("outgoing");
|
|
27
|
+
expect(peer).toHaveProperty("role", "client");
|
|
28
|
+
expect(peer).toHaveProperty("crashOnClose", false);
|
|
29
|
+
});
|
|
30
|
+
test("should handle ping messages", async () => {
|
|
31
|
+
const { listeners } = setup();
|
|
32
|
+
const pingMessage = { type: "ping", time: Date.now(), dc: "test-dc" };
|
|
33
|
+
const messageEvent = new MessageEvent("message", { data: JSON.stringify(pingMessage) });
|
|
34
|
+
const messageHandler = listeners.get("message");
|
|
35
|
+
messageHandler?.(messageEvent);
|
|
36
|
+
// Check if the ping was recorded in the global jazzPings array
|
|
37
|
+
expect(g.jazzPings?.length).toBeGreaterThan(0);
|
|
38
|
+
expect(g.jazzPings?.at(-1)).toMatchObject({
|
|
39
|
+
sent: pingMessage.time,
|
|
40
|
+
dc: pingMessage.dc,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
test("should handle disconnection", async () => {
|
|
44
|
+
expect.assertions(1);
|
|
45
|
+
const { listeners, peer } = setup();
|
|
46
|
+
const incoming = peer.incoming;
|
|
47
|
+
const pushSpy = vi.spyOn(incoming, "push");
|
|
48
|
+
const closeHandler = listeners.get("close");
|
|
49
|
+
closeHandler?.(new MessageEvent("close"));
|
|
50
|
+
expect(pushSpy).toHaveBeenCalledWith("Disconnected");
|
|
51
|
+
});
|
|
52
|
+
test("should handle ping timeout", async () => {
|
|
53
|
+
vi.useFakeTimers();
|
|
54
|
+
const { listeners, peer } = setup();
|
|
55
|
+
const incoming = peer.incoming;
|
|
56
|
+
const pushSpy = vi.spyOn(incoming, "push");
|
|
57
|
+
const messageHandler = listeners.get("message");
|
|
58
|
+
messageHandler?.(new MessageEvent("message", { data: "{}" }));
|
|
59
|
+
await vi.advanceTimersByTimeAsync(10000);
|
|
60
|
+
expect(pushSpy).toHaveBeenCalledWith("PingTimeout");
|
|
61
|
+
vi.useRealTimers();
|
|
62
|
+
});
|
|
63
|
+
test("should send outgoing messages", async () => {
|
|
64
|
+
const { peer, mockWebSocket } = setup();
|
|
65
|
+
const testMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
|
|
66
|
+
const promise = peer.outgoing.push(testMessage);
|
|
67
|
+
await new Promise(queueMicrotask);
|
|
68
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(testMessage));
|
|
69
|
+
await expect(promise).resolves.toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
test("should stop sending messages when the websocket is closed", async () => {
|
|
72
|
+
const { peer, mockWebSocket } = setup();
|
|
73
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
74
|
+
mockWebSocket.readyState = 0;
|
|
75
|
+
});
|
|
76
|
+
const message1 = { action: "known", id: "co_ztest", header: false, sessions: {} };
|
|
77
|
+
const message2 = { action: "content", id: "co_zlow", new: {}, priority: 1 };
|
|
78
|
+
void peer.outgoing.push(message1);
|
|
79
|
+
void peer.outgoing.push(message2);
|
|
80
|
+
await new Promise(queueMicrotask);
|
|
81
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
|
|
82
|
+
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
|
|
83
|
+
});
|
|
84
|
+
test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
|
|
85
|
+
vi.useFakeTimers();
|
|
86
|
+
const { peer, mockWebSocket } = setup();
|
|
87
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
88
|
+
mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
|
|
89
|
+
});
|
|
90
|
+
const message1 = { action: "known", id: "co_ztest", header: false, sessions: {} };
|
|
91
|
+
const message2 = { action: "content", id: "co_zlow", new: {}, priority: 1 };
|
|
92
|
+
void peer.outgoing.push(message1);
|
|
93
|
+
void peer.outgoing.push(message2);
|
|
94
|
+
await new Promise(queueMicrotask);
|
|
95
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
|
|
96
|
+
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
|
|
97
|
+
mockWebSocket.bufferedAmount = 0;
|
|
98
|
+
await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
|
|
99
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
|
|
100
|
+
vi.useRealTimers();
|
|
101
|
+
});
|
|
102
|
+
test("should close the websocket connection", () => {
|
|
103
|
+
const { mockWebSocket, peer } = setup();
|
|
104
|
+
peer.outgoing.close();
|
|
105
|
+
expect(mockWebSocket.close).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=createWebSocketPeer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createWebSocketPeer.test.js","sourceRoot":"","sources":["../src/createWebSocketPeer.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cojsonInternals, } from "cojson";
|
|
2
2
|
const g = globalThis;
|
|
3
|
+
export const BUFFER_LIMIT = 100000;
|
|
4
|
+
export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
|
|
3
5
|
export function createWebSocketPeer({ id, websocket, role, expectPings = true, }) {
|
|
4
6
|
const incoming = new cojsonInternals.Channel();
|
|
5
7
|
websocket.addEventListener("close", function handleClose() {
|
|
@@ -41,23 +43,23 @@ export function createWebSocketPeer({ id, websocket, role, expectPings = true, }
|
|
|
41
43
|
websocket.addEventListener("open", resolve, { once: true });
|
|
42
44
|
}
|
|
43
45
|
});
|
|
46
|
+
async function pushMessage(msg) {
|
|
47
|
+
if (websocket.readyState !== 1) {
|
|
48
|
+
await websocketOpen;
|
|
49
|
+
}
|
|
50
|
+
while (websocket.bufferedAmount > BUFFER_LIMIT && websocket.readyState === 1) {
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL));
|
|
52
|
+
}
|
|
53
|
+
if (websocket.readyState !== 1) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
websocket.send(JSON.stringify(msg));
|
|
57
|
+
}
|
|
44
58
|
return {
|
|
45
59
|
id,
|
|
46
60
|
incoming,
|
|
47
61
|
outgoing: {
|
|
48
|
-
|
|
49
|
-
await websocketOpen;
|
|
50
|
-
if (websocket.readyState === 1) {
|
|
51
|
-
while (websocket.bufferedAmount > 1000000) {
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
53
|
-
if (websocket.readyState !== 1) {
|
|
54
|
-
console.log("WebSocket closed while buffering", id, websocket.bufferedAmount);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
websocket.send(JSON.stringify(msg));
|
|
59
|
-
}
|
|
60
|
-
},
|
|
62
|
+
push: pushMessage,
|
|
61
63
|
close() {
|
|
62
64
|
console.log("Trying to close", id, websocket.readyState);
|
|
63
65
|
if (websocket.readyState === 0) {
|
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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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cojson-transport-ws",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"cojson": "0.8.0",
|
|
10
|
+
"typescript": "^5.3.3"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@types/ws": "^8.5.5"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, test, expect, vi, Mocked } from "vitest";
|
|
2
|
+
import { BUFFER_LIMIT, BUFFER_LIMIT_POLLING_INTERVAL, createWebSocketPeer } from "./index.js";
|
|
3
|
+
import { AnyWebSocket, PingMsg } from "./types.js";
|
|
4
|
+
import { SyncMessage } from "cojson";
|
|
5
|
+
import { Channel } from "cojson/src/streamUtils";
|
|
6
|
+
|
|
7
|
+
const g: typeof globalThis & {
|
|
8
|
+
jazzPings?: {
|
|
9
|
+
received: number;
|
|
10
|
+
sent: number;
|
|
11
|
+
dc: string;
|
|
12
|
+
}[];
|
|
13
|
+
} = globalThis;
|
|
14
|
+
|
|
15
|
+
function setup() {
|
|
16
|
+
const listeners = new Map<string, (event: MessageEvent) => void>();
|
|
17
|
+
|
|
18
|
+
const mockWebSocket = {
|
|
19
|
+
readyState: 1,
|
|
20
|
+
addEventListener: vi.fn().mockImplementation((type, listener) => {
|
|
21
|
+
listeners.set(type, listener);
|
|
22
|
+
}),
|
|
23
|
+
close: vi.fn(),
|
|
24
|
+
send: vi.fn(),
|
|
25
|
+
} as unknown as Mocked<AnyWebSocket>;
|
|
26
|
+
|
|
27
|
+
const peer = createWebSocketPeer({
|
|
28
|
+
id: "test-peer",
|
|
29
|
+
websocket: mockWebSocket,
|
|
30
|
+
role: "client",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return { mockWebSocket, peer, listeners };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("createWebSocketPeer", () => {
|
|
37
|
+
test("should create a peer with correct properties", () => {
|
|
38
|
+
const { peer } = setup();
|
|
39
|
+
|
|
40
|
+
expect(peer).toHaveProperty("id", "test-peer");
|
|
41
|
+
expect(peer).toHaveProperty("incoming");
|
|
42
|
+
expect(peer).toHaveProperty("outgoing");
|
|
43
|
+
expect(peer).toHaveProperty("role", "client");
|
|
44
|
+
expect(peer).toHaveProperty("crashOnClose", false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("should handle ping messages", async () => {
|
|
48
|
+
const { listeners } = setup();
|
|
49
|
+
|
|
50
|
+
const pingMessage: PingMsg = { type: "ping", time: Date.now(), dc: "test-dc" };
|
|
51
|
+
const messageEvent = new MessageEvent("message", { data: JSON.stringify(pingMessage) });
|
|
52
|
+
|
|
53
|
+
const messageHandler = listeners.get("message");
|
|
54
|
+
|
|
55
|
+
messageHandler?.(messageEvent);
|
|
56
|
+
|
|
57
|
+
// Check if the ping was recorded in the global jazzPings array
|
|
58
|
+
expect(g.jazzPings?.length).toBeGreaterThan(0);
|
|
59
|
+
expect(g.jazzPings?.at(-1)).toMatchObject({
|
|
60
|
+
sent: pingMessage.time,
|
|
61
|
+
dc: pingMessage.dc,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should handle disconnection", async () => {
|
|
66
|
+
expect.assertions(1);
|
|
67
|
+
|
|
68
|
+
const { listeners, peer } = setup();
|
|
69
|
+
|
|
70
|
+
const incoming = peer.incoming as Channel<SyncMessage | "Disconnected" | "PingTimeout">;
|
|
71
|
+
const pushSpy = vi.spyOn(incoming, "push");
|
|
72
|
+
|
|
73
|
+
const closeHandler = listeners.get("close");
|
|
74
|
+
|
|
75
|
+
closeHandler?.(new MessageEvent("close"));
|
|
76
|
+
|
|
77
|
+
expect(pushSpy).toHaveBeenCalledWith("Disconnected");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("should handle ping timeout", async () => {
|
|
81
|
+
vi.useFakeTimers();
|
|
82
|
+
const { listeners, peer } = setup();
|
|
83
|
+
|
|
84
|
+
const incoming = peer.incoming as Channel<SyncMessage | "Disconnected" | "PingTimeout">;
|
|
85
|
+
const pushSpy = vi.spyOn(incoming, "push");
|
|
86
|
+
|
|
87
|
+
const messageHandler = listeners.get("message");
|
|
88
|
+
|
|
89
|
+
messageHandler?.(new MessageEvent("message", { data: "{}" }));
|
|
90
|
+
|
|
91
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
92
|
+
|
|
93
|
+
expect(pushSpy).toHaveBeenCalledWith("PingTimeout");
|
|
94
|
+
|
|
95
|
+
vi.useRealTimers();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("should send outgoing messages", async () => {
|
|
99
|
+
const { peer, mockWebSocket } = setup();
|
|
100
|
+
|
|
101
|
+
const testMessage: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
|
|
102
|
+
const promise = peer.outgoing.push(testMessage);
|
|
103
|
+
|
|
104
|
+
await new Promise<void>(queueMicrotask);
|
|
105
|
+
|
|
106
|
+
expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(testMessage));
|
|
107
|
+
await expect(promise).resolves.toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
test("should stop sending messages when the websocket is closed", async () => {
|
|
113
|
+
const { peer, mockWebSocket } = setup();
|
|
114
|
+
|
|
115
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
116
|
+
mockWebSocket.readyState = 0;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const message1: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
|
|
120
|
+
const message2: SyncMessage = { action: "content", id: "co_zlow", new: {}, priority: 1 };
|
|
121
|
+
|
|
122
|
+
void peer.outgoing.push(message1);
|
|
123
|
+
void peer.outgoing.push(message2);
|
|
124
|
+
|
|
125
|
+
await new Promise<void>(queueMicrotask);
|
|
126
|
+
|
|
127
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
|
|
128
|
+
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("should wait for the buffer to be under BUFFER_LIMIT before sending more messages", async () => {
|
|
132
|
+
vi.useFakeTimers();
|
|
133
|
+
const { peer, mockWebSocket } = setup();
|
|
134
|
+
|
|
135
|
+
mockWebSocket.send.mockImplementation(() => {
|
|
136
|
+
mockWebSocket.bufferedAmount = BUFFER_LIMIT + 1;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const message1: SyncMessage = { action: "known", id: "co_ztest", header: false, sessions: {} };
|
|
140
|
+
const message2: SyncMessage = { action: "content", id: "co_zlow", new: {}, priority: 1 };
|
|
141
|
+
|
|
142
|
+
void peer.outgoing.push(message1);
|
|
143
|
+
void peer.outgoing.push(message2);
|
|
144
|
+
|
|
145
|
+
await new Promise<void>(queueMicrotask);
|
|
146
|
+
|
|
147
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(1, JSON.stringify(message1));
|
|
148
|
+
expect(mockWebSocket.send).not.toHaveBeenNthCalledWith(2, JSON.stringify(message2));
|
|
149
|
+
|
|
150
|
+
mockWebSocket.bufferedAmount = 0;
|
|
151
|
+
|
|
152
|
+
await vi.advanceTimersByTimeAsync(BUFFER_LIMIT_POLLING_INTERVAL + 1);
|
|
153
|
+
|
|
154
|
+
expect(mockWebSocket.send).toHaveBeenNthCalledWith(2, JSON.stringify(message2));
|
|
155
|
+
|
|
156
|
+
vi.useRealTimers();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("should close the websocket connection", () => {
|
|
160
|
+
const { mockWebSocket, peer } = setup();
|
|
161
|
+
|
|
162
|
+
peer.outgoing.close();
|
|
163
|
+
|
|
164
|
+
expect(mockWebSocket.close).toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -5,34 +5,9 @@ import {
|
|
|
5
5
|
SyncMessage,
|
|
6
6
|
cojsonInternals,
|
|
7
7
|
} from "cojson";
|
|
8
|
+
import { AnyWebSocket, PingMsg } from "./types.js";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
close: { code: number; reason: string };
|
|
11
|
-
message: { data: unknown };
|
|
12
|
-
open: void;
|
|
13
|
-
}
|
|
14
|
-
interface PingMsg {
|
|
15
|
-
time: number;
|
|
16
|
-
dc: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface AnyWebSocket {
|
|
20
|
-
addEventListener<K extends keyof WebsocketEvents>(
|
|
21
|
-
type: K,
|
|
22
|
-
listener: (event: WebsocketEvents[K]) => void,
|
|
23
|
-
options?: { once: boolean },
|
|
24
|
-
): void;
|
|
25
|
-
removeEventListener<K extends keyof WebsocketEvents>(
|
|
26
|
-
type: K,
|
|
27
|
-
listener: (event: WebsocketEvents[K]) => void,
|
|
28
|
-
): void;
|
|
29
|
-
close(): void;
|
|
30
|
-
send(data: string): void;
|
|
31
|
-
readyState: number;
|
|
32
|
-
bufferedAmount: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const g: typeof globalThis & {
|
|
10
|
+
const g: typeof globalThis & {
|
|
36
11
|
jazzPings?: {
|
|
37
12
|
received: number;
|
|
38
13
|
sent: number;
|
|
@@ -40,6 +15,9 @@ const g: typeof globalThis & {
|
|
|
40
15
|
}[];
|
|
41
16
|
} = globalThis;
|
|
42
17
|
|
|
18
|
+
export const BUFFER_LIMIT = 100_000;
|
|
19
|
+
export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
|
|
20
|
+
|
|
43
21
|
export function createWebSocketPeer({
|
|
44
22
|
id,
|
|
45
23
|
websocket,
|
|
@@ -68,6 +46,7 @@ export function createWebSocketPeer({
|
|
|
68
46
|
websocket.addEventListener("message", function handleIncomingMsg(event) {
|
|
69
47
|
const msg = JSON.parse(event.data as string);
|
|
70
48
|
pingTimeout && clearTimeout(pingTimeout);
|
|
49
|
+
|
|
71
50
|
if (msg?.type === "ping") {
|
|
72
51
|
const ping = msg as PingMsg;
|
|
73
52
|
g.jazzPings ||= [];
|
|
@@ -102,31 +81,37 @@ export function createWebSocketPeer({
|
|
|
102
81
|
}
|
|
103
82
|
});
|
|
104
83
|
|
|
84
|
+
async function pushMessage(msg: SyncMessage) {
|
|
85
|
+
if (websocket.readyState !== 1) {
|
|
86
|
+
await websocketOpen;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
while (websocket.bufferedAmount > BUFFER_LIMIT && websocket.readyState === 1) {
|
|
90
|
+
await new Promise<void>((resolve) => setTimeout(resolve, BUFFER_LIMIT_POLLING_INTERVAL));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (websocket.readyState !== 1) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
websocket.send(JSON.stringify(msg));
|
|
98
|
+
}
|
|
99
|
+
|
|
105
100
|
return {
|
|
106
101
|
id,
|
|
107
102
|
incoming,
|
|
108
103
|
outgoing: {
|
|
109
|
-
|
|
110
|
-
await websocketOpen;
|
|
111
|
-
if (websocket.readyState === 1) {
|
|
112
|
-
while (websocket.bufferedAmount > 1_000_000) {
|
|
113
|
-
await new Promise((resolve) =>
|
|
114
|
-
setTimeout(resolve, 100),
|
|
115
|
-
);
|
|
116
|
-
if (websocket.readyState !== 1) {
|
|
117
|
-
console.log("WebSocket closed while buffering", id, websocket.bufferedAmount);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
websocket.send(JSON.stringify(msg));
|
|
122
|
-
}
|
|
123
|
-
},
|
|
104
|
+
push: pushMessage,
|
|
124
105
|
close() {
|
|
125
|
-
console.log("Trying to close", id, websocket.readyState)
|
|
106
|
+
console.log("Trying to close", id, websocket.readyState);
|
|
126
107
|
if (websocket.readyState === 0) {
|
|
127
|
-
websocket.addEventListener(
|
|
128
|
-
|
|
129
|
-
|
|
108
|
+
websocket.addEventListener(
|
|
109
|
+
"open",
|
|
110
|
+
function handleClose() {
|
|
111
|
+
websocket.close();
|
|
112
|
+
},
|
|
113
|
+
{ once: true },
|
|
114
|
+
);
|
|
130
115
|
} else if (websocket.readyState == 1) {
|
|
131
116
|
websocket.close();
|
|
132
117
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
export interface WebsocketEvents {
|
|
3
|
+
close: { code: number; reason: string; };
|
|
4
|
+
message: { data: unknown; };
|
|
5
|
+
open: void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PingMsg {
|
|
9
|
+
type: "ping";
|
|
10
|
+
time: number;
|
|
11
|
+
dc: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AnyWebSocket {
|
|
15
|
+
addEventListener<K extends keyof WebsocketEvents>(
|
|
16
|
+
type: K,
|
|
17
|
+
listener: (event: WebsocketEvents[K]) => void,
|
|
18
|
+
options?: { once: boolean; }
|
|
19
|
+
): void;
|
|
20
|
+
removeEventListener<K extends keyof WebsocketEvents>(
|
|
21
|
+
type: K,
|
|
22
|
+
listener: (event: WebsocketEvents[K]) => void
|
|
23
|
+
): void;
|
|
24
|
+
close(): void;
|
|
25
|
+
send(data: string): void;
|
|
26
|
+
readyState: number;
|
|
27
|
+
bufferedAmount: number;
|
|
28
|
+
}
|