cynx-lib 1.3.40 → 1.3.42
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/index.cjs +18 -0
- package/package.json +31 -7
- package/ui/index.cjs +182 -0
- package/utils/index.cjs +80 -0
- package/utils/utils/db.cjs +779 -0
- package/utils/utils/duration.cjs +119 -0
- package/utils/utils/oidc.cjs +164 -0
- package/utils/utils/realtime.cjs +881 -0
- package/utils/utils/session.cjs +61 -0
- package/utils/utils/storage.cjs +344 -0
- package/utils/utils/storage_manager.cjs +1177 -0
- package/utils/utils/storage_manager.js +9 -2
- package/utils/utils/tz.cjs +542 -0
- package/utils/utils/ui.cjs +105 -0
- package/utils/utils/uid.cjs +187 -0
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var realtime_exports = {};
|
|
19
|
+
__export(realtime_exports, {
|
|
20
|
+
ConnectedClient: () => ConnectedClient,
|
|
21
|
+
EventBus: () => EventBus,
|
|
22
|
+
RealtimeServer: () => RealtimeServer,
|
|
23
|
+
RealtimeSocket: () => RealtimeSocket,
|
|
24
|
+
SSEStream: () => SSEStream,
|
|
25
|
+
SmartPoller: () => SmartPoller,
|
|
26
|
+
TabChannel: () => TabChannel,
|
|
27
|
+
createEventBus: () => createEventBus,
|
|
28
|
+
createPoller: () => createPoller,
|
|
29
|
+
createRealtimeServer: () => createRealtimeServer,
|
|
30
|
+
createRealtimeSocket: () => createRealtimeSocket,
|
|
31
|
+
createSSE: () => createSSE,
|
|
32
|
+
createTabChannel: () => createTabChannel,
|
|
33
|
+
default: () => realtime_default,
|
|
34
|
+
parseAIStream: () => parseAIStream
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(realtime_exports);
|
|
37
|
+
var import_uid = require("./uid.js");
|
|
38
|
+
const isBrowser = typeof window !== "undefined";
|
|
39
|
+
const isDocAvailable = typeof document !== "undefined";
|
|
40
|
+
function safeJSONParse(str, fallback = null) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(str);
|
|
43
|
+
} catch {
|
|
44
|
+
return fallback !== null ? fallback : str;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getWebSocketConstructor(customWs) {
|
|
48
|
+
if (customWs) return customWs;
|
|
49
|
+
if (typeof WebSocket !== "undefined") return WebSocket;
|
|
50
|
+
if (typeof globalThis !== "undefined" && globalThis.WebSocket) return globalThis.WebSocket;
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
class EventBus {
|
|
54
|
+
constructor() {
|
|
55
|
+
this._listeners = /* @__PURE__ */ new Map();
|
|
56
|
+
}
|
|
57
|
+
on(event, callback) {
|
|
58
|
+
if (typeof callback !== "function") return () => {
|
|
59
|
+
};
|
|
60
|
+
if (!this._listeners.has(event)) {
|
|
61
|
+
this._listeners.set(event, /* @__PURE__ */ new Set());
|
|
62
|
+
}
|
|
63
|
+
this._listeners.get(event).add(callback);
|
|
64
|
+
return () => this.off(event, callback);
|
|
65
|
+
}
|
|
66
|
+
once(event, callback) {
|
|
67
|
+
const unsub = this.on(event, (...args) => {
|
|
68
|
+
unsub();
|
|
69
|
+
callback(...args);
|
|
70
|
+
});
|
|
71
|
+
return unsub;
|
|
72
|
+
}
|
|
73
|
+
off(event, callback) {
|
|
74
|
+
if (!this._listeners.has(event)) return;
|
|
75
|
+
if (!callback) {
|
|
76
|
+
this._listeners.delete(event);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const set = this._listeners.get(event);
|
|
80
|
+
set.delete(callback);
|
|
81
|
+
if (set.size === 0) this._listeners.delete(event);
|
|
82
|
+
}
|
|
83
|
+
emit(event, ...args) {
|
|
84
|
+
if (this._listeners.has(event)) {
|
|
85
|
+
for (const cb of this._listeners.get(event)) {
|
|
86
|
+
try {
|
|
87
|
+
cb(...args);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(`[cynx/realtime] Error in handler for event "${event}":`, err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (event !== "*" && this._listeners.has("*")) {
|
|
94
|
+
for (const cb of this._listeners.get("*")) {
|
|
95
|
+
try {
|
|
96
|
+
cb(event, ...args);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error("[cynx/realtime] Error in wildcard handler:", err);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
clear() {
|
|
104
|
+
this._listeners.clear();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function createEventBus() {
|
|
108
|
+
return new EventBus();
|
|
109
|
+
}
|
|
110
|
+
class RealtimeSocket {
|
|
111
|
+
constructor(url, options = {}) {
|
|
112
|
+
this.url = url;
|
|
113
|
+
this.options = {
|
|
114
|
+
reconnect: true,
|
|
115
|
+
maxRetries: Infinity,
|
|
116
|
+
initialRetryDelay: 1e3,
|
|
117
|
+
maxRetryDelay: 3e4,
|
|
118
|
+
backoffFactor: 1.5,
|
|
119
|
+
jitter: true,
|
|
120
|
+
protocols: void 0,
|
|
121
|
+
heartbeat: {
|
|
122
|
+
enabled: true,
|
|
123
|
+
interval: 25e3,
|
|
124
|
+
pingPayload: JSON.stringify({ type: "ping" }),
|
|
125
|
+
pongPattern: "pong",
|
|
126
|
+
timeout: 1e4
|
|
127
|
+
},
|
|
128
|
+
queueOffline: true,
|
|
129
|
+
maxQueueSize: 100,
|
|
130
|
+
autoConnect: true,
|
|
131
|
+
WebSocket: void 0,
|
|
132
|
+
...options
|
|
133
|
+
};
|
|
134
|
+
this.events = new EventBus();
|
|
135
|
+
this.ws = null;
|
|
136
|
+
this.status = "DISCONNECTED";
|
|
137
|
+
this.retries = 0;
|
|
138
|
+
this._offlineQueue = [];
|
|
139
|
+
this._pendingRequests = /* @__PURE__ */ new Map();
|
|
140
|
+
this._heartbeatTimer = null;
|
|
141
|
+
this._heartbeatTimeoutTimer = null;
|
|
142
|
+
this._reconnectTimer = null;
|
|
143
|
+
this._isExplicitClose = false;
|
|
144
|
+
if (this.options.autoConnect) {
|
|
145
|
+
this.connect();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
connect() {
|
|
149
|
+
if (!isBrowser && !this.options.WebSocket && typeof globalThis.WebSocket === "undefined") {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (this.ws && (this.ws.readyState === 0 || this.ws.readyState === 1)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this._isExplicitClose = false;
|
|
156
|
+
this._setStatus(this.retries > 0 ? "RECONNECTING" : "CONNECTING");
|
|
157
|
+
const WS = getWebSocketConstructor(this.options.WebSocket);
|
|
158
|
+
if (!WS) {
|
|
159
|
+
console.warn("[cynx/realtime] WebSocket implementation not found in this environment.");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const targetUrl = typeof this.url === "function" ? this.url() : this.url;
|
|
164
|
+
this.ws = this.options.protocols ? new WS(targetUrl, this.options.protocols) : new WS(targetUrl);
|
|
165
|
+
this.ws.onopen = (e) => this._onOpen(e);
|
|
166
|
+
this.ws.onmessage = (e) => this._onMessage(e);
|
|
167
|
+
this.ws.onerror = (e) => this._onError(e);
|
|
168
|
+
this.ws.onclose = (e) => this._onClose(e);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
this._handleReconnect();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
disconnect(code = 1e3, reason = "Client disconnected") {
|
|
174
|
+
this._isExplicitClose = true;
|
|
175
|
+
this._clearTimers();
|
|
176
|
+
if (this.ws) {
|
|
177
|
+
this.ws.close(code, reason);
|
|
178
|
+
this.ws = null;
|
|
179
|
+
}
|
|
180
|
+
this._setStatus("DISCONNECTED");
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Send data or a typed event payload.
|
|
184
|
+
* If offline and queueOffline=true, queues the message until reconnect.
|
|
185
|
+
*/
|
|
186
|
+
send(eventOrData, payload = void 0) {
|
|
187
|
+
let message;
|
|
188
|
+
if (payload !== void 0) {
|
|
189
|
+
message = JSON.stringify({ event: eventOrData, data: payload });
|
|
190
|
+
} else if (typeof eventOrData === "object") {
|
|
191
|
+
message = JSON.stringify(eventOrData);
|
|
192
|
+
} else {
|
|
193
|
+
message = String(eventOrData);
|
|
194
|
+
}
|
|
195
|
+
if (this.isConnected()) {
|
|
196
|
+
this.ws.send(message);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (this.options.queueOffline) {
|
|
200
|
+
if (this._offlineQueue.length >= this.options.maxQueueSize) {
|
|
201
|
+
this._offlineQueue.shift();
|
|
202
|
+
}
|
|
203
|
+
this._offlineQueue.push(message);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Perform a Request-Response interaction over WebSocket.
|
|
210
|
+
* Attaches a correlation `requestId` and resolves when server replies.
|
|
211
|
+
*/
|
|
212
|
+
request(action, data = {}, { timeout = 1e4 } = {}) {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
215
|
+
const timer = setTimeout(() => {
|
|
216
|
+
if (this._pendingRequests.has(requestId)) {
|
|
217
|
+
this._pendingRequests.delete(requestId);
|
|
218
|
+
reject(new Error(`[cynx/realtime] Request "${action}" timed out after ${timeout}ms`));
|
|
219
|
+
}
|
|
220
|
+
}, timeout);
|
|
221
|
+
this._pendingRequests.set(requestId, { resolve, reject, timer });
|
|
222
|
+
const sent = this.send({
|
|
223
|
+
action,
|
|
224
|
+
requestId,
|
|
225
|
+
data
|
|
226
|
+
});
|
|
227
|
+
if (!sent && !this.options.queueOffline) {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
this._pendingRequests.delete(requestId);
|
|
230
|
+
reject(new Error("[cynx/realtime] WebSocket not connected"));
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
on(event, callback) {
|
|
235
|
+
return this.events.on(event, callback);
|
|
236
|
+
}
|
|
237
|
+
once(event, callback) {
|
|
238
|
+
return this.events.once(event, callback);
|
|
239
|
+
}
|
|
240
|
+
off(event, callback) {
|
|
241
|
+
this.events.off(event, callback);
|
|
242
|
+
}
|
|
243
|
+
isConnected() {
|
|
244
|
+
return this.ws !== null && this.ws.readyState === 1;
|
|
245
|
+
}
|
|
246
|
+
getState() {
|
|
247
|
+
return this.status;
|
|
248
|
+
}
|
|
249
|
+
// ── Private Handlers ──
|
|
250
|
+
_setStatus(status) {
|
|
251
|
+
if (this.status !== status) {
|
|
252
|
+
this.status = status;
|
|
253
|
+
this.events.emit("status", status);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
_onOpen(event) {
|
|
257
|
+
this.retries = 0;
|
|
258
|
+
this._setStatus("CONNECTED");
|
|
259
|
+
this.events.emit("open", event);
|
|
260
|
+
this.events.emit("connect", event);
|
|
261
|
+
this._startHeartbeat();
|
|
262
|
+
this._flushQueue();
|
|
263
|
+
}
|
|
264
|
+
_onMessage(event) {
|
|
265
|
+
this._resetHeartbeatTimeout();
|
|
266
|
+
const raw = event.data;
|
|
267
|
+
const parsed = safeJSONParse(raw, null);
|
|
268
|
+
if (parsed && typeof parsed === "object" && parsed.requestId && this._pendingRequests.has(parsed.requestId)) {
|
|
269
|
+
const req = this._pendingRequests.get(parsed.requestId);
|
|
270
|
+
clearTimeout(req.timer);
|
|
271
|
+
this._pendingRequests.delete(parsed.requestId);
|
|
272
|
+
if (parsed.error) {
|
|
273
|
+
req.reject(parsed.error);
|
|
274
|
+
} else {
|
|
275
|
+
req.resolve(parsed.data !== void 0 ? parsed.data : parsed);
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (parsed && typeof parsed === "object") {
|
|
280
|
+
const eventName = parsed.event || parsed.type;
|
|
281
|
+
if (eventName) {
|
|
282
|
+
this.events.emit(eventName, parsed.data !== void 0 ? parsed.data : parsed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
this.events.emit("message", parsed !== null ? parsed : raw, raw);
|
|
286
|
+
}
|
|
287
|
+
_onError(event) {
|
|
288
|
+
this.events.emit("error", event);
|
|
289
|
+
}
|
|
290
|
+
_onClose(event) {
|
|
291
|
+
this._clearTimers();
|
|
292
|
+
this._setStatus("DISCONNECTED");
|
|
293
|
+
this.events.emit("close", event);
|
|
294
|
+
this.events.emit("disconnect", event);
|
|
295
|
+
if (!this._isExplicitClose && this.options.reconnect) {
|
|
296
|
+
this._handleReconnect();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
_flushQueue() {
|
|
300
|
+
while (this._offlineQueue.length > 0 && this.isConnected()) {
|
|
301
|
+
const msg = this._offlineQueue.shift();
|
|
302
|
+
this.ws.send(msg);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
_handleReconnect() {
|
|
306
|
+
if (this.retries >= this.options.maxRetries) {
|
|
307
|
+
this.events.emit("max_retries_reached", this.retries);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
let delay = this.options.initialRetryDelay * Math.pow(this.options.backoffFactor, this.retries);
|
|
311
|
+
delay = Math.min(delay, this.options.maxRetryDelay);
|
|
312
|
+
if (this.options.jitter) {
|
|
313
|
+
delay = delay * (0.8 + Math.random() * 0.4);
|
|
314
|
+
}
|
|
315
|
+
this.retries += 1;
|
|
316
|
+
this._setStatus("RECONNECTING");
|
|
317
|
+
this.events.emit("reconnect_attempt", { attempt: this.retries, delay: Math.round(delay) });
|
|
318
|
+
clearTimeout(this._reconnectTimer);
|
|
319
|
+
this._reconnectTimer = setTimeout(() => {
|
|
320
|
+
this.connect();
|
|
321
|
+
}, delay);
|
|
322
|
+
}
|
|
323
|
+
_startHeartbeat() {
|
|
324
|
+
if (!this.options.heartbeat || !this.options.heartbeat.enabled) return;
|
|
325
|
+
this._clearHeartbeat();
|
|
326
|
+
this._heartbeatTimer = setInterval(() => {
|
|
327
|
+
if (this.isConnected()) {
|
|
328
|
+
try {
|
|
329
|
+
this.ws.send(this.options.heartbeat.pingPayload);
|
|
330
|
+
this._heartbeatTimeoutTimer = setTimeout(() => {
|
|
331
|
+
if (this.ws) {
|
|
332
|
+
this.ws.close(4e3, "Heartbeat timeout");
|
|
333
|
+
}
|
|
334
|
+
}, this.options.heartbeat.timeout);
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}, this.options.heartbeat.interval);
|
|
339
|
+
}
|
|
340
|
+
_resetHeartbeatTimeout() {
|
|
341
|
+
if (this._heartbeatTimeoutTimer) {
|
|
342
|
+
clearTimeout(this._heartbeatTimeoutTimer);
|
|
343
|
+
this._heartbeatTimeoutTimer = null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
_clearHeartbeat() {
|
|
347
|
+
if (this._heartbeatTimer) clearInterval(this._heartbeatTimer);
|
|
348
|
+
if (this._heartbeatTimeoutTimer) clearTimeout(this._heartbeatTimeoutTimer);
|
|
349
|
+
this._heartbeatTimer = null;
|
|
350
|
+
this._heartbeatTimeoutTimer = null;
|
|
351
|
+
}
|
|
352
|
+
_clearTimers() {
|
|
353
|
+
this._clearHeartbeat();
|
|
354
|
+
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
|
|
355
|
+
this._reconnectTimer = null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function createRealtimeSocket(url, options) {
|
|
359
|
+
return new RealtimeSocket(url, options);
|
|
360
|
+
}
|
|
361
|
+
class SSEStream {
|
|
362
|
+
constructor(url, options = {}) {
|
|
363
|
+
this.url = url;
|
|
364
|
+
this.options = {
|
|
365
|
+
withCredentials: false,
|
|
366
|
+
reconnect: true,
|
|
367
|
+
maxRetries: Infinity,
|
|
368
|
+
retryDelay: 3e3,
|
|
369
|
+
autoConnect: true,
|
|
370
|
+
...options
|
|
371
|
+
};
|
|
372
|
+
this.events = new EventBus();
|
|
373
|
+
this.eventSource = null;
|
|
374
|
+
this._isClosed = false;
|
|
375
|
+
this.retries = 0;
|
|
376
|
+
if (this.options.autoConnect) {
|
|
377
|
+
this.connect();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
connect() {
|
|
381
|
+
if (typeof EventSource === "undefined") {
|
|
382
|
+
console.warn("[cynx/realtime] EventSource is not supported in this environment.");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (this.eventSource) this.close();
|
|
386
|
+
this._isClosed = false;
|
|
387
|
+
this.eventSource = new EventSource(this.url, {
|
|
388
|
+
withCredentials: this.options.withCredentials
|
|
389
|
+
});
|
|
390
|
+
this.eventSource.onopen = (e) => {
|
|
391
|
+
this.retries = 0;
|
|
392
|
+
this.events.emit("open", e);
|
|
393
|
+
};
|
|
394
|
+
this.eventSource.onmessage = (e) => {
|
|
395
|
+
const parsed = safeJSONParse(e.data, e.data);
|
|
396
|
+
this.events.emit("message", parsed, e);
|
|
397
|
+
};
|
|
398
|
+
this.eventSource.onerror = (e) => {
|
|
399
|
+
this.events.emit("error", e);
|
|
400
|
+
if (this.eventSource.readyState === 2 && !this._isClosed && this.options.reconnect) {
|
|
401
|
+
this.eventSource.close();
|
|
402
|
+
this._retry();
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
addEventListener(eventName, handler) {
|
|
407
|
+
if (!this.eventSource) return;
|
|
408
|
+
this.eventSource.addEventListener(eventName, (e) => {
|
|
409
|
+
const parsed = safeJSONParse(e.data, e.data);
|
|
410
|
+
handler(parsed, e);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
on(event, callback) {
|
|
414
|
+
return this.events.on(event, callback);
|
|
415
|
+
}
|
|
416
|
+
close() {
|
|
417
|
+
this._isClosed = true;
|
|
418
|
+
if (this.eventSource) {
|
|
419
|
+
this.eventSource.close();
|
|
420
|
+
this.eventSource = null;
|
|
421
|
+
}
|
|
422
|
+
this.events.emit("close");
|
|
423
|
+
}
|
|
424
|
+
_retry() {
|
|
425
|
+
if (this.retries >= this.options.maxRetries) return;
|
|
426
|
+
this.retries += 1;
|
|
427
|
+
setTimeout(() => {
|
|
428
|
+
if (!this._isClosed) this.connect();
|
|
429
|
+
}, this.options.retryDelay);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function createSSE(url, options) {
|
|
433
|
+
return new SSEStream(url, options);
|
|
434
|
+
}
|
|
435
|
+
async function parseAIStream(response, { onChunk, onDone, onError } = {}) {
|
|
436
|
+
if (!response || !response.body) {
|
|
437
|
+
throw new Error("[cynx/realtime] Invalid response object for streaming");
|
|
438
|
+
}
|
|
439
|
+
const reader = response.body.getReader();
|
|
440
|
+
const decoder = new TextDecoder("utf-8");
|
|
441
|
+
let accumulated = "";
|
|
442
|
+
try {
|
|
443
|
+
while (true) {
|
|
444
|
+
const { done, value } = await reader.read();
|
|
445
|
+
if (done) break;
|
|
446
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
447
|
+
accumulated += chunk;
|
|
448
|
+
if (onChunk) onChunk(chunk, accumulated);
|
|
449
|
+
}
|
|
450
|
+
if (onDone) onDone(accumulated);
|
|
451
|
+
return accumulated;
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (onError) onError(err);
|
|
454
|
+
throw err;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
class TabChannel {
|
|
458
|
+
constructor(name = "cynx_channel") {
|
|
459
|
+
this.name = name;
|
|
460
|
+
this.events = new EventBus();
|
|
461
|
+
this.hasBroadcast = isBrowser && typeof BroadcastChannel !== "undefined";
|
|
462
|
+
this._bc = null;
|
|
463
|
+
this._storageHandler = null;
|
|
464
|
+
if (this.hasBroadcast) {
|
|
465
|
+
this._bc = new BroadcastChannel(this.name);
|
|
466
|
+
this._bc.onmessage = (e) => {
|
|
467
|
+
const { event, data } = e.data || {};
|
|
468
|
+
if (event) {
|
|
469
|
+
this.events.emit(event, data);
|
|
470
|
+
}
|
|
471
|
+
this.events.emit("message", e.data);
|
|
472
|
+
};
|
|
473
|
+
} else if (isBrowser) {
|
|
474
|
+
this._storageKey = `cynx_tab_msg_${this.name}`;
|
|
475
|
+
this._storageHandler = (e) => {
|
|
476
|
+
if (e.key === this._storageKey && e.newValue) {
|
|
477
|
+
const parsed = safeJSONParse(e.newValue, {});
|
|
478
|
+
const { event, data } = parsed;
|
|
479
|
+
if (event) {
|
|
480
|
+
this.events.emit(event, data);
|
|
481
|
+
}
|
|
482
|
+
this.events.emit("message", parsed);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
window.addEventListener("storage", this._storageHandler);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
emit(event, data = {}) {
|
|
489
|
+
const payload = { event, data, _t: Date.now() };
|
|
490
|
+
if (this._bc) {
|
|
491
|
+
this._bc.postMessage(payload);
|
|
492
|
+
} else if (isBrowser && window.localStorage) {
|
|
493
|
+
try {
|
|
494
|
+
localStorage.setItem(this._storageKey, JSON.stringify(payload));
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
on(event, callback) {
|
|
500
|
+
return this.events.on(event, callback);
|
|
501
|
+
}
|
|
502
|
+
off(event, callback) {
|
|
503
|
+
this.events.off(event, callback);
|
|
504
|
+
}
|
|
505
|
+
close() {
|
|
506
|
+
if (this._bc) {
|
|
507
|
+
this._bc.close();
|
|
508
|
+
this._bc = null;
|
|
509
|
+
}
|
|
510
|
+
if (this._storageHandler && isBrowser) {
|
|
511
|
+
window.removeEventListener("storage", this._storageHandler);
|
|
512
|
+
this._storageHandler = null;
|
|
513
|
+
}
|
|
514
|
+
this.events.clear();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function createTabChannel(channelName) {
|
|
518
|
+
return new TabChannel(channelName);
|
|
519
|
+
}
|
|
520
|
+
class SmartPoller {
|
|
521
|
+
constructor(taskFn, options = {}) {
|
|
522
|
+
this.taskFn = taskFn;
|
|
523
|
+
this.options = {
|
|
524
|
+
interval: 5e3,
|
|
525
|
+
immediate: true,
|
|
526
|
+
pauseWhenHidden: true,
|
|
527
|
+
...options
|
|
528
|
+
};
|
|
529
|
+
this.isRunning = false;
|
|
530
|
+
this._timer = null;
|
|
531
|
+
this._visibilityHandler = null;
|
|
532
|
+
if (isDocAvailable && this.options.pauseWhenHidden) {
|
|
533
|
+
this._visibilityHandler = () => {
|
|
534
|
+
if (document.hidden) {
|
|
535
|
+
this._stopTimer();
|
|
536
|
+
} else if (this.isRunning) {
|
|
537
|
+
this._tick();
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
document.addEventListener("visibilitychange", this._visibilityHandler);
|
|
541
|
+
}
|
|
542
|
+
if (this.options.immediate) {
|
|
543
|
+
this.start();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
start() {
|
|
547
|
+
if (this.isRunning) return;
|
|
548
|
+
this.isRunning = true;
|
|
549
|
+
this._tick();
|
|
550
|
+
}
|
|
551
|
+
stop() {
|
|
552
|
+
this.isRunning = false;
|
|
553
|
+
this._stopTimer();
|
|
554
|
+
}
|
|
555
|
+
destroy() {
|
|
556
|
+
this.stop();
|
|
557
|
+
if (this._visibilityHandler && isDocAvailable) {
|
|
558
|
+
document.removeEventListener("visibilitychange", this._visibilityHandler);
|
|
559
|
+
this._visibilityHandler = null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async _tick() {
|
|
563
|
+
this._stopTimer();
|
|
564
|
+
if (!this.isRunning) return;
|
|
565
|
+
if (isDocAvailable && this.options.pauseWhenHidden && document.hidden) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
await this.taskFn();
|
|
570
|
+
} catch (err) {
|
|
571
|
+
console.error("[cynx/realtime] Error during poll execution:", err);
|
|
572
|
+
}
|
|
573
|
+
if (this.isRunning) {
|
|
574
|
+
this._timer = setTimeout(() => this._tick(), this.options.interval);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
_stopTimer() {
|
|
578
|
+
if (this._timer) {
|
|
579
|
+
clearTimeout(this._timer);
|
|
580
|
+
this._timer = null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function createPoller(taskFn, options) {
|
|
585
|
+
return new SmartPoller(taskFn, options);
|
|
586
|
+
}
|
|
587
|
+
class ConnectedClient {
|
|
588
|
+
constructor(socket, server, options = {}) {
|
|
589
|
+
this.id = options.id || (typeof import_uid.uid === "function" ? (0, import_uid.uid)(12) : `client_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`);
|
|
590
|
+
this.socket = socket;
|
|
591
|
+
this.server = server;
|
|
592
|
+
this.req = options.req || null;
|
|
593
|
+
this.isAlive = true;
|
|
594
|
+
this.rooms = /* @__PURE__ */ new Set();
|
|
595
|
+
this.data = {};
|
|
596
|
+
this.events = new EventBus();
|
|
597
|
+
this._bindEvents();
|
|
598
|
+
}
|
|
599
|
+
send(eventOrData, payload = void 0) {
|
|
600
|
+
if (this.socket.readyState !== 1) return false;
|
|
601
|
+
let message;
|
|
602
|
+
if (payload !== void 0) {
|
|
603
|
+
message = JSON.stringify({ event: eventOrData, data: payload });
|
|
604
|
+
} else if (typeof eventOrData === "object") {
|
|
605
|
+
message = JSON.stringify(eventOrData);
|
|
606
|
+
} else {
|
|
607
|
+
message = String(eventOrData);
|
|
608
|
+
}
|
|
609
|
+
this.socket.send(message);
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
612
|
+
reply(requestId, data = null, error = null) {
|
|
613
|
+
return this.send({ requestId, data, error });
|
|
614
|
+
}
|
|
615
|
+
join(room) {
|
|
616
|
+
this.server.join(this, room);
|
|
617
|
+
return this;
|
|
618
|
+
}
|
|
619
|
+
leave(room) {
|
|
620
|
+
this.server.leave(this, room);
|
|
621
|
+
return this;
|
|
622
|
+
}
|
|
623
|
+
broadcast(eventOrData, payload = void 0) {
|
|
624
|
+
this.server.broadcast(eventOrData, payload, this);
|
|
625
|
+
return this;
|
|
626
|
+
}
|
|
627
|
+
broadcastTo(room, eventOrData, payload = void 0) {
|
|
628
|
+
this.server.broadcastTo(room, eventOrData, payload, this);
|
|
629
|
+
return this;
|
|
630
|
+
}
|
|
631
|
+
on(event, callback) {
|
|
632
|
+
return this.events.on(event, callback);
|
|
633
|
+
}
|
|
634
|
+
once(event, callback) {
|
|
635
|
+
return this.events.once(event, callback);
|
|
636
|
+
}
|
|
637
|
+
off(event, callback) {
|
|
638
|
+
this.events.off(event, callback);
|
|
639
|
+
}
|
|
640
|
+
close(code = 1e3, reason = "Server closed connection") {
|
|
641
|
+
if (this.socket.readyState === 1 || this.socket.readyState === 0) {
|
|
642
|
+
this.socket.close(code, reason);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
_bindEvents() {
|
|
646
|
+
if (typeof this.socket.on === "function") {
|
|
647
|
+
this.socket.on("message", (raw) => {
|
|
648
|
+
this.isAlive = true;
|
|
649
|
+
const str = raw instanceof Buffer ? raw.toString("utf-8") : String(raw);
|
|
650
|
+
const parsed = safeJSONParse(str, null);
|
|
651
|
+
if (parsed && typeof parsed === "object") {
|
|
652
|
+
if (parsed.type === "ping") {
|
|
653
|
+
this.send({ type: "pong" });
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const eventName = parsed.event || parsed.type;
|
|
657
|
+
if (eventName) {
|
|
658
|
+
this.events.emit(eventName, parsed.data !== void 0 ? parsed.data : parsed, this);
|
|
659
|
+
this.server.events.emit(eventName, parsed.data !== void 0 ? parsed.data : parsed, this);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
this.events.emit("message", parsed !== null ? parsed : str, this);
|
|
663
|
+
this.server.events.emit("message", parsed !== null ? parsed : str, this);
|
|
664
|
+
});
|
|
665
|
+
this.socket.on("pong", () => {
|
|
666
|
+
this.isAlive = true;
|
|
667
|
+
});
|
|
668
|
+
this.socket.on("close", (code, reason) => {
|
|
669
|
+
this.server._handleClientDisconnect(this, code, reason);
|
|
670
|
+
});
|
|
671
|
+
this.socket.on("error", (err) => {
|
|
672
|
+
this.events.emit("error", err, this);
|
|
673
|
+
this.server.events.emit("client_error", err, this);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
class RealtimeServer {
|
|
679
|
+
constructor(options = {}) {
|
|
680
|
+
this.options = {
|
|
681
|
+
server: void 0,
|
|
682
|
+
// HTTP/HTTPS server instance
|
|
683
|
+
port: void 0,
|
|
684
|
+
// Standalone port
|
|
685
|
+
path: "/ws",
|
|
686
|
+
heartbeatInterval: 3e4,
|
|
687
|
+
WebSocketServer: void 0,
|
|
688
|
+
// ws.WebSocketServer constructor
|
|
689
|
+
wss: void 0,
|
|
690
|
+
// Existing WebSocketServer instance
|
|
691
|
+
authenticate: void 0,
|
|
692
|
+
// (req) => Promise<boolean|object>
|
|
693
|
+
...options
|
|
694
|
+
};
|
|
695
|
+
this.clients = /* @__PURE__ */ new Map();
|
|
696
|
+
this.rooms = /* @__PURE__ */ new Map();
|
|
697
|
+
this.events = new EventBus();
|
|
698
|
+
this.wss = null;
|
|
699
|
+
this._heartbeatTimer = null;
|
|
700
|
+
if (this.options.wss) {
|
|
701
|
+
this.attachWSS(this.options.wss);
|
|
702
|
+
} else if (this.options.server || this.options.port || this.options.WebSocketServer) {
|
|
703
|
+
this.initServer();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
initServer() {
|
|
707
|
+
let WSS = this.options.WebSocketServer;
|
|
708
|
+
if (!WSS && typeof globalThis !== "undefined" && globalThis.WebSocketServer) {
|
|
709
|
+
WSS = globalThis.WebSocketServer;
|
|
710
|
+
}
|
|
711
|
+
if (!WSS) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
"[cynx/realtime] WebSocketServer constructor not found. Pass `{ WebSocketServer: ws.WebSocketServer }` or an existing `wss` instance."
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const wssOptions = {};
|
|
717
|
+
if (this.options.server) wssOptions.server = this.options.server;
|
|
718
|
+
if (this.options.port) wssOptions.port = this.options.port;
|
|
719
|
+
if (this.options.path) wssOptions.path = this.options.path;
|
|
720
|
+
this.wss = new WSS(wssOptions);
|
|
721
|
+
this.attachWSS(this.wss);
|
|
722
|
+
}
|
|
723
|
+
attachWSS(wss) {
|
|
724
|
+
this.wss = wss;
|
|
725
|
+
this.wss.on("connection", async (rawSocket, req) => {
|
|
726
|
+
try {
|
|
727
|
+
if (typeof this.options.authenticate === "function") {
|
|
728
|
+
const authResult = await this.options.authenticate(req);
|
|
729
|
+
if (!authResult) {
|
|
730
|
+
rawSocket.close(4001, "Unauthorized");
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (typeof authResult === "object") {
|
|
734
|
+
rawSocket._authData = authResult;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const client = new ConnectedClient(rawSocket, this, { req });
|
|
738
|
+
if (rawSocket._authData) {
|
|
739
|
+
client.data = { ...rawSocket._authData };
|
|
740
|
+
}
|
|
741
|
+
this.clients.set(client.id, client);
|
|
742
|
+
this.events.emit("connection", client);
|
|
743
|
+
client.events.emit("connect", client);
|
|
744
|
+
} catch (err) {
|
|
745
|
+
this.events.emit("error", err);
|
|
746
|
+
rawSocket.close(4e3, "Connection error");
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
this.wss.on("error", (err) => {
|
|
750
|
+
this.events.emit("error", err);
|
|
751
|
+
});
|
|
752
|
+
this._startHeartbeat();
|
|
753
|
+
}
|
|
754
|
+
broadcast(eventOrData, payload = void 0, excludeClient = null) {
|
|
755
|
+
const excludeId = excludeClient ? excludeClient.id || excludeClient : null;
|
|
756
|
+
for (const client of this.clients.values()) {
|
|
757
|
+
if (client.id !== excludeId) {
|
|
758
|
+
client.send(eventOrData, payload);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
broadcastTo(room, eventOrData, payload = void 0, excludeClient = null) {
|
|
763
|
+
if (!this.rooms.has(room)) return;
|
|
764
|
+
const excludeId = excludeClient ? excludeClient.id || excludeClient : null;
|
|
765
|
+
for (const client of this.rooms.get(room)) {
|
|
766
|
+
if (client.id !== excludeId) {
|
|
767
|
+
client.send(eventOrData, payload);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
join(clientOrId, room) {
|
|
772
|
+
const client = typeof clientOrId === "string" ? this.clients.get(clientOrId) : clientOrId;
|
|
773
|
+
if (!client) return;
|
|
774
|
+
if (!this.rooms.has(room)) {
|
|
775
|
+
this.rooms.set(room, /* @__PURE__ */ new Set());
|
|
776
|
+
}
|
|
777
|
+
this.rooms.get(room).add(client);
|
|
778
|
+
client.rooms.add(room);
|
|
779
|
+
this.events.emit("join_room", { client, room });
|
|
780
|
+
}
|
|
781
|
+
leave(clientOrId, room) {
|
|
782
|
+
const client = typeof clientOrId === "string" ? this.clients.get(clientOrId) : clientOrId;
|
|
783
|
+
if (!client) return;
|
|
784
|
+
if (this.rooms.has(room)) {
|
|
785
|
+
const roomSet = this.rooms.get(room);
|
|
786
|
+
roomSet.delete(client);
|
|
787
|
+
if (roomSet.size === 0) this.rooms.delete(room);
|
|
788
|
+
}
|
|
789
|
+
client.rooms.delete(room);
|
|
790
|
+
this.events.emit("leave_room", { client, room });
|
|
791
|
+
}
|
|
792
|
+
getClient(id) {
|
|
793
|
+
return this.clients.get(id);
|
|
794
|
+
}
|
|
795
|
+
getRoomClients(room) {
|
|
796
|
+
return Array.from(this.rooms.get(room) || []);
|
|
797
|
+
}
|
|
798
|
+
getOnlineCount() {
|
|
799
|
+
return this.clients.size;
|
|
800
|
+
}
|
|
801
|
+
on(event, callback) {
|
|
802
|
+
return this.events.on(event, callback);
|
|
803
|
+
}
|
|
804
|
+
off(event, callback) {
|
|
805
|
+
this.events.off(event, callback);
|
|
806
|
+
}
|
|
807
|
+
close() {
|
|
808
|
+
if (this._heartbeatTimer) clearInterval(this._heartbeatTimer);
|
|
809
|
+
for (const client of this.clients.values()) {
|
|
810
|
+
client.close(1e3, "Server shutdown");
|
|
811
|
+
}
|
|
812
|
+
this.clients.clear();
|
|
813
|
+
this.rooms.clear();
|
|
814
|
+
if (this.wss && typeof this.wss.close === "function") {
|
|
815
|
+
this.wss.close();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
_handleClientDisconnect(client, code, reason) {
|
|
819
|
+
for (const room of client.rooms) {
|
|
820
|
+
this.leave(client, room);
|
|
821
|
+
}
|
|
822
|
+
this.clients.delete(client.id);
|
|
823
|
+
client.events.emit("close", code, reason);
|
|
824
|
+
this.events.emit("disconnection", client, code, reason);
|
|
825
|
+
}
|
|
826
|
+
_startHeartbeat() {
|
|
827
|
+
if (!this.options.heartbeatInterval) return;
|
|
828
|
+
this._heartbeatTimer = setInterval(() => {
|
|
829
|
+
for (const client of this.clients.values()) {
|
|
830
|
+
if (!client.isAlive) {
|
|
831
|
+
client.close(4e3, "Heartbeat timeout");
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
client.isAlive = false;
|
|
835
|
+
if (typeof client.socket.ping === "function") {
|
|
836
|
+
client.socket.ping();
|
|
837
|
+
} else {
|
|
838
|
+
client.send({ type: "ping" });
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}, this.options.heartbeatInterval);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function createRealtimeServer(options) {
|
|
845
|
+
return new RealtimeServer(options);
|
|
846
|
+
}
|
|
847
|
+
var realtime_default = {
|
|
848
|
+
// Client
|
|
849
|
+
RealtimeSocket,
|
|
850
|
+
createRealtimeSocket,
|
|
851
|
+
SSEStream,
|
|
852
|
+
createSSE,
|
|
853
|
+
parseAIStream,
|
|
854
|
+
TabChannel,
|
|
855
|
+
createTabChannel,
|
|
856
|
+
SmartPoller,
|
|
857
|
+
createPoller,
|
|
858
|
+
EventBus,
|
|
859
|
+
createEventBus,
|
|
860
|
+
// Server
|
|
861
|
+
ConnectedClient,
|
|
862
|
+
RealtimeServer,
|
|
863
|
+
createRealtimeServer
|
|
864
|
+
};
|
|
865
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
866
|
+
0 && (module.exports = {
|
|
867
|
+
ConnectedClient,
|
|
868
|
+
EventBus,
|
|
869
|
+
RealtimeServer,
|
|
870
|
+
RealtimeSocket,
|
|
871
|
+
SSEStream,
|
|
872
|
+
SmartPoller,
|
|
873
|
+
TabChannel,
|
|
874
|
+
createEventBus,
|
|
875
|
+
createPoller,
|
|
876
|
+
createRealtimeServer,
|
|
877
|
+
createRealtimeSocket,
|
|
878
|
+
createSSE,
|
|
879
|
+
createTabChannel,
|
|
880
|
+
parseAIStream
|
|
881
|
+
});
|