durablews 1.0.1 → 2.0.0-alpha.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/README.md +43 -91
- package/dist/binding-bZNBe2Bz.d.cts +11 -0
- package/dist/binding-gyPQiK9i.d.ts +11 -0
- package/dist/chunk-GIDTCSPD.js +22 -0
- package/dist/chunk-GIDTCSPD.js.map +1 -0
- package/dist/chunk-MG4TK53H.js +590 -0
- package/dist/chunk-MG4TK53H.js.map +1 -0
- package/dist/compat.cjs +765 -0
- package/dist/compat.cjs.map +1 -0
- package/dist/compat.d.cts +72 -0
- package/dist/compat.d.ts +72 -0
- package/dist/compat.js +177 -0
- package/dist/compat.js.map +1 -0
- package/dist/index.cjs +612 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +81 -0
- package/dist/index.d.ts +81 -44
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +646 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +66 -0
- package/dist/react.d.ts +66 -0
- package/dist/react.js +42 -0
- package/dist/react.js.map +1 -0
- package/dist/types-BDvztGcG.d.cts +410 -0
- package/dist/types-BDvztGcG.d.ts +410 -0
- package/dist/vue.cjs +650 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +72 -0
- package/dist/vue.d.ts +72 -0
- package/dist/vue.js +43 -0
- package/dist/vue.js.map +1 -0
- package/package.json +128 -69
- package/dist/durablews.es.js +0 -120
- package/dist/durablews.umd.js +0 -1
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/react.ts
|
|
6
|
+
|
|
7
|
+
// src/backoff.ts
|
|
8
|
+
var RECONNECT_DEFAULTS = {
|
|
9
|
+
baseDelay: 500,
|
|
10
|
+
factor: 2,
|
|
11
|
+
maxDelay: 3e4,
|
|
12
|
+
jitter: true,
|
|
13
|
+
maxRetries: Number.POSITIVE_INFINITY,
|
|
14
|
+
shouldReconnect: () => true
|
|
15
|
+
};
|
|
16
|
+
function resolveReconnect(option) {
|
|
17
|
+
if (option === false) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return { ...RECONNECT_DEFAULTS, ...option };
|
|
21
|
+
}
|
|
22
|
+
function computeDelay(attempt, options, random = Math.random) {
|
|
23
|
+
const exponential = Math.min(
|
|
24
|
+
options.maxDelay,
|
|
25
|
+
options.baseDelay * options.factor ** attempt
|
|
26
|
+
);
|
|
27
|
+
return options.jitter ? random() * exponential : exponential;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/codec.ts
|
|
31
|
+
function safeJSONParse(data) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(data);
|
|
34
|
+
} catch {
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function isBinary(data) {
|
|
39
|
+
return data instanceof ArrayBuffer || ArrayBuffer.isView(data) || typeof Blob !== "undefined" && data instanceof Blob;
|
|
40
|
+
}
|
|
41
|
+
var jsonCodec = {
|
|
42
|
+
encode(data) {
|
|
43
|
+
if (typeof data === "string" || isBinary(data)) {
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
return JSON.stringify(data);
|
|
47
|
+
},
|
|
48
|
+
decode(data) {
|
|
49
|
+
return typeof data === "string" ? safeJSONParse(data) : data;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/fsm.ts
|
|
54
|
+
var TRANSITIONS = {
|
|
55
|
+
idle: { CONNECT: "connecting" },
|
|
56
|
+
connecting: {
|
|
57
|
+
OPEN: "open",
|
|
58
|
+
CLOSE_REQUESTED: "closing",
|
|
59
|
+
CLOSED: "closed",
|
|
60
|
+
RETRY: "reconnecting"
|
|
61
|
+
},
|
|
62
|
+
open: {
|
|
63
|
+
CLOSE_REQUESTED: "closing",
|
|
64
|
+
CLOSED: "closed",
|
|
65
|
+
RETRY: "reconnecting"
|
|
66
|
+
},
|
|
67
|
+
closing: { CLOSED: "closed" },
|
|
68
|
+
// `reconnecting` = waiting out the backoff delay; no socket exists, so
|
|
69
|
+
// CLOSE_REQUESTED goes straight to `closed` (there is nothing to wait for)
|
|
70
|
+
// and OPEN/CLOSED cannot legally occur.
|
|
71
|
+
reconnecting: {
|
|
72
|
+
CONNECT: "connecting",
|
|
73
|
+
CLOSE_REQUESTED: "closed"
|
|
74
|
+
},
|
|
75
|
+
closed: { CONNECT: "connecting" }
|
|
76
|
+
};
|
|
77
|
+
function nextState(state, event) {
|
|
78
|
+
return TRANSITIONS[state][event] ?? null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/heartbeat.ts
|
|
82
|
+
var HEARTBEAT_TIMEOUT_CODE = 4408;
|
|
83
|
+
function resolveHeartbeat(option) {
|
|
84
|
+
if (option === void 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
interval: option.interval,
|
|
89
|
+
message: option.message ?? "ping",
|
|
90
|
+
timeout: option.timeout ?? option.interval
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/helpers/event-bus.ts
|
|
95
|
+
function defineEventBus() {
|
|
96
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
97
|
+
function on(eventName, handler) {
|
|
98
|
+
const handlers = listeners.get(eventName) ?? [];
|
|
99
|
+
handlers.push(handler);
|
|
100
|
+
listeners.set(eventName, handlers);
|
|
101
|
+
}
|
|
102
|
+
function off(eventName, handler) {
|
|
103
|
+
const handlers = listeners.get(eventName);
|
|
104
|
+
if (!handlers) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
listeners.set(
|
|
108
|
+
eventName,
|
|
109
|
+
handlers.filter((h) => h !== handler)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function once(eventName, handler) {
|
|
113
|
+
const onceHandler = (payload) => {
|
|
114
|
+
off(eventName, onceHandler);
|
|
115
|
+
handler(payload);
|
|
116
|
+
};
|
|
117
|
+
on(eventName, onceHandler);
|
|
118
|
+
}
|
|
119
|
+
function emit(eventName, payload) {
|
|
120
|
+
const handlers = listeners.get(eventName);
|
|
121
|
+
handlers?.forEach((fn) => {
|
|
122
|
+
fn(payload);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return { on, off, emit, once };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/pipeline.ts
|
|
129
|
+
function runPipeline(middlewares, ctx, terminal) {
|
|
130
|
+
let invoked = -1;
|
|
131
|
+
function dispatch(i) {
|
|
132
|
+
if (i <= invoked) {
|
|
133
|
+
throw new Error("next() called multiple times");
|
|
134
|
+
}
|
|
135
|
+
invoked = i;
|
|
136
|
+
const middleware = middlewares[i];
|
|
137
|
+
if (!middleware) {
|
|
138
|
+
return terminal();
|
|
139
|
+
}
|
|
140
|
+
return middleware(ctx, () => dispatch(i + 1));
|
|
141
|
+
}
|
|
142
|
+
return dispatch(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/queue.ts
|
|
146
|
+
var QUEUE_DEFAULTS = {
|
|
147
|
+
maxSize: 256
|
|
148
|
+
};
|
|
149
|
+
function resolveQueue(option) {
|
|
150
|
+
if (option === false) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return { ...QUEUE_DEFAULTS, ...option };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/schema.ts
|
|
157
|
+
var SchemaValidationError = class extends Error {
|
|
158
|
+
issues;
|
|
159
|
+
constructor(issues) {
|
|
160
|
+
super(
|
|
161
|
+
`Inbound message failed schema validation: ${issues.map((issue) => issue.message).join("; ")}`
|
|
162
|
+
);
|
|
163
|
+
this.name = "SchemaValidationError";
|
|
164
|
+
this.issues = issues;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/client.ts
|
|
169
|
+
function client(config) {
|
|
170
|
+
const bus = defineEventBus();
|
|
171
|
+
const codec = config.codec ?? jsonCodec;
|
|
172
|
+
const reconnect = resolveReconnect(config.reconnect);
|
|
173
|
+
const queue = resolveQueue(config.queue);
|
|
174
|
+
const heartbeat = resolveHeartbeat(config.heartbeat);
|
|
175
|
+
const middlewares = [];
|
|
176
|
+
const outboundMiddlewares = [];
|
|
177
|
+
const queued = [];
|
|
178
|
+
let socket = null;
|
|
179
|
+
let state = "idle";
|
|
180
|
+
let lastError = null;
|
|
181
|
+
let lastInboundAt = 0;
|
|
182
|
+
let heartbeatTimer = null;
|
|
183
|
+
let heartbeatDeadline = null;
|
|
184
|
+
let outboundTail = null;
|
|
185
|
+
let requeueIndex = 0;
|
|
186
|
+
let closeRequested = false;
|
|
187
|
+
let retryAttempt = 0;
|
|
188
|
+
let retryTimer = null;
|
|
189
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
190
|
+
let snapshot = null;
|
|
191
|
+
let pending = null;
|
|
192
|
+
function notify() {
|
|
193
|
+
snapshot = null;
|
|
194
|
+
for (const listener of [...subscribers]) {
|
|
195
|
+
listener();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function transition(event) {
|
|
199
|
+
const next = nextState(state, event);
|
|
200
|
+
if (next === null || next === state) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const previous = state;
|
|
204
|
+
state = next;
|
|
205
|
+
notify();
|
|
206
|
+
bus.emit("statechange", { previous, current: next });
|
|
207
|
+
return next;
|
|
208
|
+
}
|
|
209
|
+
function settleConnected() {
|
|
210
|
+
pending?.resolve();
|
|
211
|
+
pending = null;
|
|
212
|
+
}
|
|
213
|
+
function settleFailed(reason) {
|
|
214
|
+
pending?.reject(reason);
|
|
215
|
+
pending = null;
|
|
216
|
+
}
|
|
217
|
+
function clearRetryTimer() {
|
|
218
|
+
if (retryTimer !== null) {
|
|
219
|
+
clearTimeout(retryTimer);
|
|
220
|
+
retryTimer = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function stopHeartbeat() {
|
|
224
|
+
if (heartbeatTimer !== null) {
|
|
225
|
+
clearInterval(heartbeatTimer);
|
|
226
|
+
heartbeatTimer = null;
|
|
227
|
+
}
|
|
228
|
+
if (heartbeatDeadline !== null) {
|
|
229
|
+
clearTimeout(heartbeatDeadline);
|
|
230
|
+
heartbeatDeadline = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function startHeartbeat() {
|
|
234
|
+
if (heartbeat === null) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
stopHeartbeat();
|
|
238
|
+
heartbeatTimer = setInterval(() => {
|
|
239
|
+
if (state !== "open" || !socket) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const pingSentAt = Date.now();
|
|
243
|
+
try {
|
|
244
|
+
socket.send(codec.encode(heartbeat.message));
|
|
245
|
+
} catch (error) {
|
|
246
|
+
bus.emit("error", toError(error));
|
|
247
|
+
}
|
|
248
|
+
if (heartbeatDeadline !== null) {
|
|
249
|
+
clearTimeout(heartbeatDeadline);
|
|
250
|
+
}
|
|
251
|
+
heartbeatDeadline = setTimeout(() => {
|
|
252
|
+
heartbeatDeadline = null;
|
|
253
|
+
if (state === "open" && socket && lastInboundAt < pingSentAt) {
|
|
254
|
+
const failure = new Error(
|
|
255
|
+
`Heartbeat timeout: no inbound traffic within ${heartbeat.timeout}ms of a ping`
|
|
256
|
+
);
|
|
257
|
+
lastError = failure;
|
|
258
|
+
notify();
|
|
259
|
+
bus.emit("error", failure);
|
|
260
|
+
socket.close(HEARTBEAT_TIMEOUT_CODE, "heartbeat timeout");
|
|
261
|
+
}
|
|
262
|
+
}, heartbeat.timeout);
|
|
263
|
+
}, heartbeat.interval);
|
|
264
|
+
}
|
|
265
|
+
function wire(transformed, original) {
|
|
266
|
+
if (socket && state === "open") {
|
|
267
|
+
socket.send(codec.encode(transformed));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (queue !== null && (state === "connecting" || state === "reconnecting")) {
|
|
271
|
+
if (queued.length >= queue.maxSize) {
|
|
272
|
+
bus.emit("drop", {
|
|
273
|
+
data: queued.shift(),
|
|
274
|
+
reason: "overflow"
|
|
275
|
+
});
|
|
276
|
+
if (requeueIndex > 0) {
|
|
277
|
+
requeueIndex -= 1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
queued.splice(requeueIndex, 0, original);
|
|
281
|
+
requeueIndex += 1;
|
|
282
|
+
notify();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
bus.emit("drop", { data: original, reason: "close" });
|
|
286
|
+
}
|
|
287
|
+
function runOutbound(data) {
|
|
288
|
+
const ctx = { data, client: api };
|
|
289
|
+
try {
|
|
290
|
+
const result = runPipeline(outboundMiddlewares, ctx, () => {
|
|
291
|
+
wire(ctx.data, data);
|
|
292
|
+
});
|
|
293
|
+
if (result instanceof Promise) {
|
|
294
|
+
return result.catch((error) => {
|
|
295
|
+
bus.emit("error", toError(error));
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
bus.emit("error", toError(error));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function transmit(data) {
|
|
303
|
+
if (outboundTail === null) {
|
|
304
|
+
const result = runOutbound(data);
|
|
305
|
+
if (result instanceof Promise) {
|
|
306
|
+
setOutboundTail(result);
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
setOutboundTail(outboundTail.then(() => runOutbound(data)));
|
|
311
|
+
}
|
|
312
|
+
function setOutboundTail(run) {
|
|
313
|
+
const tail = run.then(() => {
|
|
314
|
+
if (outboundTail === tail) {
|
|
315
|
+
outboundTail = null;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
outboundTail = tail;
|
|
319
|
+
}
|
|
320
|
+
function flushQueue() {
|
|
321
|
+
const hadQueued = queued.length > 0;
|
|
322
|
+
requeueIndex = 0;
|
|
323
|
+
while (queued.length > 0 && socket && state === "open") {
|
|
324
|
+
const data = queued.shift();
|
|
325
|
+
if (outboundMiddlewares.length === 0) {
|
|
326
|
+
try {
|
|
327
|
+
socket.send(codec.encode(data));
|
|
328
|
+
} catch (error) {
|
|
329
|
+
bus.emit("error", toError(error));
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
transmit(data);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (hadQueued) {
|
|
336
|
+
notify();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function dropQueued() {
|
|
340
|
+
const hadQueued = queued.length > 0;
|
|
341
|
+
while (queued.length > 0) {
|
|
342
|
+
bus.emit("drop", { data: queued.shift(), reason: "close" });
|
|
343
|
+
}
|
|
344
|
+
if (hadQueued) {
|
|
345
|
+
notify();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function isRetryable(event) {
|
|
349
|
+
return reconnect !== null && !closeRequested && retryAttempt < reconnect.maxRetries && reconnect.shouldReconnect(event);
|
|
350
|
+
}
|
|
351
|
+
function openSocket() {
|
|
352
|
+
if (lastError !== null) {
|
|
353
|
+
lastError = null;
|
|
354
|
+
notify();
|
|
355
|
+
}
|
|
356
|
+
socket = config.protocols ? new WebSocket(config.url, config.protocols) : new WebSocket(config.url);
|
|
357
|
+
if (config.binaryType) {
|
|
358
|
+
socket.binaryType = config.binaryType;
|
|
359
|
+
}
|
|
360
|
+
socket.onopen = () => {
|
|
361
|
+
retryAttempt = 0;
|
|
362
|
+
transition("OPEN");
|
|
363
|
+
flushQueue();
|
|
364
|
+
startHeartbeat();
|
|
365
|
+
bus.emit("open");
|
|
366
|
+
settleConnected();
|
|
367
|
+
};
|
|
368
|
+
socket.onmessage = (event) => {
|
|
369
|
+
lastInboundAt = Date.now();
|
|
370
|
+
deliver(event.data);
|
|
371
|
+
};
|
|
372
|
+
socket.onerror = (event) => {
|
|
373
|
+
lastError = event;
|
|
374
|
+
notify();
|
|
375
|
+
bus.emit("error", event);
|
|
376
|
+
};
|
|
377
|
+
socket.onclose = (event) => {
|
|
378
|
+
const wasConnecting = state === "connecting";
|
|
379
|
+
socket = null;
|
|
380
|
+
stopHeartbeat();
|
|
381
|
+
requeueIndex = 0;
|
|
382
|
+
if (reconnect !== null && isRetryable(event)) {
|
|
383
|
+
retryAttempt += 1;
|
|
384
|
+
const delay = computeDelay(retryAttempt - 1, reconnect);
|
|
385
|
+
transition("RETRY");
|
|
386
|
+
bus.emit("close", event);
|
|
387
|
+
bus.emit("reconnecting", { attempt: retryAttempt, delay });
|
|
388
|
+
retryTimer = setTimeout(() => {
|
|
389
|
+
retryTimer = null;
|
|
390
|
+
if (transition("CONNECT") !== null) {
|
|
391
|
+
openSocket();
|
|
392
|
+
}
|
|
393
|
+
}, delay);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
transition("CLOSED");
|
|
397
|
+
dropQueued();
|
|
398
|
+
bus.emit("close", event);
|
|
399
|
+
if (wasConnecting || pending) {
|
|
400
|
+
settleFailed(
|
|
401
|
+
new Error(
|
|
402
|
+
retryAttempt > 0 ? `WebSocket reconnect gave up after ${retryAttempt} attempt(s) (code ${event.code})` : `WebSocket closed before opening (code ${event.code}${event.reason ? `: ${event.reason}` : ""})`
|
|
403
|
+
)
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function deliver(raw) {
|
|
409
|
+
const decoded = codec.decode(raw);
|
|
410
|
+
if (config.schema === void 0) {
|
|
411
|
+
dispatchMessage(decoded);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
let result;
|
|
415
|
+
try {
|
|
416
|
+
result = config.schema["~standard"].validate(decoded);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
bus.emit("error", toError(error));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (result instanceof Promise) {
|
|
422
|
+
result.then(handleValidated, (error) => {
|
|
423
|
+
bus.emit("error", toError(error));
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
handleValidated(result);
|
|
428
|
+
}
|
|
429
|
+
function handleValidated(result) {
|
|
430
|
+
if (result.issues) {
|
|
431
|
+
bus.emit("error", new SchemaValidationError(result.issues));
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
dispatchMessage(result.value);
|
|
435
|
+
}
|
|
436
|
+
function dispatchMessage(data) {
|
|
437
|
+
const ctx = { data, client: api };
|
|
438
|
+
const emit = () => {
|
|
439
|
+
bus.emit("message", ctx.data);
|
|
440
|
+
};
|
|
441
|
+
try {
|
|
442
|
+
const result = runPipeline(middlewares, ctx, emit);
|
|
443
|
+
if (result instanceof Promise) {
|
|
444
|
+
result.catch((error) => {
|
|
445
|
+
bus.emit("error", toError(error));
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
} catch (error) {
|
|
449
|
+
bus.emit("error", toError(error));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function ensurePending() {
|
|
453
|
+
if (pending) {
|
|
454
|
+
return pending.promise;
|
|
455
|
+
}
|
|
456
|
+
let resolve;
|
|
457
|
+
let reject;
|
|
458
|
+
const promise = new Promise((res, rej) => {
|
|
459
|
+
resolve = res;
|
|
460
|
+
reject = rej;
|
|
461
|
+
});
|
|
462
|
+
pending = { promise, resolve, reject };
|
|
463
|
+
return promise;
|
|
464
|
+
}
|
|
465
|
+
const api = {
|
|
466
|
+
get state() {
|
|
467
|
+
return state;
|
|
468
|
+
},
|
|
469
|
+
connect() {
|
|
470
|
+
if (state === "open") {
|
|
471
|
+
return Promise.resolve();
|
|
472
|
+
}
|
|
473
|
+
if (state === "connecting") {
|
|
474
|
+
return ensurePending();
|
|
475
|
+
}
|
|
476
|
+
if (state === "closing") {
|
|
477
|
+
return Promise.reject(
|
|
478
|
+
new Error(
|
|
479
|
+
"Cannot connect() while the connection is closing"
|
|
480
|
+
)
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
if (state === "reconnecting") {
|
|
484
|
+
clearRetryTimer();
|
|
485
|
+
const promise2 = ensurePending();
|
|
486
|
+
transition("CONNECT");
|
|
487
|
+
openSocket();
|
|
488
|
+
return promise2;
|
|
489
|
+
}
|
|
490
|
+
closeRequested = false;
|
|
491
|
+
retryAttempt = 0;
|
|
492
|
+
if (transition("CONNECT") === null) {
|
|
493
|
+
return Promise.reject(
|
|
494
|
+
new Error(`Cannot connect() from state "${state}"`)
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const promise = ensurePending();
|
|
498
|
+
try {
|
|
499
|
+
openSocket();
|
|
500
|
+
} catch (error) {
|
|
501
|
+
transition("CLOSED");
|
|
502
|
+
socket = null;
|
|
503
|
+
settleFailed(toError(error));
|
|
504
|
+
}
|
|
505
|
+
return promise;
|
|
506
|
+
},
|
|
507
|
+
send(data) {
|
|
508
|
+
if (socket && state === "open") {
|
|
509
|
+
if (outboundMiddlewares.length === 0) {
|
|
510
|
+
socket.send(codec.encode(data));
|
|
511
|
+
} else {
|
|
512
|
+
transmit(data);
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (queue !== null && (state === "connecting" || state === "reconnecting")) {
|
|
517
|
+
if (queued.length >= queue.maxSize) {
|
|
518
|
+
bus.emit("drop", {
|
|
519
|
+
data: queued.shift(),
|
|
520
|
+
reason: "overflow"
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
queued.push(data);
|
|
524
|
+
notify();
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
throw new Error(
|
|
528
|
+
`Cannot send: connection is not open (state: "${state}")`
|
|
529
|
+
);
|
|
530
|
+
},
|
|
531
|
+
close(code, reason) {
|
|
532
|
+
closeRequested = true;
|
|
533
|
+
clearRetryTimer();
|
|
534
|
+
stopHeartbeat();
|
|
535
|
+
if (retryAttempt !== 0) {
|
|
536
|
+
retryAttempt = 0;
|
|
537
|
+
notify();
|
|
538
|
+
}
|
|
539
|
+
dropQueued();
|
|
540
|
+
if (state === "reconnecting") {
|
|
541
|
+
transition("CLOSE_REQUESTED");
|
|
542
|
+
settleFailed(
|
|
543
|
+
new Error("close() called before the connection opened")
|
|
544
|
+
);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (!socket) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
transition("CLOSE_REQUESTED");
|
|
551
|
+
socket.close(code, reason);
|
|
552
|
+
},
|
|
553
|
+
on(event, handler) {
|
|
554
|
+
bus.on(event, handler);
|
|
555
|
+
return () => bus.off(event, handler);
|
|
556
|
+
},
|
|
557
|
+
use(middleware) {
|
|
558
|
+
if (typeof middleware === "function") {
|
|
559
|
+
middlewares.push(middleware);
|
|
560
|
+
return api;
|
|
561
|
+
}
|
|
562
|
+
if (middleware.inbound) {
|
|
563
|
+
middlewares.push(middleware.inbound);
|
|
564
|
+
}
|
|
565
|
+
if (middleware.outbound) {
|
|
566
|
+
outboundMiddlewares.push(middleware.outbound);
|
|
567
|
+
}
|
|
568
|
+
return api;
|
|
569
|
+
},
|
|
570
|
+
getState() {
|
|
571
|
+
if (snapshot === null) {
|
|
572
|
+
snapshot = Object.freeze({
|
|
573
|
+
state,
|
|
574
|
+
lastError,
|
|
575
|
+
retryAttempt,
|
|
576
|
+
queueLength: queued.length
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return snapshot;
|
|
580
|
+
},
|
|
581
|
+
subscribe(listener) {
|
|
582
|
+
subscribers.add(listener);
|
|
583
|
+
return () => {
|
|
584
|
+
subscribers.delete(listener);
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
return api;
|
|
589
|
+
}
|
|
590
|
+
function toError(value) {
|
|
591
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/helpers/binding.ts
|
|
595
|
+
function resolveSource(source) {
|
|
596
|
+
if (isClient(source)) {
|
|
597
|
+
return { client: source, owned: false };
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
client: client(source),
|
|
601
|
+
owned: true
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function isClient(source) {
|
|
605
|
+
return typeof source.subscribe === "function";
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/react.ts
|
|
609
|
+
function useWebSocket(source) {
|
|
610
|
+
const [{ client: ws, owned }] = react.useState(() => resolveSource(source));
|
|
611
|
+
const snapshot = react.useSyncExternalStore(
|
|
612
|
+
ws.subscribe,
|
|
613
|
+
ws.getState,
|
|
614
|
+
ws.getState
|
|
615
|
+
);
|
|
616
|
+
const [lastMessage, setLastMessage] = react.useState(void 0);
|
|
617
|
+
react.useEffect(
|
|
618
|
+
// The updater form guards against function-valued messages, which
|
|
619
|
+
// setState would otherwise invoke instead of store.
|
|
620
|
+
() => ws.on("message", (msg) => setLastMessage(() => msg)),
|
|
621
|
+
[ws]
|
|
622
|
+
);
|
|
623
|
+
react.useEffect(() => {
|
|
624
|
+
if (!owned) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
ws.connect().catch(() => {
|
|
628
|
+
});
|
|
629
|
+
return () => ws.close();
|
|
630
|
+
}, [ws, owned]);
|
|
631
|
+
return {
|
|
632
|
+
client: ws,
|
|
633
|
+
state: snapshot.state,
|
|
634
|
+
lastError: snapshot.lastError,
|
|
635
|
+
retryAttempt: snapshot.retryAttempt,
|
|
636
|
+
queueLength: snapshot.queueLength,
|
|
637
|
+
lastMessage,
|
|
638
|
+
send: ws.send,
|
|
639
|
+
connect: ws.connect,
|
|
640
|
+
close: ws.close
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
exports.useWebSocket = useWebSocket;
|
|
645
|
+
//# sourceMappingURL=react.cjs.map
|
|
646
|
+
//# sourceMappingURL=react.cjs.map
|