@kyneta/websocket-transport 1.3.1 → 1.5.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/dist/browser.d.ts +31 -30
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +2 -15
- package/dist/bun.d.ts +18 -18
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +95 -43
- package/dist/bun.js.map +1 -1
- package/dist/client-transport-B2V7s2VP.js +525 -0
- package/dist/client-transport-B2V7s2VP.js.map +1 -0
- package/dist/client-transport-D3tYQYrS.d.ts +134 -0
- package/dist/client-transport-D3tYQYrS.d.ts.map +1 -0
- package/dist/server.d.ts +121 -118
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +304 -305
- package/dist/server.js.map +1 -1
- package/dist/{types-D0lbeevu.d.ts → types-c1S_xIRG.d.ts} +78 -69
- package/dist/types-c1S_xIRG.d.ts.map +1 -0
- package/package.json +13 -12
- package/src/__tests__/client-transport.test.ts +8 -9
- package/src/browser.ts +3 -3
- package/src/bun-websocket.ts +3 -3
- package/src/client-transport.ts +42 -17
- package/src/connection.ts +38 -13
- package/src/server-transport.ts +9 -24
- package/src/server.ts +5 -1
- package/src/service-client.ts +2 -2
- package/src/types.ts +17 -12
- package/dist/browser.js.map +0 -1
- package/dist/chunk-YZQF5RLV.js +0 -614
- package/dist/chunk-YZQF5RLV.js.map +0 -1
- package/dist/client-transport-DUAFjVbh.d.ts +0 -132
package/dist/chunk-YZQF5RLV.js
DELETED
|
@@ -1,614 +0,0 @@
|
|
|
1
|
-
// src/client-program.ts
|
|
2
|
-
import { computeBackoffDelay, DEFAULT_RECONNECT } from "@kyneta/transport";
|
|
3
|
-
function createWsClientProgram(options = {}) {
|
|
4
|
-
const { jitterFn = () => Math.random() * 1e3 } = options;
|
|
5
|
-
const reconnect = {
|
|
6
|
-
...DEFAULT_RECONNECT,
|
|
7
|
-
...options.reconnect
|
|
8
|
-
};
|
|
9
|
-
function tryReconnect(currentAttempt, reason, ...extraEffects) {
|
|
10
|
-
if (!reconnect.enabled) {
|
|
11
|
-
return [{ status: "disconnected", reason }, ...extraEffects];
|
|
12
|
-
}
|
|
13
|
-
if (currentAttempt >= reconnect.maxAttempts) {
|
|
14
|
-
return [
|
|
15
|
-
{
|
|
16
|
-
status: "disconnected",
|
|
17
|
-
reason: { type: "max-retries-exceeded", attempts: currentAttempt }
|
|
18
|
-
},
|
|
19
|
-
...extraEffects
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
const delay = computeBackoffDelay(
|
|
23
|
-
currentAttempt + 1,
|
|
24
|
-
reconnect.baseDelay,
|
|
25
|
-
reconnect.maxDelay,
|
|
26
|
-
jitterFn()
|
|
27
|
-
);
|
|
28
|
-
return [
|
|
29
|
-
{
|
|
30
|
-
status: "reconnecting",
|
|
31
|
-
attempt: currentAttempt + 1,
|
|
32
|
-
nextAttemptMs: delay
|
|
33
|
-
},
|
|
34
|
-
...extraEffects,
|
|
35
|
-
{ type: "start-reconnect-timer", delayMs: delay }
|
|
36
|
-
];
|
|
37
|
-
}
|
|
38
|
-
return {
|
|
39
|
-
init: [{ status: "disconnected" }],
|
|
40
|
-
update(msg, model) {
|
|
41
|
-
switch (msg.type) {
|
|
42
|
-
// -----------------------------------------------------------------
|
|
43
|
-
// start
|
|
44
|
-
// -----------------------------------------------------------------
|
|
45
|
-
case "start": {
|
|
46
|
-
if (model.status !== "disconnected") return [model];
|
|
47
|
-
return [
|
|
48
|
-
{ status: "connecting", attempt: 1 },
|
|
49
|
-
{ type: "create-websocket", attempt: 1 }
|
|
50
|
-
];
|
|
51
|
-
}
|
|
52
|
-
// -----------------------------------------------------------------
|
|
53
|
-
// socket-opened
|
|
54
|
-
// -----------------------------------------------------------------
|
|
55
|
-
case "socket-opened": {
|
|
56
|
-
if (model.status !== "connecting") return [model];
|
|
57
|
-
return [{ status: "connected" }, { type: "start-keepalive" }];
|
|
58
|
-
}
|
|
59
|
-
// -----------------------------------------------------------------
|
|
60
|
-
// server-ready
|
|
61
|
-
// -----------------------------------------------------------------
|
|
62
|
-
case "server-ready": {
|
|
63
|
-
if (model.status === "ready") return [model];
|
|
64
|
-
if (model.status === "connected") {
|
|
65
|
-
return [{ status: "ready" }, { type: "add-channel-and-establish" }];
|
|
66
|
-
}
|
|
67
|
-
if (model.status === "connecting") {
|
|
68
|
-
return [
|
|
69
|
-
{ status: "ready" },
|
|
70
|
-
{ type: "start-keepalive" },
|
|
71
|
-
{ type: "add-channel-and-establish" }
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
return [model];
|
|
75
|
-
}
|
|
76
|
-
// -----------------------------------------------------------------
|
|
77
|
-
// socket-closed
|
|
78
|
-
// -----------------------------------------------------------------
|
|
79
|
-
case "socket-closed": {
|
|
80
|
-
const reason = {
|
|
81
|
-
type: "closed",
|
|
82
|
-
code: msg.code,
|
|
83
|
-
reason: msg.reason
|
|
84
|
-
};
|
|
85
|
-
if (model.status === "connected") {
|
|
86
|
-
return tryReconnect(0, reason, { type: "stop-keepalive" });
|
|
87
|
-
}
|
|
88
|
-
if (model.status === "ready") {
|
|
89
|
-
return tryReconnect(
|
|
90
|
-
0,
|
|
91
|
-
reason,
|
|
92
|
-
{ type: "stop-keepalive" },
|
|
93
|
-
{ type: "remove-channel" }
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
return [model];
|
|
97
|
-
}
|
|
98
|
-
// -----------------------------------------------------------------
|
|
99
|
-
// socket-error
|
|
100
|
-
// -----------------------------------------------------------------
|
|
101
|
-
case "socket-error": {
|
|
102
|
-
const reason = {
|
|
103
|
-
type: "error",
|
|
104
|
-
error: msg.error
|
|
105
|
-
};
|
|
106
|
-
if (model.status === "connecting") {
|
|
107
|
-
return tryReconnect(model.attempt, reason);
|
|
108
|
-
}
|
|
109
|
-
if (model.status === "connected") {
|
|
110
|
-
return tryReconnect(0, reason, { type: "stop-keepalive" });
|
|
111
|
-
}
|
|
112
|
-
if (model.status === "ready") {
|
|
113
|
-
return tryReconnect(
|
|
114
|
-
0,
|
|
115
|
-
reason,
|
|
116
|
-
{ type: "stop-keepalive" },
|
|
117
|
-
{ type: "remove-channel" }
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
return [model];
|
|
121
|
-
}
|
|
122
|
-
// -----------------------------------------------------------------
|
|
123
|
-
// reconnect-timer-fired
|
|
124
|
-
// -----------------------------------------------------------------
|
|
125
|
-
case "reconnect-timer-fired": {
|
|
126
|
-
if (model.status !== "reconnecting") return [model];
|
|
127
|
-
return [
|
|
128
|
-
{ status: "connecting", attempt: model.attempt },
|
|
129
|
-
{ type: "create-websocket", attempt: model.attempt }
|
|
130
|
-
];
|
|
131
|
-
}
|
|
132
|
-
// -----------------------------------------------------------------
|
|
133
|
-
// stop
|
|
134
|
-
// -----------------------------------------------------------------
|
|
135
|
-
case "stop": {
|
|
136
|
-
if (model.status === "disconnected") return [model];
|
|
137
|
-
const effects = [{ type: "cancel-reconnect-timer" }];
|
|
138
|
-
if (model.status === "connecting") {
|
|
139
|
-
effects.push({ type: "close-websocket" });
|
|
140
|
-
}
|
|
141
|
-
if (model.status === "connected") {
|
|
142
|
-
effects.push(
|
|
143
|
-
{ type: "close-websocket" },
|
|
144
|
-
{ type: "stop-keepalive" }
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
if (model.status === "ready") {
|
|
148
|
-
effects.push(
|
|
149
|
-
{ type: "close-websocket" },
|
|
150
|
-
{ type: "stop-keepalive" },
|
|
151
|
-
{ type: "remove-channel" }
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
return [
|
|
155
|
-
{ status: "disconnected", reason: { type: "intentional" } },
|
|
156
|
-
...effects
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// src/types.ts
|
|
165
|
-
var READY_STATE = {
|
|
166
|
-
CONNECTING: 0,
|
|
167
|
-
OPEN: 1,
|
|
168
|
-
CLOSING: 2,
|
|
169
|
-
CLOSED: 3
|
|
170
|
-
};
|
|
171
|
-
function wrapStandardWebsocket(ws) {
|
|
172
|
-
return {
|
|
173
|
-
send(data) {
|
|
174
|
-
ws.send(
|
|
175
|
-
typeof data === "string" ? data : data
|
|
176
|
-
);
|
|
177
|
-
},
|
|
178
|
-
close(code, reason) {
|
|
179
|
-
ws.close(code, reason);
|
|
180
|
-
},
|
|
181
|
-
onMessage(handler) {
|
|
182
|
-
ws.addEventListener("message", (event) => {
|
|
183
|
-
if (event.data instanceof ArrayBuffer) {
|
|
184
|
-
handler(new Uint8Array(event.data));
|
|
185
|
-
} else if (typeof Blob !== "undefined" && event.data instanceof Blob) {
|
|
186
|
-
event.data.arrayBuffer().then((buffer) => {
|
|
187
|
-
handler(new Uint8Array(buffer));
|
|
188
|
-
});
|
|
189
|
-
} else {
|
|
190
|
-
handler(event.data);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
},
|
|
194
|
-
onClose(handler) {
|
|
195
|
-
ws.addEventListener("close", (event) => {
|
|
196
|
-
handler(event.code, event.reason);
|
|
197
|
-
});
|
|
198
|
-
},
|
|
199
|
-
onError(handler) {
|
|
200
|
-
ws.addEventListener("error", (_event) => {
|
|
201
|
-
handler(new Error("WebSocket error"));
|
|
202
|
-
});
|
|
203
|
-
},
|
|
204
|
-
get readyState() {
|
|
205
|
-
switch (ws.readyState) {
|
|
206
|
-
case READY_STATE.CONNECTING:
|
|
207
|
-
return "connecting";
|
|
208
|
-
case READY_STATE.OPEN:
|
|
209
|
-
return "open";
|
|
210
|
-
case READY_STATE.CLOSING:
|
|
211
|
-
return "closing";
|
|
212
|
-
case READY_STATE.CLOSED:
|
|
213
|
-
return "closed";
|
|
214
|
-
default:
|
|
215
|
-
return "closed";
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
function wrapNodeWebsocket(ws) {
|
|
221
|
-
const CONNECTING = 0;
|
|
222
|
-
const OPEN = 1;
|
|
223
|
-
const CLOSING = 2;
|
|
224
|
-
return {
|
|
225
|
-
send(data) {
|
|
226
|
-
ws.send(data);
|
|
227
|
-
},
|
|
228
|
-
close(code, reason) {
|
|
229
|
-
ws.close(code, reason);
|
|
230
|
-
},
|
|
231
|
-
onMessage(handler) {
|
|
232
|
-
ws.on(
|
|
233
|
-
"message",
|
|
234
|
-
(data, isBinary) => {
|
|
235
|
-
if (isBinary) {
|
|
236
|
-
if (data instanceof ArrayBuffer) {
|
|
237
|
-
handler(new Uint8Array(data));
|
|
238
|
-
} else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
239
|
-
handler(new Uint8Array(data));
|
|
240
|
-
} else {
|
|
241
|
-
handler(new Uint8Array(data));
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
245
|
-
handler(data.toString("utf-8"));
|
|
246
|
-
} else {
|
|
247
|
-
handler(data);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
);
|
|
252
|
-
},
|
|
253
|
-
onClose(handler) {
|
|
254
|
-
ws.on("close", (code, reason) => {
|
|
255
|
-
handler(code, reason.toString());
|
|
256
|
-
});
|
|
257
|
-
},
|
|
258
|
-
onError(handler) {
|
|
259
|
-
ws.on("error", handler);
|
|
260
|
-
},
|
|
261
|
-
get readyState() {
|
|
262
|
-
switch (ws.readyState) {
|
|
263
|
-
case CONNECTING:
|
|
264
|
-
return "connecting";
|
|
265
|
-
case OPEN:
|
|
266
|
-
return "open";
|
|
267
|
-
case CLOSING:
|
|
268
|
-
return "closing";
|
|
269
|
-
default:
|
|
270
|
-
return "closed";
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// src/client-transport.ts
|
|
277
|
-
import { createObservableProgram } from "@kyneta/machine";
|
|
278
|
-
import { Transport } from "@kyneta/transport";
|
|
279
|
-
import {
|
|
280
|
-
decodeBinaryMessages,
|
|
281
|
-
encodeBinaryAndSend,
|
|
282
|
-
FragmentReassembler
|
|
283
|
-
} from "@kyneta/wire";
|
|
284
|
-
var DEFAULT_FRAGMENT_THRESHOLD = 100 * 1024;
|
|
285
|
-
var WebsocketClientTransport = class extends Transport {
|
|
286
|
-
#peerId;
|
|
287
|
-
#options;
|
|
288
|
-
#WebSocketImpl;
|
|
289
|
-
// Observable program handle — created in constructor, drives all state
|
|
290
|
-
#handle;
|
|
291
|
-
// Executor-local I/O state — not in the program model
|
|
292
|
-
#socket;
|
|
293
|
-
#serverChannel;
|
|
294
|
-
#keepaliveTimer;
|
|
295
|
-
#reconnectTimer;
|
|
296
|
-
// Fragmentation
|
|
297
|
-
#fragmentThreshold;
|
|
298
|
-
#reassembler;
|
|
299
|
-
constructor(options) {
|
|
300
|
-
super({ transportType: "websocket-client" });
|
|
301
|
-
this.#options = options;
|
|
302
|
-
this.#WebSocketImpl = options.WebSocket;
|
|
303
|
-
this.#fragmentThreshold = options.fragmentThreshold ?? DEFAULT_FRAGMENT_THRESHOLD;
|
|
304
|
-
this.#reassembler = new FragmentReassembler({
|
|
305
|
-
timeoutMs: 1e4
|
|
306
|
-
});
|
|
307
|
-
const program = createWsClientProgram({
|
|
308
|
-
reconnect: options.reconnect
|
|
309
|
-
});
|
|
310
|
-
this.#handle = createObservableProgram(program, (effect, dispatch) => {
|
|
311
|
-
this.#executeEffect(effect, dispatch);
|
|
312
|
-
});
|
|
313
|
-
this.#setupLifecycleEvents();
|
|
314
|
-
}
|
|
315
|
-
// ==========================================================================
|
|
316
|
-
// Effect executor — interprets data effects as I/O
|
|
317
|
-
// ==========================================================================
|
|
318
|
-
#executeEffect(effect, dispatch) {
|
|
319
|
-
switch (effect.type) {
|
|
320
|
-
case "create-websocket": {
|
|
321
|
-
this.#doCreateWebsocket(dispatch);
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
case "close-websocket": {
|
|
325
|
-
if (this.#socket) {
|
|
326
|
-
this.#socket.close(1e3, "Client disconnecting");
|
|
327
|
-
this.#socket = void 0;
|
|
328
|
-
}
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
case "add-channel-and-establish": {
|
|
332
|
-
if (this.#serverChannel) {
|
|
333
|
-
this.removeChannel(this.#serverChannel.channelId);
|
|
334
|
-
this.#serverChannel = void 0;
|
|
335
|
-
}
|
|
336
|
-
this.#serverChannel = this.addChannel();
|
|
337
|
-
this.establishChannel(this.#serverChannel.channelId);
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
case "remove-channel": {
|
|
341
|
-
if (this.#serverChannel) {
|
|
342
|
-
this.removeChannel(this.#serverChannel.channelId);
|
|
343
|
-
this.#serverChannel = void 0;
|
|
344
|
-
}
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
case "start-reconnect-timer": {
|
|
348
|
-
this.#reconnectTimer = setTimeout(() => {
|
|
349
|
-
this.#reconnectTimer = void 0;
|
|
350
|
-
dispatch({ type: "reconnect-timer-fired" });
|
|
351
|
-
}, effect.delayMs);
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
case "cancel-reconnect-timer": {
|
|
355
|
-
if (this.#reconnectTimer !== void 0) {
|
|
356
|
-
clearTimeout(this.#reconnectTimer);
|
|
357
|
-
this.#reconnectTimer = void 0;
|
|
358
|
-
}
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
case "start-keepalive": {
|
|
362
|
-
this.#startKeepalive();
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
case "stop-keepalive": {
|
|
366
|
-
this.#stopKeepalive();
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
// ==========================================================================
|
|
372
|
-
// WebSocket creation — the core I/O operation
|
|
373
|
-
// ==========================================================================
|
|
374
|
-
/**
|
|
375
|
-
* Create a WebSocket and wire up event handlers to dispatch messages.
|
|
376
|
-
*
|
|
377
|
-
* The message handler is set up IMMEDIATELY after creation (before
|
|
378
|
-
* the open event) to handle the race condition where the server sends
|
|
379
|
-
* "ready" before the client's open promise resolves.
|
|
380
|
-
*/
|
|
381
|
-
#doCreateWebsocket(dispatch) {
|
|
382
|
-
const peerId = this.#peerId;
|
|
383
|
-
if (!peerId) {
|
|
384
|
-
dispatch({
|
|
385
|
-
type: "socket-error",
|
|
386
|
-
error: new Error("Cannot connect: peerId not set")
|
|
387
|
-
});
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
const url = typeof this.#options.url === "function" ? this.#options.url(peerId) : this.#options.url;
|
|
391
|
-
try {
|
|
392
|
-
if (this.#options.headers && Object.keys(this.#options.headers).length > 0) {
|
|
393
|
-
this.#socket = new this.#WebSocketImpl(url, {
|
|
394
|
-
headers: this.#options.headers
|
|
395
|
-
});
|
|
396
|
-
} else {
|
|
397
|
-
this.#socket = new this.#WebSocketImpl(url);
|
|
398
|
-
}
|
|
399
|
-
this.#socket.binaryType = "arraybuffer";
|
|
400
|
-
const socket = this.#socket;
|
|
401
|
-
socket.addEventListener("message", (event) => {
|
|
402
|
-
this.#handleMessage(event, dispatch);
|
|
403
|
-
});
|
|
404
|
-
let settled = false;
|
|
405
|
-
const onOpen = () => {
|
|
406
|
-
cleanup();
|
|
407
|
-
settled = true;
|
|
408
|
-
dispatch({ type: "socket-opened" });
|
|
409
|
-
socket.addEventListener("close", (event) => {
|
|
410
|
-
dispatch({
|
|
411
|
-
type: "socket-closed",
|
|
412
|
-
code: event.code,
|
|
413
|
-
reason: event.reason
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
};
|
|
417
|
-
const onError = () => {
|
|
418
|
-
if (settled) return;
|
|
419
|
-
cleanup();
|
|
420
|
-
settled = true;
|
|
421
|
-
dispatch({
|
|
422
|
-
type: "socket-error",
|
|
423
|
-
error: new Error("WebSocket connection failed")
|
|
424
|
-
});
|
|
425
|
-
};
|
|
426
|
-
const onClose = () => {
|
|
427
|
-
if (settled) return;
|
|
428
|
-
cleanup();
|
|
429
|
-
settled = true;
|
|
430
|
-
dispatch({
|
|
431
|
-
type: "socket-error",
|
|
432
|
-
error: new Error("WebSocket closed during connection")
|
|
433
|
-
});
|
|
434
|
-
};
|
|
435
|
-
const cleanup = () => {
|
|
436
|
-
socket.removeEventListener("open", onOpen);
|
|
437
|
-
socket.removeEventListener("error", onError);
|
|
438
|
-
socket.removeEventListener("close", onClose);
|
|
439
|
-
};
|
|
440
|
-
socket.addEventListener("open", onOpen);
|
|
441
|
-
socket.addEventListener("error", onError);
|
|
442
|
-
socket.addEventListener("close", onClose);
|
|
443
|
-
} catch (error) {
|
|
444
|
-
dispatch({
|
|
445
|
-
type: "socket-error",
|
|
446
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
// ==========================================================================
|
|
451
|
-
// Message handling — I/O parsing logic
|
|
452
|
-
// ==========================================================================
|
|
453
|
-
/**
|
|
454
|
-
* Handle incoming Websocket messages.
|
|
455
|
-
*
|
|
456
|
-
* Text frames carry the "ready" handshake and keepalive pong.
|
|
457
|
-
* Binary frames carry CBOR-encoded ChannelMsg.
|
|
458
|
-
*/
|
|
459
|
-
#handleMessage(event, dispatch) {
|
|
460
|
-
const data = event.data;
|
|
461
|
-
if (typeof data === "string") {
|
|
462
|
-
if (data === "ready") {
|
|
463
|
-
dispatch({ type: "server-ready" });
|
|
464
|
-
}
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
if (data instanceof ArrayBuffer) {
|
|
468
|
-
try {
|
|
469
|
-
const messages = decodeBinaryMessages(
|
|
470
|
-
new Uint8Array(data),
|
|
471
|
-
this.#reassembler
|
|
472
|
-
);
|
|
473
|
-
if (messages) {
|
|
474
|
-
for (const msg of messages) {
|
|
475
|
-
this.#handleChannelMessage(msg);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
} catch (error) {
|
|
479
|
-
console.error("Failed to decode message:", error);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Handle a decoded channel message.
|
|
485
|
-
*/
|
|
486
|
-
#handleChannelMessage(msg) {
|
|
487
|
-
if (!this.#serverChannel) {
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
this.#serverChannel.onReceive(msg);
|
|
491
|
-
}
|
|
492
|
-
// ==========================================================================
|
|
493
|
-
// Keepalive
|
|
494
|
-
// ==========================================================================
|
|
495
|
-
#startKeepalive() {
|
|
496
|
-
this.#stopKeepalive();
|
|
497
|
-
const interval = this.#options.keepaliveInterval ?? 3e4;
|
|
498
|
-
this.#keepaliveTimer = setInterval(() => {
|
|
499
|
-
if (this.#socket?.readyState === READY_STATE.OPEN) {
|
|
500
|
-
this.#socket.send("ping");
|
|
501
|
-
}
|
|
502
|
-
}, interval);
|
|
503
|
-
}
|
|
504
|
-
#stopKeepalive() {
|
|
505
|
-
if (this.#keepaliveTimer) {
|
|
506
|
-
clearInterval(this.#keepaliveTimer);
|
|
507
|
-
this.#keepaliveTimer = void 0;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
// ==========================================================================
|
|
511
|
-
// Lifecycle event forwarding
|
|
512
|
-
// ==========================================================================
|
|
513
|
-
#setupLifecycleEvents() {
|
|
514
|
-
let wasConnectedBefore = false;
|
|
515
|
-
this.#handle.subscribeToTransitions((transition) => {
|
|
516
|
-
this.#options.lifecycle?.onStateChange?.(transition);
|
|
517
|
-
const { from, to } = transition;
|
|
518
|
-
if (to.status === "disconnected" && to.reason) {
|
|
519
|
-
this.#options.lifecycle?.onDisconnect?.(to.reason);
|
|
520
|
-
}
|
|
521
|
-
if (to.status === "reconnecting") {
|
|
522
|
-
this.#options.lifecycle?.onReconnecting?.(to.attempt, to.nextAttemptMs);
|
|
523
|
-
}
|
|
524
|
-
if (wasConnectedBefore && (from.status === "reconnecting" || from.status === "connecting") && (to.status === "connected" || to.status === "ready")) {
|
|
525
|
-
this.#options.lifecycle?.onReconnected?.();
|
|
526
|
-
}
|
|
527
|
-
if (to.status === "ready") {
|
|
528
|
-
this.#options.lifecycle?.onReady?.();
|
|
529
|
-
wasConnectedBefore = true;
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
// ==========================================================================
|
|
534
|
-
// State observation — delegated to the observable handle
|
|
535
|
-
// ==========================================================================
|
|
536
|
-
/**
|
|
537
|
-
* Get the current connection state.
|
|
538
|
-
*/
|
|
539
|
-
getState() {
|
|
540
|
-
return this.#handle.getState();
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Subscribe to state transitions.
|
|
544
|
-
*/
|
|
545
|
-
subscribeToTransitions(listener) {
|
|
546
|
-
return this.#handle.subscribeToTransitions(listener);
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Wait for a specific state.
|
|
550
|
-
*/
|
|
551
|
-
waitForState(predicate, options) {
|
|
552
|
-
return this.#handle.waitForState(predicate, options);
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Wait for a specific status.
|
|
556
|
-
*/
|
|
557
|
-
waitForStatus(status, options) {
|
|
558
|
-
return this.#handle.waitForStatus(status, options);
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* Whether the client is ready (server ready signal received).
|
|
562
|
-
*/
|
|
563
|
-
get isReady() {
|
|
564
|
-
return this.#handle.getState().status === "ready";
|
|
565
|
-
}
|
|
566
|
-
// ==========================================================================
|
|
567
|
-
// Transport abstract method implementations
|
|
568
|
-
// ==========================================================================
|
|
569
|
-
generate() {
|
|
570
|
-
return {
|
|
571
|
-
transportType: this.transportType,
|
|
572
|
-
send: (msg) => {
|
|
573
|
-
const socket = this.#socket;
|
|
574
|
-
if (!socket || socket.readyState !== READY_STATE.OPEN) {
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
encodeBinaryAndSend(
|
|
578
|
-
msg,
|
|
579
|
-
this.#fragmentThreshold,
|
|
580
|
-
(data) => socket.send(new Uint8Array(data).buffer)
|
|
581
|
-
);
|
|
582
|
-
},
|
|
583
|
-
stop: () => {
|
|
584
|
-
}
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
async onStart() {
|
|
588
|
-
if (!this.identity) {
|
|
589
|
-
throw new Error(
|
|
590
|
-
"Transport not properly initialized \u2014 identity not available"
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
this.#peerId = this.identity.peerId;
|
|
594
|
-
this.#handle.dispatch({ type: "start" });
|
|
595
|
-
}
|
|
596
|
-
async onStop() {
|
|
597
|
-
this.#reassembler.dispose();
|
|
598
|
-
this.#handle.dispatch({ type: "stop" });
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
function createWebsocketClient(options) {
|
|
602
|
-
return () => new WebsocketClientTransport(options);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
export {
|
|
606
|
-
createWsClientProgram,
|
|
607
|
-
READY_STATE,
|
|
608
|
-
wrapStandardWebsocket,
|
|
609
|
-
wrapNodeWebsocket,
|
|
610
|
-
DEFAULT_FRAGMENT_THRESHOLD,
|
|
611
|
-
WebsocketClientTransport,
|
|
612
|
-
createWebsocketClient
|
|
613
|
-
};
|
|
614
|
-
//# sourceMappingURL=chunk-YZQF5RLV.js.map
|