@emeryld/rrroutes-client 2.6.2 → 2.6.4
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 +10 -6
- package/dist/index.cjs +1262 -1229
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1260 -1227
- package/dist/index.mjs.map +1 -1
- package/dist/sockets/socket.client.context.client.d.ts +5 -0
- package/dist/sockets/socket.client.context.connection.d.ts +18 -0
- package/dist/sockets/socket.client.context.d.ts +4 -71
- package/dist/sockets/socket.client.context.debug.d.ts +50 -0
- package/dist/sockets/socket.client.context.provider.d.ts +26 -0
- package/dist/sockets/socket.client.core.d.ts +116 -0
- package/dist/sockets/socket.client.index.d.ts +1 -116
- package/dist/sockets/socket.client.sys.d.ts +1 -1
- package/dist/sockets/socketedRoute/socket.client.helper.d.ts +2 -70
- package/dist/sockets/socketedRoute/socket.client.helper.debug.d.ts +54 -0
- package/dist/sockets/socketedRoute/socket.client.helper.rooms.d.ts +17 -0
- package/dist/sockets/socketedRoute/socket.client.helper.route.d.ts +31 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1339,1323 +1339,669 @@ var buildRoomPayloadSchema = (metaSchema) => import_zod.z.object({
|
|
|
1339
1339
|
meta: metaSchema
|
|
1340
1340
|
});
|
|
1341
1341
|
|
|
1342
|
-
// src/sockets/socket.client.
|
|
1343
|
-
var
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if (isProbablySocket(value)) {
|
|
1376
|
-
return describeSocketLike(value);
|
|
1377
|
-
}
|
|
1378
|
-
const ctorName = value.constructor?.name ?? "object";
|
|
1379
|
-
const keys = Object.keys(value);
|
|
1380
|
-
const keyPreview = keys.slice(0, 4).join(",");
|
|
1381
|
-
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
1382
|
-
return `[${ctorName} keys=${keyPreview}${suffix}]`;
|
|
1383
|
-
}
|
|
1384
|
-
function summarizeEvents(events) {
|
|
1385
|
-
if (!events || typeof events !== "object")
|
|
1386
|
-
return safeDescribeHookValue(events);
|
|
1387
|
-
const keys = Object.keys(events);
|
|
1388
|
-
if (!keys.length) return "[events empty]";
|
|
1389
|
-
const preview = keys.slice(0, 4).join(",");
|
|
1390
|
-
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
1391
|
-
return `[events count=${keys.length} keys=${preview}${suffix}]`;
|
|
1392
|
-
}
|
|
1393
|
-
function summarizeBaseOptions(options) {
|
|
1394
|
-
if (!options || typeof options !== "object")
|
|
1395
|
-
return safeDescribeHookValue(options);
|
|
1396
|
-
const obj = options;
|
|
1397
|
-
const keys = Object.keys(obj);
|
|
1398
|
-
if (!keys.length) return "[baseOptions empty]";
|
|
1399
|
-
const preview = keys.slice(0, 4).join(",");
|
|
1400
|
-
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
1401
|
-
const hasDebug = "debug" in obj;
|
|
1402
|
-
return `[baseOptions keys=${preview}${suffix} debug=${hasDebug}]`;
|
|
1403
|
-
}
|
|
1404
|
-
function summarizeMeta(meta, label) {
|
|
1405
|
-
if (meta == null) return null;
|
|
1406
|
-
if (typeof meta !== "object") return safeDescribeHookValue(meta);
|
|
1407
|
-
const keys = Object.keys(meta);
|
|
1408
|
-
if (!keys.length) return `[${label} empty]`;
|
|
1409
|
-
const preview = keys.slice(0, 4).join(",");
|
|
1410
|
-
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
1411
|
-
return `[${label} keys=${preview}${suffix}]`;
|
|
1412
|
-
}
|
|
1413
|
-
function createHookDebugEvent(prev, next, hook) {
|
|
1414
|
-
const reason = prev ? "change" : "init";
|
|
1415
|
-
const changed = Object.keys(next).reduce((acc, dependency) => {
|
|
1416
|
-
const prevValue = prev ? prev[dependency] : void 0;
|
|
1417
|
-
const nextValue = next[dependency];
|
|
1418
|
-
if (!prev || !Object.is(prevValue, nextValue)) {
|
|
1419
|
-
acc.push({
|
|
1420
|
-
dependency,
|
|
1421
|
-
previous: safeDescribeHookValue(prevValue),
|
|
1422
|
-
next: safeDescribeHookValue(nextValue)
|
|
1342
|
+
// src/sockets/socket.client.core.ts
|
|
1343
|
+
var SocketClient = class {
|
|
1344
|
+
constructor(events, opts) {
|
|
1345
|
+
this.hbTimer = null;
|
|
1346
|
+
// stats
|
|
1347
|
+
this.roomCounts = /* @__PURE__ */ new Map();
|
|
1348
|
+
this.handlerMap = /* @__PURE__ */ new Map();
|
|
1349
|
+
this.events = events;
|
|
1350
|
+
this.socket = opts.socket ?? null;
|
|
1351
|
+
this.environment = opts.environment ?? "development";
|
|
1352
|
+
this.debug = opts.debug ?? {};
|
|
1353
|
+
this.config = opts.config;
|
|
1354
|
+
this.sysEvents = opts.sys;
|
|
1355
|
+
this.roomJoinSchema = buildRoomPayloadSchema(this.config.joinMetaMessage);
|
|
1356
|
+
this.roomLeaveSchema = buildRoomPayloadSchema(this.config.leaveMetaMessage);
|
|
1357
|
+
const hb = opts.heartbeat ?? {};
|
|
1358
|
+
this.hb = {
|
|
1359
|
+
intervalMs: hb.intervalMs ?? 15e3,
|
|
1360
|
+
timeoutMs: hb.timeoutMs ?? 7500
|
|
1361
|
+
};
|
|
1362
|
+
if (!this.socket) {
|
|
1363
|
+
this.dbg({
|
|
1364
|
+
type: "lifecycle",
|
|
1365
|
+
phase: "init",
|
|
1366
|
+
err: "Socket reference is null during initialization"
|
|
1367
|
+
});
|
|
1368
|
+
} else {
|
|
1369
|
+
this.dbg({
|
|
1370
|
+
type: "lifecycle",
|
|
1371
|
+
phase: "init",
|
|
1372
|
+
heartbeatIntervalMs: this.hb.intervalMs,
|
|
1373
|
+
heartbeatTimeoutMs: this.hb.timeoutMs,
|
|
1374
|
+
environment: this.environment
|
|
1423
1375
|
});
|
|
1376
|
+
this.logSocketConfigSnapshot("constructor");
|
|
1424
1377
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
providerDebug,
|
|
1434
|
-
snapshot
|
|
1435
|
-
}) {
|
|
1436
|
-
const prev = ref.current;
|
|
1437
|
-
ref.current = snapshot;
|
|
1438
|
-
if (!providerDebug?.logger || !providerDebug?.hook) return;
|
|
1439
|
-
const event = createHookDebugEvent(prev, snapshot, hook);
|
|
1440
|
-
if (event) dbg(providerDebug, event);
|
|
1441
|
-
}
|
|
1442
|
-
function buildSocketProvider(args) {
|
|
1443
|
-
const { events, options: baseOptions } = args;
|
|
1444
|
-
return {
|
|
1445
|
-
SocketProvider: (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1446
|
-
SocketProvider,
|
|
1447
|
-
{
|
|
1448
|
-
events,
|
|
1449
|
-
baseOptions,
|
|
1450
|
-
providerDebug: baseOptions.debug,
|
|
1451
|
-
...props
|
|
1452
|
-
}
|
|
1453
|
-
),
|
|
1454
|
-
useSocketClient: () => useSocketClient(),
|
|
1455
|
-
useSocketConnection: (p) => useSocketConnection(p)
|
|
1456
|
-
};
|
|
1457
|
-
}
|
|
1458
|
-
function SocketProvider(props) {
|
|
1459
|
-
const {
|
|
1460
|
-
events,
|
|
1461
|
-
baseOptions,
|
|
1462
|
-
children,
|
|
1463
|
-
fallback,
|
|
1464
|
-
providerDebug,
|
|
1465
|
-
destroyLeaveMeta
|
|
1466
|
-
} = props;
|
|
1467
|
-
const [resolvedSocket, setResolvedSocket] = React.useState(
|
|
1468
|
-
null
|
|
1469
|
-
);
|
|
1470
|
-
const socket = "socket" in props ? props.socket ?? null : resolvedSocket;
|
|
1471
|
-
const providerDebugRef = React.useRef();
|
|
1472
|
-
providerDebugRef.current = providerDebug;
|
|
1473
|
-
const resolveEffectDebugRef = React.useRef(null);
|
|
1474
|
-
const clientMemoDebugRef = React.useRef(null);
|
|
1475
|
-
const destroyEffectDebugRef = React.useRef(null);
|
|
1476
|
-
React.useEffect(() => {
|
|
1477
|
-
trackHookTrigger({
|
|
1478
|
-
ref: resolveEffectDebugRef,
|
|
1479
|
-
hook: "resolve_effect",
|
|
1480
|
-
providerDebug: providerDebugRef.current,
|
|
1481
|
-
snapshot: {
|
|
1482
|
-
resolvedSocket: describeSocketLike(resolvedSocket)
|
|
1378
|
+
this.onConnect = async () => {
|
|
1379
|
+
if (!this.socket) {
|
|
1380
|
+
this.dbg({
|
|
1381
|
+
type: "connection",
|
|
1382
|
+
phase: "connect_event",
|
|
1383
|
+
err: "Socket is null"
|
|
1384
|
+
});
|
|
1385
|
+
throw new Error("Socket is null in onConnect handler");
|
|
1483
1386
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
if (cancelled) {
|
|
1491
|
-
dbg(providerDebugRef.current, {
|
|
1492
|
-
type: "resolve",
|
|
1493
|
-
phase: "cancelled"
|
|
1494
|
-
});
|
|
1495
|
-
return;
|
|
1496
|
-
}
|
|
1497
|
-
if (!s) {
|
|
1498
|
-
dbg(providerDebugRef.current, {
|
|
1499
|
-
type: "resolve",
|
|
1500
|
-
phase: "socketMissing"
|
|
1501
|
-
});
|
|
1502
|
-
return;
|
|
1387
|
+
this.dbg({
|
|
1388
|
+
type: "connection",
|
|
1389
|
+
phase: "connect_event",
|
|
1390
|
+
id: this.socket.id,
|
|
1391
|
+
details: {
|
|
1392
|
+
nsp: this.getNamespace(this.socket)
|
|
1503
1393
|
}
|
|
1504
|
-
setResolvedSocket(s);
|
|
1505
|
-
dbg(providerDebugRef.current, { type: "resolve", phase: "ok" });
|
|
1506
|
-
}).catch((err) => {
|
|
1507
|
-
if (cancelled) return;
|
|
1508
|
-
dbg(providerDebugRef.current, {
|
|
1509
|
-
type: "resolve",
|
|
1510
|
-
phase: "error",
|
|
1511
|
-
err: String(err)
|
|
1512
|
-
});
|
|
1513
1394
|
});
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1395
|
+
this.logSocketConfigSnapshot("connect_event");
|
|
1396
|
+
await this.getSysEvent("sys:connect")({
|
|
1397
|
+
socket: this.socket,
|
|
1398
|
+
client: this
|
|
1399
|
+
});
|
|
1517
1400
|
};
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1401
|
+
this.onReconnect = async (attempt) => {
|
|
1402
|
+
if (!this.socket) {
|
|
1403
|
+
this.dbg({
|
|
1404
|
+
type: "connection",
|
|
1405
|
+
phase: "reconnect_event",
|
|
1406
|
+
err: "Socket is null"
|
|
1407
|
+
});
|
|
1408
|
+
throw new Error("Socket is null in onReconnect handler");
|
|
1409
|
+
}
|
|
1410
|
+
this.dbg({
|
|
1411
|
+
type: "connection",
|
|
1412
|
+
phase: "reconnect_event",
|
|
1413
|
+
attempt,
|
|
1414
|
+
id: this.socket.id,
|
|
1415
|
+
details: {
|
|
1416
|
+
nsp: this.getNamespace(this.socket)
|
|
1417
|
+
}
|
|
1535
1418
|
});
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
trackHookTrigger({
|
|
1552
|
-
ref: destroyEffectDebugRef,
|
|
1553
|
-
hook: "destroy_effect",
|
|
1554
|
-
providerDebug: providerDebugRef.current,
|
|
1555
|
-
snapshot: {
|
|
1556
|
-
hasClient: !!client,
|
|
1557
|
-
destroyLeaveMeta: summarizeMeta(
|
|
1558
|
-
destroyLeaveMetaRef.current,
|
|
1559
|
-
"destroyLeaveMeta"
|
|
1560
|
-
)
|
|
1419
|
+
this.logSocketConfigSnapshot("reconnect_event");
|
|
1420
|
+
await this.getSysEvent("sys:reconnect")({
|
|
1421
|
+
attempt,
|
|
1422
|
+
socket: this.socket,
|
|
1423
|
+
client: this
|
|
1424
|
+
});
|
|
1425
|
+
};
|
|
1426
|
+
this.onDisconnect = async (reason) => {
|
|
1427
|
+
if (!this.socket) {
|
|
1428
|
+
this.dbg({
|
|
1429
|
+
type: "connection",
|
|
1430
|
+
phase: "disconnect_event",
|
|
1431
|
+
err: "Socket is null"
|
|
1432
|
+
});
|
|
1433
|
+
throw new Error("Socket is null in onDisconnect handler");
|
|
1561
1434
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1435
|
+
this.dbg({
|
|
1436
|
+
type: "connection",
|
|
1437
|
+
phase: "disconnect_event",
|
|
1438
|
+
reason: String(reason),
|
|
1439
|
+
details: {
|
|
1440
|
+
roomsTracked: this.roomCounts.size
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
this.logSocketConfigSnapshot("disconnect_event");
|
|
1444
|
+
await this.getSysEvent("sys:disconnect")({
|
|
1445
|
+
reason: String(reason),
|
|
1446
|
+
socket: this.socket,
|
|
1447
|
+
client: this
|
|
1448
|
+
});
|
|
1449
|
+
};
|
|
1450
|
+
this.onConnectError = async (err) => {
|
|
1451
|
+
if (!this.socket) {
|
|
1452
|
+
this.dbg({
|
|
1453
|
+
type: "connection",
|
|
1454
|
+
phase: "connect_error_event",
|
|
1455
|
+
err: "Socket is null"
|
|
1456
|
+
});
|
|
1457
|
+
throw new Error("Socket is null in onConnectError handler");
|
|
1567
1458
|
}
|
|
1459
|
+
this.dbg({
|
|
1460
|
+
type: "connection",
|
|
1461
|
+
phase: "connect_error_event",
|
|
1462
|
+
err: String(err),
|
|
1463
|
+
details: this.getVerboseDetails({ rawError: err })
|
|
1464
|
+
});
|
|
1465
|
+
this.logSocketConfigSnapshot("connect_error_event");
|
|
1466
|
+
await this.getSysEvent("sys:connect_error")({
|
|
1467
|
+
error: String(err),
|
|
1468
|
+
socket: this.socket,
|
|
1469
|
+
client: this
|
|
1470
|
+
});
|
|
1568
1471
|
};
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
return () => {
|
|
1600
|
-
unsubscribe();
|
|
1601
|
-
if (autoLeave && normalizedRooms.length > 0)
|
|
1602
|
-
client.leaveRooms(normalizedRooms, args.leaveMeta);
|
|
1603
|
-
if (onCleanup) onCleanup();
|
|
1472
|
+
this.onPong = async (raw) => {
|
|
1473
|
+
if (!this.socket) {
|
|
1474
|
+
this.dbg({
|
|
1475
|
+
type: "heartbeat",
|
|
1476
|
+
phase: "pong_recv",
|
|
1477
|
+
err: "Socket is null"
|
|
1478
|
+
});
|
|
1479
|
+
throw new Error("Socket is null in onPong handler");
|
|
1480
|
+
}
|
|
1481
|
+
const parsed = this.config.pongPayload.safeParse(raw);
|
|
1482
|
+
if (!parsed.success) {
|
|
1483
|
+
this.dbg({
|
|
1484
|
+
type: "heartbeat",
|
|
1485
|
+
phase: "validation_failed",
|
|
1486
|
+
err: `pong payload validation failed: ${parsed.error.message}`,
|
|
1487
|
+
details: this.getValidationDetails(parsed.error)
|
|
1488
|
+
});
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const validated = parsed.data;
|
|
1492
|
+
this.dbg({
|
|
1493
|
+
type: "heartbeat",
|
|
1494
|
+
phase: "pong_recv",
|
|
1495
|
+
payload: validated
|
|
1496
|
+
});
|
|
1497
|
+
await this.getSysEvent("sys:pong")({
|
|
1498
|
+
socket: this.socket,
|
|
1499
|
+
payload: validated,
|
|
1500
|
+
client: this
|
|
1501
|
+
});
|
|
1604
1502
|
};
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
const list = Array.isArray(rooms) ? rooms : [rooms];
|
|
1613
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1614
|
-
const normalized = [];
|
|
1615
|
-
for (const r of list) {
|
|
1616
|
-
if (typeof r !== "string") continue;
|
|
1617
|
-
if (seen.has(r)) continue;
|
|
1618
|
-
seen.add(r);
|
|
1619
|
-
normalized.push(r);
|
|
1503
|
+
if (this.socket) {
|
|
1504
|
+
this.socket.on("connect", this.onConnect);
|
|
1505
|
+
this.socket.on("reconnect", this.onReconnect);
|
|
1506
|
+
this.socket.on("disconnect", this.onDisconnect);
|
|
1507
|
+
this.socket.on("connect_error", this.onConnectError);
|
|
1508
|
+
this.socket.on("sys:pong", this.onPong);
|
|
1509
|
+
}
|
|
1620
1510
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1511
|
+
snapshotSocketConfig(socket) {
|
|
1512
|
+
if (!socket) return null;
|
|
1513
|
+
const manager = socket.io ?? null;
|
|
1514
|
+
const base = {
|
|
1515
|
+
nsp: this.getNamespace(socket)
|
|
1516
|
+
};
|
|
1517
|
+
if (!manager) return base;
|
|
1518
|
+
const opts = manager.opts ?? {};
|
|
1519
|
+
const transports = Array.isArray(opts.transports) ? opts.transports.filter((t) => typeof t === "string") : void 0;
|
|
1520
|
+
const transportName = typeof manager.engine?.transport?.name === "string" ? manager.engine.transport?.name : void 0;
|
|
1521
|
+
const readyState = typeof manager._readyState === "string" ? manager._readyState : void 0;
|
|
1522
|
+
const uri = typeof manager.uri === "string" ? manager.uri : void 0;
|
|
1523
|
+
return {
|
|
1524
|
+
...base,
|
|
1525
|
+
url: uri,
|
|
1526
|
+
path: typeof opts.path === "string" ? opts.path : void 0,
|
|
1527
|
+
transport: transportName ?? transports?.[0],
|
|
1528
|
+
transports,
|
|
1529
|
+
readyState,
|
|
1530
|
+
autoConnect: typeof opts.autoConnect === "boolean" ? opts.autoConnect : void 0,
|
|
1531
|
+
reconnection: typeof opts.reconnection === "boolean" ? opts.reconnection : void 0,
|
|
1532
|
+
reconnectionAttempts: typeof opts.reconnectionAttempts === "number" ? opts.reconnectionAttempts : void 0,
|
|
1533
|
+
reconnectionDelay: typeof opts.reconnectionDelay === "number" ? opts.reconnectionDelay : void 0,
|
|
1534
|
+
reconnectionDelayMax: typeof opts.reconnectionDelayMax === "number" ? opts.reconnectionDelayMax : void 0,
|
|
1535
|
+
timeout: typeof opts.timeout === "number" ? opts.timeout : void 0,
|
|
1536
|
+
hostname: typeof opts.hostname === "string" ? opts.hostname : void 0,
|
|
1537
|
+
port: typeof opts.port === "number" || typeof opts.port === "string" ? opts.port : void 0,
|
|
1538
|
+
secure: typeof opts.secure === "boolean" ? opts.secure : void 0
|
|
1539
|
+
};
|
|
1634
1540
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1541
|
+
logSocketConfigSnapshot(reason) {
|
|
1542
|
+
if (!this.debug.logger || !this.debug.config) return;
|
|
1543
|
+
const snapshot = this.snapshotSocketConfig(this.socket);
|
|
1544
|
+
this.dbg({
|
|
1545
|
+
type: "config",
|
|
1546
|
+
phase: reason,
|
|
1547
|
+
...snapshot == null ? { err: "Socket is missing. " } : {
|
|
1548
|
+
socketId: this.socket?.id,
|
|
1549
|
+
snapshot
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1643
1552
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
1648
|
-
if (a[i] !== b[i]) return false;
|
|
1553
|
+
getValidationDetails(error) {
|
|
1554
|
+
if (!this.debug.verbose) return void 0;
|
|
1555
|
+
return { issues: error.issues };
|
|
1649
1556
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
return arrayShallowEqual(prev.rooms, next.rooms) && safeJsonKey(prev.joinMeta) === safeJsonKey(next.joinMeta) && safeJsonKey(prev.leaveMeta) === safeJsonKey(next.leaveMeta);
|
|
1654
|
-
}
|
|
1655
|
-
function safeDescribeHookValue2(value) {
|
|
1656
|
-
if (value == null) return value;
|
|
1657
|
-
const valueType = typeof value;
|
|
1658
|
-
if (valueType === "string" || valueType === "number" || valueType === "boolean") {
|
|
1659
|
-
return value;
|
|
1557
|
+
getVerboseDetails(details) {
|
|
1558
|
+
if (!this.debug.verbose) return void 0;
|
|
1559
|
+
return details;
|
|
1660
1560
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
debug.logger(event);
|
|
1693
|
-
}
|
|
1694
|
-
function trackHookTrigger2({
|
|
1695
|
-
ref,
|
|
1696
|
-
phase,
|
|
1697
|
-
debug,
|
|
1698
|
-
snapshot
|
|
1699
|
-
}) {
|
|
1700
|
-
const prev = ref.current;
|
|
1701
|
-
ref.current = snapshot;
|
|
1702
|
-
if (!debug?.logger || !debug?.hook) return;
|
|
1703
|
-
const event = createHookDebugEvent2(prev, snapshot, phase);
|
|
1704
|
-
if (event) dbg2(debug, event);
|
|
1705
|
-
}
|
|
1706
|
-
function isSocketClientUnavailableError(err) {
|
|
1707
|
-
return err instanceof Error && err.message.includes("SocketClient not found");
|
|
1708
|
-
}
|
|
1709
|
-
function mergeRoomState(prev, toRoomsResult) {
|
|
1710
|
-
const merged = new Set(prev.rooms);
|
|
1711
|
-
for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
|
|
1712
|
-
return {
|
|
1713
|
-
rooms: Array.from(merged),
|
|
1714
|
-
joinMeta: toRoomsResult.joinMeta ?? prev.joinMeta,
|
|
1715
|
-
leaveMeta: toRoomsResult.leaveMeta ?? prev.leaveMeta
|
|
1716
|
-
};
|
|
1717
|
-
}
|
|
1718
|
-
function roomsFromData(data, toRooms) {
|
|
1719
|
-
if (data == null) return { rooms: [] };
|
|
1720
|
-
let state = { rooms: [] };
|
|
1721
|
-
const add = (input) => {
|
|
1722
|
-
const mergeForValue = (value) => {
|
|
1723
|
-
state = mergeRoomState(state, toRooms(value));
|
|
1561
|
+
getNamespace(socket) {
|
|
1562
|
+
if (!socket) return void 0;
|
|
1563
|
+
const nsp = socket.nsp;
|
|
1564
|
+
return typeof nsp === "string" ? nsp : void 0;
|
|
1565
|
+
}
|
|
1566
|
+
getSysEvent(name) {
|
|
1567
|
+
return this.sysEvents[name];
|
|
1568
|
+
}
|
|
1569
|
+
dbg(e) {
|
|
1570
|
+
const d = this.debug;
|
|
1571
|
+
if (!d.logger) return;
|
|
1572
|
+
if (!d[e.type]) return;
|
|
1573
|
+
if (d.only && "event" in e && !d.only.includes(e.event)) return;
|
|
1574
|
+
d.logger(e);
|
|
1575
|
+
}
|
|
1576
|
+
/** internal stats snapshot */
|
|
1577
|
+
stats() {
|
|
1578
|
+
const rooms = Array.from(this.roomCounts.entries()).map(
|
|
1579
|
+
([room, count]) => ({ room, count })
|
|
1580
|
+
);
|
|
1581
|
+
const handlers = Array.from(this.handlerMap.entries()).map(
|
|
1582
|
+
([event, set]) => ({
|
|
1583
|
+
event,
|
|
1584
|
+
handlers: set.size
|
|
1585
|
+
})
|
|
1586
|
+
);
|
|
1587
|
+
return {
|
|
1588
|
+
roomsCount: rooms.length,
|
|
1589
|
+
totalHandlers: handlers.reduce((a, b) => a + b.handlers, 0),
|
|
1590
|
+
rooms,
|
|
1591
|
+
handlers
|
|
1724
1592
|
};
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1593
|
+
}
|
|
1594
|
+
toArray(rooms) {
|
|
1595
|
+
return rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
|
|
1596
|
+
}
|
|
1597
|
+
rollbackJoinIncrement(rooms) {
|
|
1598
|
+
for (const room of rooms) {
|
|
1599
|
+
const curr = this.roomCounts.get(room) ?? 0;
|
|
1600
|
+
const next = Math.max(0, curr - 1);
|
|
1601
|
+
if (next === 0) this.roomCounts.delete(room);
|
|
1602
|
+
else this.roomCounts.set(room, next);
|
|
1735
1603
|
}
|
|
1736
|
-
mergeForValue(input);
|
|
1737
|
-
};
|
|
1738
|
-
const maybePages = data?.pages;
|
|
1739
|
-
if (Array.isArray(maybePages)) {
|
|
1740
|
-
for (const page of maybePages) add(page);
|
|
1741
|
-
return state;
|
|
1742
1604
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
const { built, toRooms, applySocket, useSocketClient: useSocketClient2, debug } = options;
|
|
1748
|
-
const { useEndpoint: useInnerEndpoint, ...rest } = built;
|
|
1749
|
-
const useEndpoint = (...useArgs) => {
|
|
1750
|
-
let client = null;
|
|
1751
|
-
let socketClientError = null;
|
|
1752
|
-
try {
|
|
1753
|
-
client = useSocketClient2();
|
|
1754
|
-
} catch (err) {
|
|
1755
|
-
if (!isSocketClientUnavailableError(err)) throw err;
|
|
1756
|
-
socketClientError = err;
|
|
1605
|
+
rollbackLeaveDecrement(rooms) {
|
|
1606
|
+
for (const room of rooms) {
|
|
1607
|
+
const curr = this.roomCounts.get(room) ?? 0;
|
|
1608
|
+
this.roomCounts.set(room, curr + 1);
|
|
1757
1609
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
const roomsKey = (0, import_react4.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
|
|
1773
|
-
const joinMetaKey = (0, import_react4.useMemo)(
|
|
1774
|
-
() => safeJsonKey(roomState.joinMeta ?? null),
|
|
1775
|
-
[roomState.joinMeta]
|
|
1776
|
-
);
|
|
1777
|
-
const leaveMetaKey = (0, import_react4.useMemo)(
|
|
1778
|
-
() => safeJsonKey(roomState.leaveMeta ?? null),
|
|
1779
|
-
[roomState.leaveMeta]
|
|
1780
|
-
);
|
|
1781
|
-
const hasClient = !!client;
|
|
1782
|
-
if (clientReadyRef.current !== hasClient) {
|
|
1783
|
-
clientReadyRef.current = hasClient;
|
|
1784
|
-
dbg2(debug, {
|
|
1785
|
-
type: "client",
|
|
1786
|
-
phase: hasClient ? "ready" : "missing",
|
|
1787
|
-
err: hasClient ? void 0 : String(socketClientError)
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Public: start the heartbeat loop manually.
|
|
1613
|
+
*
|
|
1614
|
+
* This is called by the default 'sys:connect' handler, but you can also
|
|
1615
|
+
* call it yourself from any sysHandler to change when heartbeats start.
|
|
1616
|
+
*/
|
|
1617
|
+
startHeartbeat() {
|
|
1618
|
+
this.stopHeartbeat("stop_before_start");
|
|
1619
|
+
if (!this.socket) {
|
|
1620
|
+
this.dbg({
|
|
1621
|
+
type: "heartbeat",
|
|
1622
|
+
phase: "start",
|
|
1623
|
+
err: "Socket is null"
|
|
1788
1624
|
});
|
|
1625
|
+
return;
|
|
1789
1626
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
leaveMetaKey
|
|
1627
|
+
const socket = this.socket;
|
|
1628
|
+
this.dbg({
|
|
1629
|
+
type: "heartbeat",
|
|
1630
|
+
phase: "start",
|
|
1631
|
+
details: {
|
|
1632
|
+
intervalMs: this.hb.intervalMs,
|
|
1633
|
+
timeoutMs: this.hb.timeoutMs
|
|
1634
|
+
}
|
|
1799
1635
|
});
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
endpointResultRef: describeObjectReference(endpointResult),
|
|
1807
|
-
endpointDataRef: describeObjectReference(endpointResult.data),
|
|
1808
|
-
toRoomsRef: describeObjectReference(toRooms)
|
|
1809
|
-
}
|
|
1810
|
-
});
|
|
1811
|
-
const unsubscribe = endpointResult.onReceive((data) => {
|
|
1812
|
-
setRoomState((prev) => {
|
|
1813
|
-
const next = mergeRoomState(prev, toRooms(data));
|
|
1814
|
-
return roomStateEqual(prev, next) ? prev : next;
|
|
1636
|
+
const tick = async () => {
|
|
1637
|
+
if (!socket) {
|
|
1638
|
+
this.dbg({
|
|
1639
|
+
type: "heartbeat",
|
|
1640
|
+
phase: "tick_skip",
|
|
1641
|
+
err: "Socket missing during heartbeat tick"
|
|
1815
1642
|
});
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const payload = await this.getSysEvent("sys:ping")({
|
|
1646
|
+
socket,
|
|
1647
|
+
client: this
|
|
1816
1648
|
});
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
snapshot: {
|
|
1825
|
-
endpointDataRef: describeObjectReference(endpointResult.data),
|
|
1826
|
-
toRoomsRef: describeObjectReference(toRooms)
|
|
1827
|
-
}
|
|
1828
|
-
});
|
|
1829
|
-
const next = roomsFromData(
|
|
1830
|
-
endpointResult.data,
|
|
1831
|
-
toRooms
|
|
1832
|
-
);
|
|
1833
|
-
setRoomState((prev) => roomStateEqual(prev, next) ? prev : next);
|
|
1834
|
-
}, [endpointResult.data, toRooms, debug]);
|
|
1835
|
-
(0, import_react4.useEffect)(() => {
|
|
1836
|
-
trackHookTrigger2({
|
|
1837
|
-
ref: joinRoomsEffectDebugRef,
|
|
1838
|
-
phase: "join_rooms_effect",
|
|
1839
|
-
debug,
|
|
1840
|
-
snapshot: {
|
|
1841
|
-
hasClient: !!client,
|
|
1842
|
-
clientRef: describeObjectReference(client),
|
|
1843
|
-
roomsKey,
|
|
1844
|
-
joinMetaKey,
|
|
1845
|
-
leaveMetaKey
|
|
1846
|
-
}
|
|
1847
|
-
});
|
|
1848
|
-
if (!client) {
|
|
1849
|
-
dbg2(debug, {
|
|
1850
|
-
type: "room",
|
|
1851
|
-
phase: "join_skip",
|
|
1852
|
-
rooms: roomState.rooms,
|
|
1853
|
-
reason: "socket_client_missing"
|
|
1649
|
+
const check = this.config.pingPayload.safeParse(payload);
|
|
1650
|
+
if (!check.success) {
|
|
1651
|
+
this.dbg({
|
|
1652
|
+
type: "heartbeat",
|
|
1653
|
+
phase: "validation_failed",
|
|
1654
|
+
err: "ping payload validation failed",
|
|
1655
|
+
details: this.getValidationDetails(check.error)
|
|
1854
1656
|
});
|
|
1657
|
+
if (this.environment === "development") {
|
|
1658
|
+
console.warn(
|
|
1659
|
+
"[socket] ping schema validation failed",
|
|
1660
|
+
check.error.issues
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1855
1663
|
return;
|
|
1856
1664
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
});
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
const { joinMeta, leaveMeta } = roomState;
|
|
1867
|
-
if (!joinMeta || !leaveMeta) {
|
|
1868
|
-
dbg2(debug, {
|
|
1869
|
-
type: "room",
|
|
1870
|
-
phase: "join_skip",
|
|
1871
|
-
rooms: roomState.rooms,
|
|
1872
|
-
reason: "missing_meta"
|
|
1873
|
-
});
|
|
1874
|
-
return;
|
|
1875
|
-
}
|
|
1876
|
-
let active = true;
|
|
1877
|
-
dbg2(debug, {
|
|
1878
|
-
type: "room",
|
|
1879
|
-
phase: "join_attempt",
|
|
1880
|
-
rooms: roomState.rooms
|
|
1881
|
-
});
|
|
1882
|
-
(async () => {
|
|
1883
|
-
try {
|
|
1884
|
-
await client.joinRooms(roomState.rooms, joinMeta);
|
|
1885
|
-
} catch (err) {
|
|
1886
|
-
dbg2(debug, {
|
|
1887
|
-
type: "room",
|
|
1888
|
-
phase: "join_error",
|
|
1889
|
-
rooms: roomState.rooms,
|
|
1890
|
-
err: String(err)
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1893
|
-
})();
|
|
1894
|
-
return () => {
|
|
1895
|
-
if (!active) return;
|
|
1896
|
-
active = false;
|
|
1897
|
-
if (roomState.rooms.length === 0) {
|
|
1898
|
-
dbg2(debug, {
|
|
1899
|
-
type: "room",
|
|
1900
|
-
phase: "leave_skip",
|
|
1901
|
-
rooms: [],
|
|
1902
|
-
reason: "no_rooms"
|
|
1903
|
-
});
|
|
1904
|
-
return;
|
|
1905
|
-
}
|
|
1906
|
-
dbg2(debug, {
|
|
1907
|
-
type: "room",
|
|
1908
|
-
phase: "leave_attempt",
|
|
1909
|
-
rooms: roomState.rooms
|
|
1910
|
-
});
|
|
1911
|
-
void client.leaveRooms(roomState.rooms, leaveMeta).catch((err) => {
|
|
1912
|
-
dbg2(debug, {
|
|
1913
|
-
type: "room",
|
|
1914
|
-
phase: "leave_error",
|
|
1915
|
-
rooms: roomState.rooms,
|
|
1916
|
-
err: String(err)
|
|
1917
|
-
});
|
|
1918
|
-
});
|
|
1919
|
-
};
|
|
1920
|
-
}, [client, roomsKey, joinMetaKey, leaveMetaKey, debug]);
|
|
1921
|
-
(0, import_react4.useEffect)(() => {
|
|
1922
|
-
trackHookTrigger2({
|
|
1923
|
-
ref: applySocketEffectDebugRef,
|
|
1924
|
-
phase: "apply_socket_effect",
|
|
1925
|
-
debug,
|
|
1926
|
-
snapshot: {
|
|
1927
|
-
hasClient: !!client,
|
|
1928
|
-
clientRef: describeObjectReference(client),
|
|
1929
|
-
applySocketKeys: Object.keys(applySocket).sort().join(","),
|
|
1930
|
-
argsKey,
|
|
1931
|
-
toRoomsRef: describeObjectReference(toRooms)
|
|
1932
|
-
}
|
|
1665
|
+
const dataToSend = check.data;
|
|
1666
|
+
socket.emit("sys:ping", dataToSend);
|
|
1667
|
+
this.dbg({
|
|
1668
|
+
type: "heartbeat",
|
|
1669
|
+
phase: "ping_emit",
|
|
1670
|
+
payload: dataToSend
|
|
1933
1671
|
});
|
|
1934
|
-
if (!client) return;
|
|
1935
|
-
const queue = [];
|
|
1936
|
-
let draining = false;
|
|
1937
|
-
let active = true;
|
|
1938
|
-
const drainQueue = () => {
|
|
1939
|
-
if (!active || draining) return;
|
|
1940
|
-
draining = true;
|
|
1941
|
-
try {
|
|
1942
|
-
while (active && queue.length > 0) {
|
|
1943
|
-
const nextUpdate = queue.shift();
|
|
1944
|
-
if (!nextUpdate) continue;
|
|
1945
|
-
built.setData(
|
|
1946
|
-
(prev) => {
|
|
1947
|
-
const next = nextUpdate.fn(
|
|
1948
|
-
prev,
|
|
1949
|
-
nextUpdate.payload,
|
|
1950
|
-
nextUpdate.meta ? { ...nextUpdate.meta, args: useArgs } : { args: useArgs }
|
|
1951
|
-
);
|
|
1952
|
-
const nextRoomState = roomsFromData(
|
|
1953
|
-
next,
|
|
1954
|
-
toRooms
|
|
1955
|
-
);
|
|
1956
|
-
setRoomState(
|
|
1957
|
-
(prevRoomState) => roomStateEqual(prevRoomState, nextRoomState) ? prevRoomState : nextRoomState
|
|
1958
|
-
);
|
|
1959
|
-
return next;
|
|
1960
|
-
},
|
|
1961
|
-
...useArgs
|
|
1962
|
-
);
|
|
1963
|
-
}
|
|
1964
|
-
} finally {
|
|
1965
|
-
draining = false;
|
|
1966
|
-
if (active && queue.length > 0) drainQueue();
|
|
1967
|
-
}
|
|
1968
|
-
};
|
|
1969
|
-
const entries = Object.entries(applySocket).filter(
|
|
1970
|
-
([_event, fn]) => typeof fn === "function"
|
|
1971
|
-
);
|
|
1972
|
-
const unsubscribes = entries.map(
|
|
1973
|
-
([ev, fn]) => client.on(ev, (payload, meta) => {
|
|
1974
|
-
queue.push({
|
|
1975
|
-
fn,
|
|
1976
|
-
payload,
|
|
1977
|
-
...meta ? { meta } : {}
|
|
1978
|
-
});
|
|
1979
|
-
drainQueue();
|
|
1980
|
-
})
|
|
1981
|
-
);
|
|
1982
|
-
return () => {
|
|
1983
|
-
active = false;
|
|
1984
|
-
queue.length = 0;
|
|
1985
|
-
unsubscribes.forEach((u) => u?.());
|
|
1986
|
-
};
|
|
1987
|
-
}, [client, applySocket, built, argsKey, toRooms, debug]);
|
|
1988
|
-
return { ...endpointResult, rooms: roomState.rooms };
|
|
1989
|
-
};
|
|
1990
|
-
return {
|
|
1991
|
-
...rest,
|
|
1992
|
-
useEndpoint
|
|
1993
|
-
};
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
// src/sockets/socket.client.index.ts
|
|
1997
|
-
var SocketClient = class {
|
|
1998
|
-
constructor(events, opts) {
|
|
1999
|
-
this.hbTimer = null;
|
|
2000
|
-
// stats
|
|
2001
|
-
this.roomCounts = /* @__PURE__ */ new Map();
|
|
2002
|
-
this.handlerMap = /* @__PURE__ */ new Map();
|
|
2003
|
-
this.events = events;
|
|
2004
|
-
this.socket = opts.socket ?? null;
|
|
2005
|
-
this.environment = opts.environment ?? "development";
|
|
2006
|
-
this.debug = opts.debug ?? {};
|
|
2007
|
-
this.config = opts.config;
|
|
2008
|
-
this.sysEvents = opts.sys;
|
|
2009
|
-
this.roomJoinSchema = buildRoomPayloadSchema(this.config.joinMetaMessage);
|
|
2010
|
-
this.roomLeaveSchema = buildRoomPayloadSchema(this.config.leaveMetaMessage);
|
|
2011
|
-
const hb = opts.heartbeat ?? {};
|
|
2012
|
-
this.hb = {
|
|
2013
|
-
intervalMs: hb.intervalMs ?? 15e3,
|
|
2014
|
-
timeoutMs: hb.timeoutMs ?? 7500
|
|
2015
1672
|
};
|
|
1673
|
+
this.hbTimer = setInterval(tick, this.hb.intervalMs);
|
|
1674
|
+
tick();
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Public: stop the heartbeat loop.
|
|
1678
|
+
*
|
|
1679
|
+
* This is called by the default 'sys:disconnect' handler, but you can also
|
|
1680
|
+
* call it yourself from any sysHandler to fully control heartbeat lifecycle.
|
|
1681
|
+
*/
|
|
1682
|
+
stopHeartbeat(reason) {
|
|
1683
|
+
const hadTimer = Boolean(this.hbTimer);
|
|
1684
|
+
if (this.hbTimer) {
|
|
1685
|
+
clearInterval(this.hbTimer);
|
|
1686
|
+
this.hbTimer = null;
|
|
1687
|
+
}
|
|
1688
|
+
this.dbg({
|
|
1689
|
+
type: "heartbeat",
|
|
1690
|
+
phase: "stop",
|
|
1691
|
+
reason,
|
|
1692
|
+
hadTimer
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
emit(event, payload, metadata) {
|
|
2016
1696
|
if (!this.socket) {
|
|
1697
|
+
this.dbg({ type: "emit", event, err: "Socket is null" });
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
const schema = this.events[event].message;
|
|
1701
|
+
const parsed = schema.safeParse(payload);
|
|
1702
|
+
if (!parsed.success) {
|
|
2017
1703
|
this.dbg({
|
|
2018
|
-
type: "
|
|
2019
|
-
|
|
2020
|
-
err: "
|
|
1704
|
+
type: "emit",
|
|
1705
|
+
event,
|
|
1706
|
+
err: "payload_validation_failed",
|
|
1707
|
+
details: this.getValidationDetails(parsed.error)
|
|
2021
1708
|
});
|
|
2022
|
-
|
|
1709
|
+
throw new Error(`Invalid payload for "${event}": ${parsed.error.message}`);
|
|
1710
|
+
}
|
|
1711
|
+
this.socket.emit(String(event), parsed.data);
|
|
1712
|
+
this.dbg({
|
|
1713
|
+
type: "emit",
|
|
1714
|
+
event,
|
|
1715
|
+
metadata
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
async joinRooms(rooms, meta) {
|
|
1719
|
+
if (!this.socket) {
|
|
2023
1720
|
this.dbg({
|
|
2024
|
-
type: "
|
|
2025
|
-
phase: "
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
environment: this.environment
|
|
1721
|
+
type: "room",
|
|
1722
|
+
phase: "join",
|
|
1723
|
+
rooms: this.toArray(rooms),
|
|
1724
|
+
err: "Socket is null"
|
|
2029
1725
|
});
|
|
2030
|
-
|
|
1726
|
+
throw new Error("Socket is null in joinRooms method");
|
|
2031
1727
|
}
|
|
2032
|
-
this.
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
});
|
|
2039
|
-
throw new Error("Socket is null in onConnect handler");
|
|
2040
|
-
}
|
|
1728
|
+
if (!await this.getSysEvent("sys:room_join")({
|
|
1729
|
+
rooms,
|
|
1730
|
+
meta,
|
|
1731
|
+
socket: this.socket,
|
|
1732
|
+
client: this
|
|
1733
|
+
})) {
|
|
2041
1734
|
this.dbg({
|
|
2042
|
-
type: "
|
|
2043
|
-
phase: "
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
nsp: this.getNamespace(this.socket)
|
|
2047
|
-
}
|
|
2048
|
-
});
|
|
2049
|
-
this.logSocketConfigSnapshot("connect_event");
|
|
2050
|
-
await this.getSysEvent("sys:connect")({
|
|
2051
|
-
socket: this.socket,
|
|
2052
|
-
client: this
|
|
1735
|
+
type: "room",
|
|
1736
|
+
phase: "join",
|
|
1737
|
+
rooms: this.toArray(rooms),
|
|
1738
|
+
err: "sys:room_join handler aborted join"
|
|
2053
1739
|
});
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
id: this.socket.id,
|
|
2069
|
-
details: {
|
|
2070
|
-
nsp: this.getNamespace(this.socket)
|
|
2071
|
-
}
|
|
2072
|
-
});
|
|
2073
|
-
this.logSocketConfigSnapshot("reconnect_event");
|
|
2074
|
-
await this.getSysEvent("sys:reconnect")({
|
|
2075
|
-
attempt,
|
|
2076
|
-
socket: this.socket,
|
|
2077
|
-
client: this
|
|
1740
|
+
return async () => {
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
const list = this.toArray(rooms);
|
|
1744
|
+
const toJoin = [];
|
|
1745
|
+
for (const r of list) {
|
|
1746
|
+
const next = (this.roomCounts.get(r) ?? 0) + 1;
|
|
1747
|
+
this.roomCounts.set(r, next);
|
|
1748
|
+
if (next === 1) toJoin.push(r);
|
|
1749
|
+
}
|
|
1750
|
+
if (toJoin.length > 0 && this.socket) {
|
|
1751
|
+
const payloadResult = this.roomJoinSchema.safeParse({
|
|
1752
|
+
rooms: toJoin,
|
|
1753
|
+
meta
|
|
2078
1754
|
});
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
if (!this.socket) {
|
|
1755
|
+
if (!payloadResult.success) {
|
|
1756
|
+
this.rollbackJoinIncrement(toJoin);
|
|
2082
1757
|
this.dbg({
|
|
2083
|
-
type: "
|
|
2084
|
-
phase: "
|
|
2085
|
-
|
|
1758
|
+
type: "room",
|
|
1759
|
+
phase: "join",
|
|
1760
|
+
rooms: toJoin,
|
|
1761
|
+
err: "payload validation failed",
|
|
1762
|
+
details: this.getValidationDetails(payloadResult.error)
|
|
2086
1763
|
});
|
|
2087
|
-
|
|
1764
|
+
return async () => {
|
|
1765
|
+
};
|
|
2088
1766
|
}
|
|
1767
|
+
const payload = payloadResult.data;
|
|
1768
|
+
const normalizedRooms = this.toArray(payload.rooms);
|
|
1769
|
+
this.socket.emit("sys:room_join", payload);
|
|
1770
|
+
this.dbg({ type: "room", phase: "join", rooms: normalizedRooms });
|
|
1771
|
+
}
|
|
1772
|
+
return async () => {
|
|
1773
|
+
await this.leaveRooms(rooms, meta);
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
async leaveRooms(rooms, meta) {
|
|
1777
|
+
if (!this.socket) {
|
|
2089
1778
|
this.dbg({
|
|
2090
|
-
type: "
|
|
2091
|
-
phase: "
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
roomsTracked: this.roomCounts.size
|
|
2095
|
-
}
|
|
2096
|
-
});
|
|
2097
|
-
this.logSocketConfigSnapshot("disconnect_event");
|
|
2098
|
-
await this.getSysEvent("sys:disconnect")({
|
|
2099
|
-
reason: String(reason),
|
|
2100
|
-
socket: this.socket,
|
|
2101
|
-
client: this
|
|
1779
|
+
type: "room",
|
|
1780
|
+
phase: "leave",
|
|
1781
|
+
rooms: this.toArray(rooms),
|
|
1782
|
+
err: "Socket is null"
|
|
2102
1783
|
});
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
throw new Error("Socket is null in onConnectError handler");
|
|
2112
|
-
}
|
|
1784
|
+
throw new Error("Socket is null in leaveRooms method");
|
|
1785
|
+
}
|
|
1786
|
+
if (!await this.getSysEvent("sys:room_leave")({
|
|
1787
|
+
rooms,
|
|
1788
|
+
meta,
|
|
1789
|
+
socket: this.socket,
|
|
1790
|
+
client: this
|
|
1791
|
+
})) {
|
|
2113
1792
|
this.dbg({
|
|
2114
|
-
type: "
|
|
2115
|
-
phase: "
|
|
2116
|
-
|
|
2117
|
-
|
|
1793
|
+
type: "room",
|
|
1794
|
+
phase: "leave",
|
|
1795
|
+
rooms: this.toArray(rooms),
|
|
1796
|
+
err: "sys:room_leave handler aborted leave"
|
|
2118
1797
|
});
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
const list = this.toArray(rooms);
|
|
1801
|
+
const toLeave = [];
|
|
1802
|
+
for (const r of list) {
|
|
1803
|
+
const curr = this.roomCounts.get(r) ?? 0;
|
|
1804
|
+
const next = Math.max(0, curr - 1);
|
|
1805
|
+
if (next === 0 && curr > 0) toLeave.push(r);
|
|
1806
|
+
if (next === 0) this.roomCounts.delete(r);
|
|
1807
|
+
else this.roomCounts.set(r, next);
|
|
1808
|
+
}
|
|
1809
|
+
if (toLeave.length > 0 && this.socket) {
|
|
1810
|
+
const payloadResult = this.roomLeaveSchema.safeParse({
|
|
1811
|
+
rooms: toLeave,
|
|
1812
|
+
meta
|
|
2124
1813
|
});
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
if (!this.socket) {
|
|
1814
|
+
if (!payloadResult.success) {
|
|
1815
|
+
this.rollbackLeaveDecrement(toLeave);
|
|
2128
1816
|
this.dbg({
|
|
2129
|
-
type: "
|
|
2130
|
-
phase: "
|
|
2131
|
-
|
|
1817
|
+
type: "room",
|
|
1818
|
+
phase: "leave",
|
|
1819
|
+
rooms: toLeave,
|
|
1820
|
+
err: "payload validation failed",
|
|
1821
|
+
details: this.getValidationDetails(payloadResult.error)
|
|
2132
1822
|
});
|
|
2133
|
-
|
|
1823
|
+
return;
|
|
2134
1824
|
}
|
|
2135
|
-
const
|
|
1825
|
+
const payload = payloadResult.data;
|
|
1826
|
+
const normalizedRooms = this.toArray(payload.rooms);
|
|
1827
|
+
this.socket.emit("sys:room_leave", payload);
|
|
1828
|
+
this.dbg({ type: "room", phase: "leave", rooms: normalizedRooms });
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
on(event, handler) {
|
|
1832
|
+
const schema = this.events[event].message;
|
|
1833
|
+
this.dbg({ type: "register", phase: "register", event });
|
|
1834
|
+
if (!this.socket) {
|
|
1835
|
+
this.dbg({
|
|
1836
|
+
type: "register",
|
|
1837
|
+
phase: "register",
|
|
1838
|
+
event,
|
|
1839
|
+
err: "Socket is null"
|
|
1840
|
+
});
|
|
1841
|
+
return () => {
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
const socket = this.socket;
|
|
1845
|
+
const toStringList = (value) => Array.isArray(value) ? value.filter((entry2) => typeof entry2 === "string") : [];
|
|
1846
|
+
const wrappedEnv = (envelope) => {
|
|
1847
|
+
const rawData = envelope.data;
|
|
1848
|
+
const parsed = schema.safeParse(rawData);
|
|
2136
1849
|
if (!parsed.success) {
|
|
2137
1850
|
this.dbg({
|
|
2138
|
-
type: "
|
|
2139
|
-
|
|
2140
|
-
err:
|
|
1851
|
+
type: "receive",
|
|
1852
|
+
event,
|
|
1853
|
+
err: "payload_validation_failed",
|
|
2141
1854
|
details: this.getValidationDetails(parsed.error)
|
|
2142
1855
|
});
|
|
2143
1856
|
return;
|
|
2144
1857
|
}
|
|
2145
|
-
const
|
|
1858
|
+
const data = parsed.data;
|
|
1859
|
+
const receivedAt = /* @__PURE__ */ new Date();
|
|
1860
|
+
const sentAt = envelope?.sentAt ? new Date(envelope.sentAt) : void 0;
|
|
1861
|
+
const meta = {
|
|
1862
|
+
envelope: {
|
|
1863
|
+
eventName: typeof envelope?.eventName === "string" ? envelope.eventName : event,
|
|
1864
|
+
sentAt: envelope?.sentAt ?? receivedAt.toISOString(),
|
|
1865
|
+
sentTo: toStringList(envelope?.sentTo),
|
|
1866
|
+
data,
|
|
1867
|
+
metadata: envelope?.metadata,
|
|
1868
|
+
rooms: toStringList(envelope?.rooms)
|
|
1869
|
+
},
|
|
1870
|
+
ctx: {
|
|
1871
|
+
receivedAt,
|
|
1872
|
+
latencyMs: sentAt ? Math.max(0, receivedAt.getTime() - sentAt.getTime()) : void 0,
|
|
1873
|
+
nsp: this.getNamespace(socket),
|
|
1874
|
+
socketId: socket.id,
|
|
1875
|
+
socket
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
2146
1878
|
this.dbg({
|
|
2147
|
-
type: "
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
1879
|
+
type: "receive",
|
|
1880
|
+
event,
|
|
1881
|
+
envelope: this.debug.verbose ? {
|
|
1882
|
+
eventName: meta.envelope.eventName,
|
|
1883
|
+
sentAt: meta.envelope.sentAt,
|
|
1884
|
+
sentTo: meta.envelope.sentTo,
|
|
1885
|
+
metadata: meta.envelope.metadata
|
|
1886
|
+
} : void 0
|
|
2155
1887
|
});
|
|
1888
|
+
handler(data, meta);
|
|
2156
1889
|
};
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
const base = {
|
|
2169
|
-
nsp: this.getNamespace(socket)
|
|
1890
|
+
const wrappedDispatcher = (envelopeOrRaw) => {
|
|
1891
|
+
if (typeof envelopeOrRaw === "object" && envelopeOrRaw !== null && "eventName" in envelopeOrRaw && "sentAt" in envelopeOrRaw && "sentTo" in envelopeOrRaw && "data" in envelopeOrRaw) {
|
|
1892
|
+
wrappedEnv(envelopeOrRaw);
|
|
1893
|
+
} else {
|
|
1894
|
+
this.dbg({
|
|
1895
|
+
type: "receive",
|
|
1896
|
+
event,
|
|
1897
|
+
envelope: void 0
|
|
1898
|
+
});
|
|
1899
|
+
handler(envelopeOrRaw, void 0);
|
|
1900
|
+
}
|
|
2170
1901
|
};
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
const transports = Array.isArray(opts.transports) ? opts.transports.filter((t) => typeof t === "string") : void 0;
|
|
2174
|
-
const transportName = typeof manager.engine?.transport?.name === "string" ? manager.engine.transport?.name : void 0;
|
|
2175
|
-
const readyState = typeof manager._readyState === "string" ? manager._readyState : void 0;
|
|
2176
|
-
const uri = typeof manager.uri === "string" ? manager.uri : void 0;
|
|
2177
|
-
return {
|
|
2178
|
-
...base,
|
|
2179
|
-
url: uri,
|
|
2180
|
-
path: typeof opts.path === "string" ? opts.path : void 0,
|
|
2181
|
-
transport: transportName ?? transports?.[0],
|
|
2182
|
-
transports,
|
|
2183
|
-
readyState,
|
|
2184
|
-
autoConnect: typeof opts.autoConnect === "boolean" ? opts.autoConnect : void 0,
|
|
2185
|
-
reconnection: typeof opts.reconnection === "boolean" ? opts.reconnection : void 0,
|
|
2186
|
-
reconnectionAttempts: typeof opts.reconnectionAttempts === "number" ? opts.reconnectionAttempts : void 0,
|
|
2187
|
-
reconnectionDelay: typeof opts.reconnectionDelay === "number" ? opts.reconnectionDelay : void 0,
|
|
2188
|
-
reconnectionDelayMax: typeof opts.reconnectionDelayMax === "number" ? opts.reconnectionDelayMax : void 0,
|
|
2189
|
-
timeout: typeof opts.timeout === "number" ? opts.timeout : void 0,
|
|
2190
|
-
hostname: typeof opts.hostname === "string" ? opts.hostname : void 0,
|
|
2191
|
-
port: typeof opts.port === "number" || typeof opts.port === "string" ? opts.port : void 0,
|
|
2192
|
-
secure: typeof opts.secure === "boolean" ? opts.secure : void 0
|
|
1902
|
+
const errorWrapped = (e) => {
|
|
1903
|
+
this.dbg({ type: "receive", event, err: String(e) });
|
|
2193
1904
|
};
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
1905
|
+
socket.on(String(event), wrappedDispatcher);
|
|
1906
|
+
socket.on(`${String(event)}:error`, errorWrapped);
|
|
1907
|
+
let set = this.handlerMap.get(String(event));
|
|
1908
|
+
if (!set) {
|
|
1909
|
+
set = /* @__PURE__ */ new Set();
|
|
1910
|
+
this.handlerMap.set(String(event), set);
|
|
1911
|
+
}
|
|
1912
|
+
const entry = {
|
|
1913
|
+
orig: handler,
|
|
1914
|
+
wrapped: wrappedDispatcher,
|
|
1915
|
+
errorWrapped
|
|
1916
|
+
};
|
|
1917
|
+
set.add(entry);
|
|
1918
|
+
return () => {
|
|
1919
|
+
socket.off(String(event), wrappedDispatcher);
|
|
1920
|
+
socket.off(`${String(event)}:error`, errorWrapped);
|
|
1921
|
+
const s = this.handlerMap.get(String(event));
|
|
1922
|
+
if (s) {
|
|
1923
|
+
s.delete(entry);
|
|
1924
|
+
if (s.size === 0) this.handlerMap.delete(String(event));
|
|
2204
1925
|
}
|
|
2205
|
-
|
|
2206
|
-
}
|
|
2207
|
-
getValidationDetails(error) {
|
|
2208
|
-
if (!this.debug.verbose) return void 0;
|
|
2209
|
-
return { issues: error.issues };
|
|
2210
|
-
}
|
|
2211
|
-
getVerboseDetails(details) {
|
|
2212
|
-
if (!this.debug.verbose) return void 0;
|
|
2213
|
-
return details;
|
|
2214
|
-
}
|
|
2215
|
-
getNamespace(socket) {
|
|
2216
|
-
if (!socket) return void 0;
|
|
2217
|
-
const nsp = socket.nsp;
|
|
2218
|
-
return typeof nsp === "string" ? nsp : void 0;
|
|
2219
|
-
}
|
|
2220
|
-
getSysEvent(name) {
|
|
2221
|
-
return this.sysEvents[name];
|
|
2222
|
-
}
|
|
2223
|
-
dbg(e) {
|
|
2224
|
-
const d = this.debug;
|
|
2225
|
-
if (!d.logger) return;
|
|
2226
|
-
if (!d[e.type]) return;
|
|
2227
|
-
if (d.only && "event" in e && !d.only.includes(e.event)) return;
|
|
2228
|
-
d.logger(e);
|
|
2229
|
-
}
|
|
2230
|
-
/** internal stats snapshot */
|
|
2231
|
-
stats() {
|
|
2232
|
-
const rooms = Array.from(this.roomCounts.entries()).map(
|
|
2233
|
-
([room, count]) => ({ room, count })
|
|
2234
|
-
);
|
|
2235
|
-
const handlers = Array.from(this.handlerMap.entries()).map(
|
|
2236
|
-
([event, set]) => ({
|
|
2237
|
-
event,
|
|
2238
|
-
handlers: set.size
|
|
2239
|
-
})
|
|
2240
|
-
);
|
|
2241
|
-
return {
|
|
2242
|
-
roomsCount: rooms.length,
|
|
2243
|
-
totalHandlers: handlers.reduce((a, b) => a + b.handlers, 0),
|
|
2244
|
-
rooms,
|
|
2245
|
-
handlers
|
|
1926
|
+
this.dbg({ type: "register", phase: "unregister", event });
|
|
2246
1927
|
};
|
|
2247
1928
|
}
|
|
2248
|
-
toArray(rooms) {
|
|
2249
|
-
return rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
|
|
2250
|
-
}
|
|
2251
|
-
rollbackJoinIncrement(rooms) {
|
|
2252
|
-
for (const room of rooms) {
|
|
2253
|
-
const curr = this.roomCounts.get(room) ?? 0;
|
|
2254
|
-
const next = Math.max(0, curr - 1);
|
|
2255
|
-
if (next === 0) this.roomCounts.delete(room);
|
|
2256
|
-
else this.roomCounts.set(room, next);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
rollbackLeaveDecrement(rooms) {
|
|
2260
|
-
for (const room of rooms) {
|
|
2261
|
-
const curr = this.roomCounts.get(room) ?? 0;
|
|
2262
|
-
this.roomCounts.set(room, curr + 1);
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
1929
|
/**
|
|
2266
|
-
*
|
|
2267
|
-
*
|
|
2268
|
-
* This is called by the default 'sys:connect' handler, but you can also
|
|
2269
|
-
* call it yourself from any sysHandler to change when heartbeats start.
|
|
1930
|
+
* Remove all listeners, stop timers, and leave rooms.
|
|
1931
|
+
* Call when disposing the client instance.
|
|
2270
1932
|
*/
|
|
2271
|
-
|
|
2272
|
-
this.stopHeartbeat("stop_before_start");
|
|
2273
|
-
if (!this.socket) {
|
|
2274
|
-
this.dbg({
|
|
2275
|
-
type: "heartbeat",
|
|
2276
|
-
phase: "start",
|
|
2277
|
-
err: "Socket is null"
|
|
2278
|
-
});
|
|
2279
|
-
return;
|
|
2280
|
-
}
|
|
1933
|
+
async destroy(leaveMeta) {
|
|
2281
1934
|
const socket = this.socket;
|
|
2282
1935
|
this.dbg({
|
|
2283
|
-
type: "
|
|
2284
|
-
phase: "
|
|
1936
|
+
type: "lifecycle",
|
|
1937
|
+
phase: "destroy_begin",
|
|
1938
|
+
socketId: socket?.id,
|
|
2285
1939
|
details: {
|
|
2286
|
-
|
|
2287
|
-
|
|
1940
|
+
roomsTracked: this.roomCounts.size,
|
|
1941
|
+
handlerEvents: this.handlerMap.size
|
|
2288
1942
|
}
|
|
2289
1943
|
});
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
client: this
|
|
2302
|
-
});
|
|
2303
|
-
const check = this.config.pingPayload.safeParse(payload);
|
|
2304
|
-
if (!check.success) {
|
|
2305
|
-
this.dbg({
|
|
2306
|
-
type: "heartbeat",
|
|
2307
|
-
phase: "validation_failed",
|
|
2308
|
-
err: "ping payload validation failed",
|
|
2309
|
-
details: this.getValidationDetails(check.error)
|
|
2310
|
-
});
|
|
2311
|
-
if (this.environment === "development") {
|
|
2312
|
-
console.warn(
|
|
2313
|
-
"[socket] ping schema validation failed",
|
|
2314
|
-
check.error.issues
|
|
2315
|
-
);
|
|
1944
|
+
this.stopHeartbeat("destroy");
|
|
1945
|
+
if (socket) {
|
|
1946
|
+
socket.off("connect", this.onConnect);
|
|
1947
|
+
socket.off("reconnect", this.onReconnect);
|
|
1948
|
+
socket.off("disconnect", this.onDisconnect);
|
|
1949
|
+
socket.off("connect_error", this.onConnectError);
|
|
1950
|
+
socket.off("sys:pong", this.onPong);
|
|
1951
|
+
for (const [event, set] of this.handlerMap.entries()) {
|
|
1952
|
+
for (const entry of set) {
|
|
1953
|
+
socket.off(String(event), entry.wrapped);
|
|
1954
|
+
socket.off(`${String(event)}:error`, entry.errorWrapped);
|
|
2316
1955
|
}
|
|
2317
|
-
return;
|
|
2318
1956
|
}
|
|
2319
|
-
const dataToSend = check.data;
|
|
2320
|
-
socket.emit("sys:ping", dataToSend);
|
|
2321
|
-
this.dbg({
|
|
2322
|
-
type: "heartbeat",
|
|
2323
|
-
phase: "ping_emit",
|
|
2324
|
-
payload: dataToSend
|
|
2325
|
-
});
|
|
2326
|
-
};
|
|
2327
|
-
this.hbTimer = setInterval(tick, this.hb.intervalMs);
|
|
2328
|
-
tick();
|
|
2329
|
-
}
|
|
2330
|
-
/**
|
|
2331
|
-
* Public: stop the heartbeat loop.
|
|
2332
|
-
*
|
|
2333
|
-
* This is called by the default 'sys:disconnect' handler, but you can also
|
|
2334
|
-
* call it yourself from any sysHandler to fully control heartbeat lifecycle.
|
|
2335
|
-
*/
|
|
2336
|
-
stopHeartbeat(reason) {
|
|
2337
|
-
const hadTimer = Boolean(this.hbTimer);
|
|
2338
|
-
if (this.hbTimer) {
|
|
2339
|
-
clearInterval(this.hbTimer);
|
|
2340
|
-
this.hbTimer = null;
|
|
2341
1957
|
}
|
|
1958
|
+
this.handlerMap.clear();
|
|
1959
|
+
const toLeave = Array.from(this.roomCounts.entries()).filter(([, count]) => count > 0).map(([room]) => room);
|
|
1960
|
+
if (toLeave.length > 0 && socket) {
|
|
1961
|
+
await this.leaveRooms(toLeave, leaveMeta);
|
|
1962
|
+
}
|
|
1963
|
+
this.roomCounts.clear();
|
|
2342
1964
|
this.dbg({
|
|
2343
|
-
type: "
|
|
2344
|
-
phase: "
|
|
2345
|
-
|
|
2346
|
-
|
|
1965
|
+
type: "lifecycle",
|
|
1966
|
+
phase: "destroy_complete",
|
|
1967
|
+
socketId: socket?.id,
|
|
1968
|
+
details: {
|
|
1969
|
+
roomsTracked: this.roomCounts.size,
|
|
1970
|
+
handlerEvents: this.handlerMap.size
|
|
1971
|
+
}
|
|
2347
1972
|
});
|
|
2348
1973
|
}
|
|
2349
|
-
|
|
1974
|
+
/** Pass-throughs. Managing connection is the caller’s responsibility. */
|
|
1975
|
+
disconnect() {
|
|
2350
1976
|
if (!this.socket) {
|
|
2351
|
-
this.dbg({ type: "emit", event, err: "Socket is null" });
|
|
2352
|
-
return;
|
|
2353
|
-
}
|
|
2354
|
-
const schema = this.events[event].message;
|
|
2355
|
-
const parsed = schema.safeParse(payload);
|
|
2356
|
-
if (!parsed.success) {
|
|
2357
1977
|
this.dbg({
|
|
2358
|
-
type: "
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
1978
|
+
type: "connection",
|
|
1979
|
+
phase: "disconnect",
|
|
1980
|
+
reason: "client_disconnect",
|
|
1981
|
+
err: "Socket is null"
|
|
2362
1982
|
});
|
|
2363
|
-
|
|
1983
|
+
return;
|
|
2364
1984
|
}
|
|
2365
|
-
this.
|
|
1985
|
+
this.stopHeartbeat("disconnect");
|
|
1986
|
+
this.socket.disconnect();
|
|
2366
1987
|
this.dbg({
|
|
2367
|
-
type: "
|
|
2368
|
-
|
|
2369
|
-
|
|
1988
|
+
type: "connection",
|
|
1989
|
+
phase: "disconnect",
|
|
1990
|
+
reason: "client_disconnect",
|
|
1991
|
+
details: {
|
|
1992
|
+
nsp: this.getNamespace(this.socket)
|
|
1993
|
+
}
|
|
2370
1994
|
});
|
|
1995
|
+
this.logSocketConfigSnapshot("disconnect_call");
|
|
2371
1996
|
}
|
|
2372
|
-
|
|
1997
|
+
connect() {
|
|
2373
1998
|
if (!this.socket) {
|
|
2374
1999
|
this.dbg({
|
|
2375
|
-
type: "
|
|
2376
|
-
phase: "
|
|
2377
|
-
rooms: this.toArray(rooms),
|
|
2000
|
+
type: "connection",
|
|
2001
|
+
phase: "connect",
|
|
2378
2002
|
err: "Socket is null"
|
|
2379
2003
|
});
|
|
2380
|
-
|
|
2381
|
-
}
|
|
2382
|
-
if (!await this.getSysEvent("sys:room_join")({
|
|
2383
|
-
rooms,
|
|
2384
|
-
meta,
|
|
2385
|
-
socket: this.socket,
|
|
2386
|
-
client: this
|
|
2387
|
-
})) {
|
|
2388
|
-
this.dbg({
|
|
2389
|
-
type: "room",
|
|
2390
|
-
phase: "join",
|
|
2391
|
-
rooms: this.toArray(rooms),
|
|
2392
|
-
err: "sys:room_join handler aborted join"
|
|
2393
|
-
});
|
|
2394
|
-
return async () => {
|
|
2395
|
-
};
|
|
2396
|
-
}
|
|
2397
|
-
const list = this.toArray(rooms);
|
|
2398
|
-
const toJoin = [];
|
|
2399
|
-
for (const r of list) {
|
|
2400
|
-
const next = (this.roomCounts.get(r) ?? 0) + 1;
|
|
2401
|
-
this.roomCounts.set(r, next);
|
|
2402
|
-
if (next === 1) toJoin.push(r);
|
|
2403
|
-
}
|
|
2404
|
-
if (toJoin.length > 0 && this.socket) {
|
|
2405
|
-
const payloadResult = this.roomJoinSchema.safeParse({
|
|
2406
|
-
rooms: toJoin,
|
|
2407
|
-
meta
|
|
2408
|
-
});
|
|
2409
|
-
if (!payloadResult.success) {
|
|
2410
|
-
this.rollbackJoinIncrement(toJoin);
|
|
2411
|
-
this.dbg({
|
|
2412
|
-
type: "room",
|
|
2413
|
-
phase: "join",
|
|
2414
|
-
rooms: toJoin,
|
|
2415
|
-
err: "payload validation failed",
|
|
2416
|
-
details: this.getValidationDetails(payloadResult.error)
|
|
2417
|
-
});
|
|
2418
|
-
return async () => {
|
|
2419
|
-
};
|
|
2420
|
-
}
|
|
2421
|
-
const payload = payloadResult.data;
|
|
2422
|
-
const normalizedRooms = this.toArray(payload.rooms);
|
|
2423
|
-
this.socket.emit("sys:room_join", payload);
|
|
2424
|
-
this.dbg({ type: "room", phase: "join", rooms: normalizedRooms });
|
|
2425
|
-
}
|
|
2426
|
-
return async () => {
|
|
2427
|
-
await this.leaveRooms(rooms, meta);
|
|
2428
|
-
};
|
|
2429
|
-
}
|
|
2430
|
-
async leaveRooms(rooms, meta) {
|
|
2431
|
-
if (!this.socket) {
|
|
2432
|
-
this.dbg({
|
|
2433
|
-
type: "room",
|
|
2434
|
-
phase: "leave",
|
|
2435
|
-
rooms: this.toArray(rooms),
|
|
2436
|
-
err: "Socket is null"
|
|
2437
|
-
});
|
|
2438
|
-
throw new Error("Socket is null in leaveRooms method");
|
|
2439
|
-
}
|
|
2440
|
-
if (!await this.getSysEvent("sys:room_leave")({
|
|
2441
|
-
rooms,
|
|
2442
|
-
meta,
|
|
2443
|
-
socket: this.socket,
|
|
2444
|
-
client: this
|
|
2445
|
-
})) {
|
|
2446
|
-
this.dbg({
|
|
2447
|
-
type: "room",
|
|
2448
|
-
phase: "leave",
|
|
2449
|
-
rooms: this.toArray(rooms),
|
|
2450
|
-
err: "sys:room_leave handler aborted leave"
|
|
2451
|
-
});
|
|
2452
|
-
return;
|
|
2453
|
-
}
|
|
2454
|
-
const list = this.toArray(rooms);
|
|
2455
|
-
const toLeave = [];
|
|
2456
|
-
for (const r of list) {
|
|
2457
|
-
const curr = this.roomCounts.get(r) ?? 0;
|
|
2458
|
-
const next = Math.max(0, curr - 1);
|
|
2459
|
-
if (next === 0 && curr > 0) toLeave.push(r);
|
|
2460
|
-
if (next === 0) this.roomCounts.delete(r);
|
|
2461
|
-
else this.roomCounts.set(r, next);
|
|
2462
|
-
}
|
|
2463
|
-
if (toLeave.length > 0 && this.socket) {
|
|
2464
|
-
const payloadResult = this.roomLeaveSchema.safeParse({
|
|
2465
|
-
rooms: toLeave,
|
|
2466
|
-
meta
|
|
2467
|
-
});
|
|
2468
|
-
if (!payloadResult.success) {
|
|
2469
|
-
this.rollbackLeaveDecrement(toLeave);
|
|
2470
|
-
this.dbg({
|
|
2471
|
-
type: "room",
|
|
2472
|
-
phase: "leave",
|
|
2473
|
-
rooms: toLeave,
|
|
2474
|
-
err: "payload validation failed",
|
|
2475
|
-
details: this.getValidationDetails(payloadResult.error)
|
|
2476
|
-
});
|
|
2477
|
-
return;
|
|
2478
|
-
}
|
|
2479
|
-
const payload = payloadResult.data;
|
|
2480
|
-
const normalizedRooms = this.toArray(payload.rooms);
|
|
2481
|
-
this.socket.emit("sys:room_leave", payload);
|
|
2482
|
-
this.dbg({ type: "room", phase: "leave", rooms: normalizedRooms });
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
on(event, handler) {
|
|
2486
|
-
const schema = this.events[event].message;
|
|
2487
|
-
this.dbg({ type: "register", phase: "register", event });
|
|
2488
|
-
if (!this.socket) {
|
|
2489
|
-
this.dbg({
|
|
2490
|
-
type: "register",
|
|
2491
|
-
phase: "register",
|
|
2492
|
-
event,
|
|
2493
|
-
err: "Socket is null"
|
|
2494
|
-
});
|
|
2495
|
-
return () => {
|
|
2496
|
-
};
|
|
2497
|
-
}
|
|
2498
|
-
const socket = this.socket;
|
|
2499
|
-
const toStringList = (value) => Array.isArray(value) ? value.filter((entry2) => typeof entry2 === "string") : [];
|
|
2500
|
-
const wrappedEnv = (envelope) => {
|
|
2501
|
-
const rawData = envelope.data;
|
|
2502
|
-
const parsed = schema.safeParse(rawData);
|
|
2503
|
-
if (!parsed.success) {
|
|
2504
|
-
this.dbg({
|
|
2505
|
-
type: "receive",
|
|
2506
|
-
event,
|
|
2507
|
-
err: "payload_validation_failed",
|
|
2508
|
-
details: this.getValidationDetails(parsed.error)
|
|
2509
|
-
});
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
2512
|
-
const data = parsed.data;
|
|
2513
|
-
const receivedAt = /* @__PURE__ */ new Date();
|
|
2514
|
-
const sentAt = envelope?.sentAt ? new Date(envelope.sentAt) : void 0;
|
|
2515
|
-
const meta = {
|
|
2516
|
-
envelope: {
|
|
2517
|
-
eventName: typeof envelope?.eventName === "string" ? envelope.eventName : event,
|
|
2518
|
-
sentAt: envelope?.sentAt ?? receivedAt.toISOString(),
|
|
2519
|
-
sentTo: toStringList(envelope?.sentTo),
|
|
2520
|
-
data,
|
|
2521
|
-
metadata: envelope?.metadata,
|
|
2522
|
-
rooms: toStringList(envelope?.rooms)
|
|
2523
|
-
},
|
|
2524
|
-
ctx: {
|
|
2525
|
-
receivedAt,
|
|
2526
|
-
latencyMs: sentAt ? Math.max(0, receivedAt.getTime() - sentAt.getTime()) : void 0,
|
|
2527
|
-
nsp: this.getNamespace(socket),
|
|
2528
|
-
socketId: socket.id,
|
|
2529
|
-
socket
|
|
2530
|
-
}
|
|
2531
|
-
};
|
|
2532
|
-
this.dbg({
|
|
2533
|
-
type: "receive",
|
|
2534
|
-
event,
|
|
2535
|
-
envelope: this.debug.verbose ? {
|
|
2536
|
-
eventName: meta.envelope.eventName,
|
|
2537
|
-
sentAt: meta.envelope.sentAt,
|
|
2538
|
-
sentTo: meta.envelope.sentTo,
|
|
2539
|
-
metadata: meta.envelope.metadata
|
|
2540
|
-
} : void 0
|
|
2541
|
-
});
|
|
2542
|
-
handler(data, meta);
|
|
2543
|
-
};
|
|
2544
|
-
const wrappedDispatcher = (envelopeOrRaw) => {
|
|
2545
|
-
if (typeof envelopeOrRaw === "object" && envelopeOrRaw !== null && "eventName" in envelopeOrRaw && "sentAt" in envelopeOrRaw && "sentTo" in envelopeOrRaw && "data" in envelopeOrRaw) {
|
|
2546
|
-
wrappedEnv(envelopeOrRaw);
|
|
2547
|
-
} else {
|
|
2548
|
-
this.dbg({
|
|
2549
|
-
type: "receive",
|
|
2550
|
-
event,
|
|
2551
|
-
envelope: void 0
|
|
2552
|
-
});
|
|
2553
|
-
handler(envelopeOrRaw, void 0);
|
|
2554
|
-
}
|
|
2555
|
-
};
|
|
2556
|
-
const errorWrapped = (e) => {
|
|
2557
|
-
this.dbg({ type: "receive", event, err: String(e) });
|
|
2558
|
-
};
|
|
2559
|
-
socket.on(String(event), wrappedDispatcher);
|
|
2560
|
-
socket.on(`${String(event)}:error`, errorWrapped);
|
|
2561
|
-
let set = this.handlerMap.get(String(event));
|
|
2562
|
-
if (!set) {
|
|
2563
|
-
set = /* @__PURE__ */ new Set();
|
|
2564
|
-
this.handlerMap.set(String(event), set);
|
|
2565
|
-
}
|
|
2566
|
-
const entry = {
|
|
2567
|
-
orig: handler,
|
|
2568
|
-
wrapped: wrappedDispatcher,
|
|
2569
|
-
errorWrapped
|
|
2570
|
-
};
|
|
2571
|
-
set.add(entry);
|
|
2572
|
-
return () => {
|
|
2573
|
-
socket.off(String(event), wrappedDispatcher);
|
|
2574
|
-
socket.off(`${String(event)}:error`, errorWrapped);
|
|
2575
|
-
const s = this.handlerMap.get(String(event));
|
|
2576
|
-
if (s) {
|
|
2577
|
-
s.delete(entry);
|
|
2578
|
-
if (s.size === 0) this.handlerMap.delete(String(event));
|
|
2579
|
-
}
|
|
2580
|
-
this.dbg({ type: "register", phase: "unregister", event });
|
|
2581
|
-
};
|
|
2582
|
-
}
|
|
2583
|
-
/**
|
|
2584
|
-
* Remove all listeners, stop timers, and leave rooms.
|
|
2585
|
-
* Call when disposing the client instance.
|
|
2586
|
-
*/
|
|
2587
|
-
async destroy(leaveMeta) {
|
|
2588
|
-
const socket = this.socket;
|
|
2589
|
-
this.dbg({
|
|
2590
|
-
type: "lifecycle",
|
|
2591
|
-
phase: "destroy_begin",
|
|
2592
|
-
socketId: socket?.id,
|
|
2593
|
-
details: {
|
|
2594
|
-
roomsTracked: this.roomCounts.size,
|
|
2595
|
-
handlerEvents: this.handlerMap.size
|
|
2596
|
-
}
|
|
2597
|
-
});
|
|
2598
|
-
this.stopHeartbeat("destroy");
|
|
2599
|
-
if (socket) {
|
|
2600
|
-
socket.off("connect", this.onConnect);
|
|
2601
|
-
socket.off("reconnect", this.onReconnect);
|
|
2602
|
-
socket.off("disconnect", this.onDisconnect);
|
|
2603
|
-
socket.off("connect_error", this.onConnectError);
|
|
2604
|
-
socket.off("sys:pong", this.onPong);
|
|
2605
|
-
for (const [event, set] of this.handlerMap.entries()) {
|
|
2606
|
-
for (const entry of set) {
|
|
2607
|
-
socket.off(String(event), entry.wrapped);
|
|
2608
|
-
socket.off(`${String(event)}:error`, entry.errorWrapped);
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
this.handlerMap.clear();
|
|
2613
|
-
const toLeave = Array.from(this.roomCounts.entries()).filter(([, count]) => count > 0).map(([room]) => room);
|
|
2614
|
-
if (toLeave.length > 0 && socket) {
|
|
2615
|
-
await this.leaveRooms(toLeave, leaveMeta);
|
|
2616
|
-
}
|
|
2617
|
-
this.roomCounts.clear();
|
|
2618
|
-
this.dbg({
|
|
2619
|
-
type: "lifecycle",
|
|
2620
|
-
phase: "destroy_complete",
|
|
2621
|
-
socketId: socket?.id,
|
|
2622
|
-
details: {
|
|
2623
|
-
roomsTracked: this.roomCounts.size,
|
|
2624
|
-
handlerEvents: this.handlerMap.size
|
|
2625
|
-
}
|
|
2626
|
-
});
|
|
2627
|
-
}
|
|
2628
|
-
/** Pass-throughs. Managing connection is the caller’s responsibility. */
|
|
2629
|
-
disconnect() {
|
|
2630
|
-
if (!this.socket) {
|
|
2631
|
-
this.dbg({
|
|
2632
|
-
type: "connection",
|
|
2633
|
-
phase: "disconnect",
|
|
2634
|
-
reason: "client_disconnect",
|
|
2635
|
-
err: "Socket is null"
|
|
2636
|
-
});
|
|
2637
|
-
return;
|
|
2638
|
-
}
|
|
2639
|
-
this.stopHeartbeat("disconnect");
|
|
2640
|
-
this.socket.disconnect();
|
|
2641
|
-
this.dbg({
|
|
2642
|
-
type: "connection",
|
|
2643
|
-
phase: "disconnect",
|
|
2644
|
-
reason: "client_disconnect",
|
|
2645
|
-
details: {
|
|
2646
|
-
nsp: this.getNamespace(this.socket)
|
|
2647
|
-
}
|
|
2648
|
-
});
|
|
2649
|
-
this.logSocketConfigSnapshot("disconnect_call");
|
|
2650
|
-
}
|
|
2651
|
-
connect() {
|
|
2652
|
-
if (!this.socket) {
|
|
2653
|
-
this.dbg({
|
|
2654
|
-
type: "connection",
|
|
2655
|
-
phase: "connect",
|
|
2656
|
-
err: "Socket is null"
|
|
2657
|
-
});
|
|
2658
|
-
return;
|
|
2004
|
+
return;
|
|
2659
2005
|
}
|
|
2660
2006
|
this.socket.connect();
|
|
2661
2007
|
this.dbg({
|
|
@@ -2669,6 +2015,693 @@ var SocketClient = class {
|
|
|
2669
2015
|
this.logSocketConfigSnapshot("connect_call");
|
|
2670
2016
|
}
|
|
2671
2017
|
};
|
|
2018
|
+
|
|
2019
|
+
// src/sockets/socket.client.context.provider.tsx
|
|
2020
|
+
var React3 = __toESM(require("react"), 1);
|
|
2021
|
+
|
|
2022
|
+
// src/sockets/socket.client.context.client.ts
|
|
2023
|
+
var React = __toESM(require("react"), 1);
|
|
2024
|
+
var SocketCtx = React.createContext(null);
|
|
2025
|
+
function useSocketClient() {
|
|
2026
|
+
const ctx = React.useContext(SocketCtx);
|
|
2027
|
+
if (!ctx)
|
|
2028
|
+
throw new Error("SocketClient not found. Wrap with <SocketProvider>.");
|
|
2029
|
+
return ctx;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// src/sockets/socket.client.context.connection.ts
|
|
2033
|
+
var React2 = __toESM(require("react"), 1);
|
|
2034
|
+
function useSocketConnection(args) {
|
|
2035
|
+
const {
|
|
2036
|
+
event,
|
|
2037
|
+
rooms,
|
|
2038
|
+
onMessage,
|
|
2039
|
+
onCleanup,
|
|
2040
|
+
autoJoin = true,
|
|
2041
|
+
autoLeave = true
|
|
2042
|
+
} = args;
|
|
2043
|
+
const client = useSocketClient();
|
|
2044
|
+
const normalizedRooms = React2.useMemo(
|
|
2045
|
+
() => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms],
|
|
2046
|
+
[rooms]
|
|
2047
|
+
);
|
|
2048
|
+
React2.useEffect(() => {
|
|
2049
|
+
if (autoJoin && normalizedRooms.length > 0)
|
|
2050
|
+
client.joinRooms(normalizedRooms, args.joinMeta);
|
|
2051
|
+
const unsubscribe = client.on(event, (payload, meta) => {
|
|
2052
|
+
onMessage(payload, meta);
|
|
2053
|
+
});
|
|
2054
|
+
return () => {
|
|
2055
|
+
unsubscribe();
|
|
2056
|
+
if (autoLeave && normalizedRooms.length > 0)
|
|
2057
|
+
client.leaveRooms(normalizedRooms, args.leaveMeta);
|
|
2058
|
+
if (onCleanup) onCleanup();
|
|
2059
|
+
};
|
|
2060
|
+
}, [client, event, onMessage, autoJoin, autoLeave, ...normalizedRooms]);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/sockets/socket.client.context.debug.ts
|
|
2064
|
+
function dbg(dbgOpts, e) {
|
|
2065
|
+
if (!dbgOpts?.logger) return;
|
|
2066
|
+
if (!dbgOpts[e.type]) return;
|
|
2067
|
+
dbgOpts.logger(e);
|
|
2068
|
+
}
|
|
2069
|
+
function isProbablySocket(value) {
|
|
2070
|
+
if (!value || typeof value !== "object") return false;
|
|
2071
|
+
const anyVal = value;
|
|
2072
|
+
const ctorName = anyVal.constructor?.name;
|
|
2073
|
+
if (ctorName === "Socket") return true;
|
|
2074
|
+
return ("connected" in anyVal || "recovered" in anyVal) && typeof anyVal.on === "function" && typeof anyVal.emit === "function";
|
|
2075
|
+
}
|
|
2076
|
+
function describeSocketLike(value) {
|
|
2077
|
+
if (!value) return null;
|
|
2078
|
+
const id = value.id ?? "unknown";
|
|
2079
|
+
const connected = value.connected ?? false;
|
|
2080
|
+
const recovered = typeof value.recovered === "boolean" ? ` recovered=${value.recovered}` : "";
|
|
2081
|
+
return `[Socket id=${id} connected=${connected}${recovered}]`;
|
|
2082
|
+
}
|
|
2083
|
+
function safeDescribeHookValue(value) {
|
|
2084
|
+
if (value == null) return value;
|
|
2085
|
+
const valueType = typeof value;
|
|
2086
|
+
if (valueType === "string" || valueType === "number" || valueType === "boolean") {
|
|
2087
|
+
return value;
|
|
2088
|
+
}
|
|
2089
|
+
if (valueType === "bigint" || valueType === "symbol") return String(value);
|
|
2090
|
+
if (valueType === "function")
|
|
2091
|
+
return `[function ${value.name || "anonymous"}]`;
|
|
2092
|
+
if (Array.isArray(value)) return `[array length=${value.length}]`;
|
|
2093
|
+
if (isProbablySocket(value)) return describeSocketLike(value);
|
|
2094
|
+
const ctorName = value.constructor?.name ?? "object";
|
|
2095
|
+
const keys = Object.keys(value);
|
|
2096
|
+
const keyPreview = keys.slice(0, 4).join(",");
|
|
2097
|
+
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
2098
|
+
return `[${ctorName} keys=${keyPreview}${suffix}]`;
|
|
2099
|
+
}
|
|
2100
|
+
function summarizeEvents(events) {
|
|
2101
|
+
if (!events || typeof events !== "object")
|
|
2102
|
+
return safeDescribeHookValue(events);
|
|
2103
|
+
const keys = Object.keys(events);
|
|
2104
|
+
if (!keys.length) return "[events empty]";
|
|
2105
|
+
const preview = keys.slice(0, 4).join(",");
|
|
2106
|
+
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
2107
|
+
return `[events count=${keys.length} keys=${preview}${suffix}]`;
|
|
2108
|
+
}
|
|
2109
|
+
function summarizeBaseOptions(options) {
|
|
2110
|
+
if (!options || typeof options !== "object")
|
|
2111
|
+
return safeDescribeHookValue(options);
|
|
2112
|
+
const obj = options;
|
|
2113
|
+
const keys = Object.keys(obj);
|
|
2114
|
+
if (!keys.length) return "[baseOptions empty]";
|
|
2115
|
+
const preview = keys.slice(0, 4).join(",");
|
|
2116
|
+
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
2117
|
+
const hasDebug = "debug" in obj;
|
|
2118
|
+
return `[baseOptions keys=${preview}${suffix} debug=${hasDebug}]`;
|
|
2119
|
+
}
|
|
2120
|
+
function summarizeMeta(meta, label) {
|
|
2121
|
+
if (meta == null) return null;
|
|
2122
|
+
if (typeof meta !== "object") return safeDescribeHookValue(meta);
|
|
2123
|
+
const keys = Object.keys(meta);
|
|
2124
|
+
if (!keys.length) return `[${label} empty]`;
|
|
2125
|
+
const preview = keys.slice(0, 4).join(",");
|
|
2126
|
+
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
2127
|
+
return `[${label} keys=${preview}${suffix}]`;
|
|
2128
|
+
}
|
|
2129
|
+
function createHookDebugEvent(prev, next, hook) {
|
|
2130
|
+
const reason = prev ? "change" : "init";
|
|
2131
|
+
const changed = Object.keys(next).reduce((acc, dependency) => {
|
|
2132
|
+
const prevValue = prev ? prev[dependency] : void 0;
|
|
2133
|
+
const nextValue = next[dependency];
|
|
2134
|
+
if (!prev || !Object.is(prevValue, nextValue)) {
|
|
2135
|
+
acc.push({
|
|
2136
|
+
dependency,
|
|
2137
|
+
previous: safeDescribeHookValue(prevValue),
|
|
2138
|
+
next: safeDescribeHookValue(nextValue)
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
return acc;
|
|
2142
|
+
}, []);
|
|
2143
|
+
if (!changed.length) return null;
|
|
2144
|
+
return { type: "hook", phase: hook, reason, changes: changed };
|
|
2145
|
+
}
|
|
2146
|
+
function trackHookTrigger({
|
|
2147
|
+
ref,
|
|
2148
|
+
hook,
|
|
2149
|
+
providerDebug,
|
|
2150
|
+
snapshot
|
|
2151
|
+
}) {
|
|
2152
|
+
const prev = ref.current;
|
|
2153
|
+
ref.current = snapshot;
|
|
2154
|
+
if (!providerDebug?.logger || !providerDebug?.hook) return;
|
|
2155
|
+
const event = createHookDebugEvent(prev, snapshot, hook);
|
|
2156
|
+
if (event) dbg(providerDebug, event);
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// src/sockets/socket.client.context.provider.tsx
|
|
2160
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
2161
|
+
function buildSocketProvider(args) {
|
|
2162
|
+
const { events, options: baseOptions } = args;
|
|
2163
|
+
return {
|
|
2164
|
+
SocketProvider: (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2165
|
+
SocketProvider,
|
|
2166
|
+
{
|
|
2167
|
+
events,
|
|
2168
|
+
baseOptions,
|
|
2169
|
+
providerDebug: baseOptions.debug,
|
|
2170
|
+
...props
|
|
2171
|
+
}
|
|
2172
|
+
),
|
|
2173
|
+
useSocketClient: () => useSocketClient(),
|
|
2174
|
+
useSocketConnection: (p) => useSocketConnection(p)
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
function SocketProvider(props) {
|
|
2178
|
+
const {
|
|
2179
|
+
events,
|
|
2180
|
+
baseOptions,
|
|
2181
|
+
children,
|
|
2182
|
+
fallback,
|
|
2183
|
+
providerDebug,
|
|
2184
|
+
destroyLeaveMeta
|
|
2185
|
+
} = props;
|
|
2186
|
+
const [resolvedSocket, setResolvedSocket] = React3.useState(
|
|
2187
|
+
null
|
|
2188
|
+
);
|
|
2189
|
+
const socket = "socket" in props ? props.socket ?? null : resolvedSocket;
|
|
2190
|
+
const providerDebugRef = React3.useRef();
|
|
2191
|
+
providerDebugRef.current = providerDebug;
|
|
2192
|
+
const resolveEffectDebugRef = React3.useRef(null);
|
|
2193
|
+
const clientMemoDebugRef = React3.useRef(null);
|
|
2194
|
+
const destroyEffectDebugRef = React3.useRef(null);
|
|
2195
|
+
React3.useEffect(() => {
|
|
2196
|
+
trackHookTrigger({
|
|
2197
|
+
ref: resolveEffectDebugRef,
|
|
2198
|
+
hook: "resolve_effect",
|
|
2199
|
+
providerDebug: providerDebugRef.current,
|
|
2200
|
+
snapshot: {
|
|
2201
|
+
resolvedSocket: describeSocketLike(resolvedSocket)
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
if (!("getSocket" in props)) return;
|
|
2205
|
+
let cancelled = false;
|
|
2206
|
+
dbg(providerDebugRef.current, { type: "resolve", phase: "start" });
|
|
2207
|
+
if (!resolvedSocket) {
|
|
2208
|
+
Promise.resolve(props.getSocket()).then((s) => {
|
|
2209
|
+
if (cancelled) {
|
|
2210
|
+
dbg(providerDebugRef.current, {
|
|
2211
|
+
type: "resolve",
|
|
2212
|
+
phase: "cancelled"
|
|
2213
|
+
});
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
if (!s) {
|
|
2217
|
+
dbg(providerDebugRef.current, {
|
|
2218
|
+
type: "resolve",
|
|
2219
|
+
phase: "socketMissing"
|
|
2220
|
+
});
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
setResolvedSocket(s);
|
|
2224
|
+
dbg(providerDebugRef.current, { type: "resolve", phase: "ok" });
|
|
2225
|
+
}).catch((err) => {
|
|
2226
|
+
if (cancelled) return;
|
|
2227
|
+
dbg(providerDebugRef.current, {
|
|
2228
|
+
type: "resolve",
|
|
2229
|
+
phase: "error",
|
|
2230
|
+
err: String(err)
|
|
2231
|
+
});
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
return () => {
|
|
2235
|
+
cancelled = true;
|
|
2236
|
+
};
|
|
2237
|
+
}, [resolvedSocket]);
|
|
2238
|
+
trackHookTrigger({
|
|
2239
|
+
ref: clientMemoDebugRef,
|
|
2240
|
+
hook: "client_memo",
|
|
2241
|
+
providerDebug: providerDebugRef.current,
|
|
2242
|
+
snapshot: {
|
|
2243
|
+
events: summarizeEvents(events),
|
|
2244
|
+
baseOptions: summarizeBaseOptions(baseOptions),
|
|
2245
|
+
socket: describeSocketLike(socket)
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
const client = React3.useMemo(() => {
|
|
2249
|
+
if (!socket) {
|
|
2250
|
+
dbg(providerDebugRef.current, {
|
|
2251
|
+
type: "client",
|
|
2252
|
+
phase: "init",
|
|
2253
|
+
missing: true
|
|
2254
|
+
});
|
|
2255
|
+
return null;
|
|
2256
|
+
}
|
|
2257
|
+
const c = new SocketClient(events, { ...baseOptions, socket });
|
|
2258
|
+
dbg(providerDebugRef.current, {
|
|
2259
|
+
type: "client",
|
|
2260
|
+
phase: "init",
|
|
2261
|
+
missing: false
|
|
2262
|
+
});
|
|
2263
|
+
return c;
|
|
2264
|
+
}, [events, baseOptions, socket]);
|
|
2265
|
+
const destroyLeaveMetaRef = React3.useRef(destroyLeaveMeta);
|
|
2266
|
+
React3.useEffect(() => {
|
|
2267
|
+
destroyLeaveMetaRef.current = destroyLeaveMeta;
|
|
2268
|
+
}, [destroyLeaveMeta]);
|
|
2269
|
+
React3.useEffect(() => {
|
|
2270
|
+
trackHookTrigger({
|
|
2271
|
+
ref: destroyEffectDebugRef,
|
|
2272
|
+
hook: "destroy_effect",
|
|
2273
|
+
providerDebug: providerDebugRef.current,
|
|
2274
|
+
snapshot: {
|
|
2275
|
+
hasClient: !!client,
|
|
2276
|
+
destroyLeaveMeta: summarizeMeta(
|
|
2277
|
+
destroyLeaveMetaRef.current,
|
|
2278
|
+
"destroyLeaveMeta"
|
|
2279
|
+
)
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
return () => {
|
|
2283
|
+
if (client) {
|
|
2284
|
+
client.destroy(destroyLeaveMetaRef.current);
|
|
2285
|
+
dbg(providerDebugRef.current, { type: "client", phase: "destroy" });
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
}, [client]);
|
|
2289
|
+
dbg(providerDebugRef.current, { type: "render", hasClient: !!client });
|
|
2290
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SocketCtx.Provider, { value: client, children: client == null ? fallback ?? children : children });
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
// src/sockets/socketedRoute/socket.client.helper.route.ts
|
|
2294
|
+
var import_react4 = require("react");
|
|
2295
|
+
|
|
2296
|
+
// src/sockets/socketedRoute/socket.client.helper.debug.ts
|
|
2297
|
+
var objectReferenceIds = /* @__PURE__ */ new WeakMap();
|
|
2298
|
+
var objectReferenceCounter = 0;
|
|
2299
|
+
function describeObjectReference(value) {
|
|
2300
|
+
if (value == null) return null;
|
|
2301
|
+
const valueType = typeof value;
|
|
2302
|
+
if (valueType !== "object" && valueType !== "function") return null;
|
|
2303
|
+
const obj = value;
|
|
2304
|
+
let id = objectReferenceIds.get(obj);
|
|
2305
|
+
if (!id) {
|
|
2306
|
+
id = ++objectReferenceCounter;
|
|
2307
|
+
objectReferenceIds.set(obj, id);
|
|
2308
|
+
}
|
|
2309
|
+
return `ref#${id}`;
|
|
2310
|
+
}
|
|
2311
|
+
function safeJsonKey(value) {
|
|
2312
|
+
try {
|
|
2313
|
+
const serialized = JSON.stringify(value ?? null);
|
|
2314
|
+
return serialized ?? "null";
|
|
2315
|
+
} catch {
|
|
2316
|
+
return "[unserializable]";
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
function isSameObjectReference(prev, next) {
|
|
2320
|
+
if (prev !== next) return false;
|
|
2321
|
+
if (next == null) return false;
|
|
2322
|
+
const valueType = typeof next;
|
|
2323
|
+
return valueType === "object" || valueType === "function";
|
|
2324
|
+
}
|
|
2325
|
+
function shouldWarnSocketMutationGuard() {
|
|
2326
|
+
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
2327
|
+
return nodeEnv !== "production";
|
|
2328
|
+
}
|
|
2329
|
+
function safeDescribeHookValue2(value) {
|
|
2330
|
+
if (value == null) return value;
|
|
2331
|
+
const valueType = typeof value;
|
|
2332
|
+
if (valueType === "string" || valueType === "number" || valueType === "boolean") {
|
|
2333
|
+
return value;
|
|
2334
|
+
}
|
|
2335
|
+
if (valueType === "bigint" || valueType === "symbol") return String(value);
|
|
2336
|
+
if (valueType === "function")
|
|
2337
|
+
return `[function ${value.name || "anonymous"}]`;
|
|
2338
|
+
if (Array.isArray(value)) return `[array length=${value.length}]`;
|
|
2339
|
+
const ctorName = value.constructor?.name ?? "object";
|
|
2340
|
+
const keys = Object.keys(value);
|
|
2341
|
+
const keyPreview = keys.slice(0, 4).join(",");
|
|
2342
|
+
const suffix = keys.length > 4 ? ",\u2026" : "";
|
|
2343
|
+
const ref = describeObjectReference(value);
|
|
2344
|
+
return `[${ctorName}${ref ? ` ${ref}` : ""} keys=${keyPreview}${suffix}]`;
|
|
2345
|
+
}
|
|
2346
|
+
function createHookDebugEvent2(prev, next, phase) {
|
|
2347
|
+
const reason = prev ? "change" : "init";
|
|
2348
|
+
const changed = Object.keys(next).reduce((acc, dependency) => {
|
|
2349
|
+
const prevValue = prev ? prev[dependency] : void 0;
|
|
2350
|
+
const nextValue = next[dependency];
|
|
2351
|
+
if (!prev || !Object.is(prevValue, nextValue)) {
|
|
2352
|
+
acc.push({
|
|
2353
|
+
dependency,
|
|
2354
|
+
previous: safeDescribeHookValue2(prevValue),
|
|
2355
|
+
next: safeDescribeHookValue2(nextValue)
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
return acc;
|
|
2359
|
+
}, []);
|
|
2360
|
+
if (!changed.length) return null;
|
|
2361
|
+
return { type: "hook", phase, reason, changes: changed };
|
|
2362
|
+
}
|
|
2363
|
+
function dbg2(debug, event) {
|
|
2364
|
+
if (!debug?.logger) return;
|
|
2365
|
+
if (!debug[event.type]) return;
|
|
2366
|
+
debug.logger(event);
|
|
2367
|
+
}
|
|
2368
|
+
function trackHookTrigger2({
|
|
2369
|
+
ref,
|
|
2370
|
+
phase,
|
|
2371
|
+
debug,
|
|
2372
|
+
snapshot
|
|
2373
|
+
}) {
|
|
2374
|
+
const prev = ref.current;
|
|
2375
|
+
ref.current = snapshot;
|
|
2376
|
+
if (!debug?.logger || !debug?.hook) return;
|
|
2377
|
+
const event = createHookDebugEvent2(prev, snapshot, phase);
|
|
2378
|
+
if (event) dbg2(debug, event);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
// src/sockets/socketedRoute/socket.client.helper.rooms.ts
|
|
2382
|
+
function normalizeRooms(rooms) {
|
|
2383
|
+
if (rooms == null) return [];
|
|
2384
|
+
const list = Array.isArray(rooms) ? rooms : [rooms];
|
|
2385
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2386
|
+
const normalized = [];
|
|
2387
|
+
for (const r of list) {
|
|
2388
|
+
if (typeof r !== "string") continue;
|
|
2389
|
+
if (seen.has(r)) continue;
|
|
2390
|
+
seen.add(r);
|
|
2391
|
+
normalized.push(r);
|
|
2392
|
+
}
|
|
2393
|
+
return normalized;
|
|
2394
|
+
}
|
|
2395
|
+
function arrayShallowEqual(a, b) {
|
|
2396
|
+
if (a.length !== b.length) return false;
|
|
2397
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
2398
|
+
if (a[i] !== b[i]) return false;
|
|
2399
|
+
}
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
function roomStateEqual(prev, next) {
|
|
2403
|
+
return arrayShallowEqual(prev.rooms, next.rooms) && safeJsonKey(prev.joinMeta) === safeJsonKey(next.joinMeta) && safeJsonKey(prev.leaveMeta) === safeJsonKey(next.leaveMeta);
|
|
2404
|
+
}
|
|
2405
|
+
function mergeRoomState(prev, toRoomsResult) {
|
|
2406
|
+
const merged = new Set(prev.rooms);
|
|
2407
|
+
for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
|
|
2408
|
+
return {
|
|
2409
|
+
rooms: Array.from(merged),
|
|
2410
|
+
joinMeta: toRoomsResult.joinMeta ?? prev.joinMeta,
|
|
2411
|
+
leaveMeta: toRoomsResult.leaveMeta ?? prev.leaveMeta
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
function roomsFromData(data, toRooms) {
|
|
2415
|
+
if (data == null) return { rooms: [] };
|
|
2416
|
+
let state = { rooms: [] };
|
|
2417
|
+
const add = (input) => {
|
|
2418
|
+
const mergeForValue = (value) => {
|
|
2419
|
+
state = mergeRoomState(state, toRooms(value));
|
|
2420
|
+
};
|
|
2421
|
+
if (Array.isArray(input)) {
|
|
2422
|
+
input.forEach((entry) => mergeForValue(entry));
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
if (input && typeof input === "object") {
|
|
2426
|
+
const maybeItems = input.items;
|
|
2427
|
+
if (Array.isArray(maybeItems)) {
|
|
2428
|
+
maybeItems.forEach((entry) => mergeForValue(entry));
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
mergeForValue(input);
|
|
2433
|
+
};
|
|
2434
|
+
const maybePages = data?.pages;
|
|
2435
|
+
if (Array.isArray(maybePages)) {
|
|
2436
|
+
for (const page of maybePages) add(page);
|
|
2437
|
+
return state;
|
|
2438
|
+
}
|
|
2439
|
+
add(data);
|
|
2440
|
+
return state;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// src/sockets/socketedRoute/socket.client.helper.route.ts
|
|
2444
|
+
function isSocketClientUnavailableError(err) {
|
|
2445
|
+
return err instanceof Error && err.message.includes("SocketClient not found");
|
|
2446
|
+
}
|
|
2447
|
+
function buildSocketedRoute(options) {
|
|
2448
|
+
const { built, toRooms, applySocket, useSocketClient: useSocketClient2, debug } = options;
|
|
2449
|
+
const { useEndpoint: useInnerEndpoint, ...rest } = built;
|
|
2450
|
+
const useEndpoint = (...useArgs) => {
|
|
2451
|
+
let client = null;
|
|
2452
|
+
let socketClientError = null;
|
|
2453
|
+
try {
|
|
2454
|
+
client = useSocketClient2();
|
|
2455
|
+
} catch (err) {
|
|
2456
|
+
if (!isSocketClientUnavailableError(err)) throw err;
|
|
2457
|
+
socketClientError = err;
|
|
2458
|
+
}
|
|
2459
|
+
const endpointResult = useInnerEndpoint(
|
|
2460
|
+
...useArgs
|
|
2461
|
+
);
|
|
2462
|
+
const argsKey = (0, import_react4.useMemo)(() => safeJsonKey(useArgs[0] ?? null), [useArgs]);
|
|
2463
|
+
const [roomState, setRoomState] = (0, import_react4.useState)(
|
|
2464
|
+
() => roomsFromData(endpointResult.data, toRooms)
|
|
2465
|
+
);
|
|
2466
|
+
const renderCountRef = (0, import_react4.useRef)(0);
|
|
2467
|
+
const clientReadyRef = (0, import_react4.useRef)(null);
|
|
2468
|
+
const onReceiveEffectDebugRef = (0, import_react4.useRef)(null);
|
|
2469
|
+
const deriveRoomsEffectDebugRef = (0, import_react4.useRef)(null);
|
|
2470
|
+
const joinRoomsEffectDebugRef = (0, import_react4.useRef)(null);
|
|
2471
|
+
const applySocketEffectDebugRef = (0, import_react4.useRef)(null);
|
|
2472
|
+
renderCountRef.current += 1;
|
|
2473
|
+
const roomsKey = (0, import_react4.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
|
|
2474
|
+
const joinMetaKey = (0, import_react4.useMemo)(
|
|
2475
|
+
() => safeJsonKey(roomState.joinMeta ?? null),
|
|
2476
|
+
[roomState.joinMeta]
|
|
2477
|
+
);
|
|
2478
|
+
const leaveMetaKey = (0, import_react4.useMemo)(
|
|
2479
|
+
() => safeJsonKey(roomState.leaveMeta ?? null),
|
|
2480
|
+
[roomState.leaveMeta]
|
|
2481
|
+
);
|
|
2482
|
+
const hasClient = !!client;
|
|
2483
|
+
if (clientReadyRef.current !== hasClient) {
|
|
2484
|
+
clientReadyRef.current = hasClient;
|
|
2485
|
+
dbg2(debug, {
|
|
2486
|
+
type: "client",
|
|
2487
|
+
phase: hasClient ? "ready" : "missing",
|
|
2488
|
+
err: hasClient ? void 0 : String(socketClientError)
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
dbg2(debug, {
|
|
2492
|
+
type: "render",
|
|
2493
|
+
renderCount: renderCountRef.current,
|
|
2494
|
+
hasClient,
|
|
2495
|
+
argsKey,
|
|
2496
|
+
rooms: roomState.rooms,
|
|
2497
|
+
roomsKey,
|
|
2498
|
+
joinMetaKey,
|
|
2499
|
+
leaveMetaKey
|
|
2500
|
+
});
|
|
2501
|
+
(0, import_react4.useEffect)(() => {
|
|
2502
|
+
trackHookTrigger2({
|
|
2503
|
+
ref: onReceiveEffectDebugRef,
|
|
2504
|
+
phase: "endpoint_on_receive_effect",
|
|
2505
|
+
debug,
|
|
2506
|
+
snapshot: {
|
|
2507
|
+
endpointResultRef: describeObjectReference(endpointResult),
|
|
2508
|
+
endpointDataRef: describeObjectReference(endpointResult.data),
|
|
2509
|
+
toRoomsRef: describeObjectReference(toRooms)
|
|
2510
|
+
}
|
|
2511
|
+
});
|
|
2512
|
+
const unsubscribe = endpointResult.onReceive((data) => {
|
|
2513
|
+
setRoomState((prev) => {
|
|
2514
|
+
const next = mergeRoomState(prev, toRooms(data));
|
|
2515
|
+
return roomStateEqual(prev, next) ? prev : next;
|
|
2516
|
+
});
|
|
2517
|
+
});
|
|
2518
|
+
return unsubscribe;
|
|
2519
|
+
}, [endpointResult, toRooms, debug]);
|
|
2520
|
+
(0, import_react4.useEffect)(() => {
|
|
2521
|
+
trackHookTrigger2({
|
|
2522
|
+
ref: deriveRoomsEffectDebugRef,
|
|
2523
|
+
phase: "derive_rooms_effect",
|
|
2524
|
+
debug,
|
|
2525
|
+
snapshot: {
|
|
2526
|
+
endpointDataRef: describeObjectReference(endpointResult.data),
|
|
2527
|
+
toRoomsRef: describeObjectReference(toRooms)
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2530
|
+
const next = roomsFromData(
|
|
2531
|
+
endpointResult.data,
|
|
2532
|
+
toRooms
|
|
2533
|
+
);
|
|
2534
|
+
setRoomState((prev) => roomStateEqual(prev, next) ? prev : next);
|
|
2535
|
+
}, [endpointResult.data, toRooms, debug]);
|
|
2536
|
+
(0, import_react4.useEffect)(() => {
|
|
2537
|
+
trackHookTrigger2({
|
|
2538
|
+
ref: joinRoomsEffectDebugRef,
|
|
2539
|
+
phase: "join_rooms_effect",
|
|
2540
|
+
debug,
|
|
2541
|
+
snapshot: {
|
|
2542
|
+
hasClient: !!client,
|
|
2543
|
+
clientRef: describeObjectReference(client),
|
|
2544
|
+
roomsKey,
|
|
2545
|
+
joinMetaKey,
|
|
2546
|
+
leaveMetaKey
|
|
2547
|
+
}
|
|
2548
|
+
});
|
|
2549
|
+
if (!client) {
|
|
2550
|
+
dbg2(debug, {
|
|
2551
|
+
type: "room",
|
|
2552
|
+
phase: "join_skip",
|
|
2553
|
+
rooms: roomState.rooms,
|
|
2554
|
+
reason: "socket_client_missing"
|
|
2555
|
+
});
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (roomState.rooms.length === 0) {
|
|
2559
|
+
dbg2(debug, {
|
|
2560
|
+
type: "room",
|
|
2561
|
+
phase: "join_skip",
|
|
2562
|
+
rooms: [],
|
|
2563
|
+
reason: "no_rooms"
|
|
2564
|
+
});
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
const { joinMeta, leaveMeta } = roomState;
|
|
2568
|
+
if (!joinMeta || !leaveMeta) {
|
|
2569
|
+
dbg2(debug, {
|
|
2570
|
+
type: "room",
|
|
2571
|
+
phase: "join_skip",
|
|
2572
|
+
rooms: roomState.rooms,
|
|
2573
|
+
reason: "missing_meta"
|
|
2574
|
+
});
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
let active = true;
|
|
2578
|
+
dbg2(debug, {
|
|
2579
|
+
type: "room",
|
|
2580
|
+
phase: "join_attempt",
|
|
2581
|
+
rooms: roomState.rooms
|
|
2582
|
+
});
|
|
2583
|
+
(async () => {
|
|
2584
|
+
try {
|
|
2585
|
+
await client.joinRooms(roomState.rooms, joinMeta);
|
|
2586
|
+
} catch (err) {
|
|
2587
|
+
dbg2(debug, {
|
|
2588
|
+
type: "room",
|
|
2589
|
+
phase: "join_error",
|
|
2590
|
+
rooms: roomState.rooms,
|
|
2591
|
+
err: String(err)
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
})();
|
|
2595
|
+
return () => {
|
|
2596
|
+
if (!active) return;
|
|
2597
|
+
active = false;
|
|
2598
|
+
if (roomState.rooms.length === 0) {
|
|
2599
|
+
dbg2(debug, {
|
|
2600
|
+
type: "room",
|
|
2601
|
+
phase: "leave_skip",
|
|
2602
|
+
rooms: [],
|
|
2603
|
+
reason: "no_rooms"
|
|
2604
|
+
});
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
dbg2(debug, {
|
|
2608
|
+
type: "room",
|
|
2609
|
+
phase: "leave_attempt",
|
|
2610
|
+
rooms: roomState.rooms
|
|
2611
|
+
});
|
|
2612
|
+
void client.leaveRooms(roomState.rooms, leaveMeta).catch((err) => {
|
|
2613
|
+
dbg2(debug, {
|
|
2614
|
+
type: "room",
|
|
2615
|
+
phase: "leave_error",
|
|
2616
|
+
rooms: roomState.rooms,
|
|
2617
|
+
err: String(err)
|
|
2618
|
+
});
|
|
2619
|
+
});
|
|
2620
|
+
};
|
|
2621
|
+
}, [client, roomsKey, joinMetaKey, leaveMetaKey, debug]);
|
|
2622
|
+
(0, import_react4.useEffect)(() => {
|
|
2623
|
+
trackHookTrigger2({
|
|
2624
|
+
ref: applySocketEffectDebugRef,
|
|
2625
|
+
phase: "apply_socket_effect",
|
|
2626
|
+
debug,
|
|
2627
|
+
snapshot: {
|
|
2628
|
+
hasClient: !!client,
|
|
2629
|
+
clientRef: describeObjectReference(client),
|
|
2630
|
+
applySocketKeys: Object.keys(applySocket).sort().join(","),
|
|
2631
|
+
argsKey,
|
|
2632
|
+
toRoomsRef: describeObjectReference(toRooms)
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
if (!client) return;
|
|
2636
|
+
const queue = [];
|
|
2637
|
+
const sameRefWarnedEvents = /* @__PURE__ */ new Set();
|
|
2638
|
+
let draining = false;
|
|
2639
|
+
let active = true;
|
|
2640
|
+
const drainQueue = () => {
|
|
2641
|
+
if (!active || draining) return;
|
|
2642
|
+
draining = true;
|
|
2643
|
+
try {
|
|
2644
|
+
while (active && queue.length > 0) {
|
|
2645
|
+
const nextUpdate = queue.shift();
|
|
2646
|
+
if (!nextUpdate) continue;
|
|
2647
|
+
built.setData(
|
|
2648
|
+
(prev) => {
|
|
2649
|
+
const next = nextUpdate.fn(
|
|
2650
|
+
prev,
|
|
2651
|
+
nextUpdate.payload,
|
|
2652
|
+
nextUpdate.meta ? { ...nextUpdate.meta, args: useArgs } : { args: useArgs }
|
|
2653
|
+
);
|
|
2654
|
+
if (next === null) return prev;
|
|
2655
|
+
if (shouldWarnSocketMutationGuard() && isSameObjectReference(prev, next) && !sameRefWarnedEvents.has(nextUpdate.event)) {
|
|
2656
|
+
sameRefWarnedEvents.add(nextUpdate.event);
|
|
2657
|
+
console.warn(
|
|
2658
|
+
`[socketedRoute] applySocket("${nextUpdate.event}") returned the previous reference. Return a new object/array for updates, or return null for no change.`
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
const nextRoomState = roomsFromData(
|
|
2662
|
+
next,
|
|
2663
|
+
toRooms
|
|
2664
|
+
);
|
|
2665
|
+
setRoomState(
|
|
2666
|
+
(prevRoomState) => roomStateEqual(prevRoomState, nextRoomState) ? prevRoomState : nextRoomState
|
|
2667
|
+
);
|
|
2668
|
+
return next;
|
|
2669
|
+
},
|
|
2670
|
+
...useArgs
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
} finally {
|
|
2674
|
+
draining = false;
|
|
2675
|
+
if (active && queue.length > 0) drainQueue();
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
const entries = Object.entries(applySocket).filter(
|
|
2679
|
+
([_event, fn]) => typeof fn === "function"
|
|
2680
|
+
);
|
|
2681
|
+
const unsubscribes = entries.map(
|
|
2682
|
+
([ev, fn]) => client.on(ev, (payload, meta) => {
|
|
2683
|
+
queue.push({
|
|
2684
|
+
event: ev,
|
|
2685
|
+
fn,
|
|
2686
|
+
payload,
|
|
2687
|
+
...meta ? { meta } : {}
|
|
2688
|
+
});
|
|
2689
|
+
drainQueue();
|
|
2690
|
+
})
|
|
2691
|
+
);
|
|
2692
|
+
return () => {
|
|
2693
|
+
active = false;
|
|
2694
|
+
queue.length = 0;
|
|
2695
|
+
unsubscribes.forEach((u) => u?.());
|
|
2696
|
+
};
|
|
2697
|
+
}, [client, applySocket, built, argsKey, toRooms, debug]);
|
|
2698
|
+
return { ...endpointResult, rooms: roomState.rooms };
|
|
2699
|
+
};
|
|
2700
|
+
return {
|
|
2701
|
+
...rest,
|
|
2702
|
+
useEndpoint
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2672
2705
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2673
2706
|
0 && (module.exports = {
|
|
2674
2707
|
HttpError,
|