@emeryld/rrroutes-client 2.6.3 → 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 +1261 -1247
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1265 -1251
- 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,1355 +1339,1369 @@ 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 isSameObjectReference(prev, next) {
|
|
1656
|
-
if (prev !== next) return false;
|
|
1657
|
-
if (next == null) return false;
|
|
1658
|
-
const valueType = typeof next;
|
|
1659
|
-
return valueType === "object" || valueType === "function";
|
|
1660
|
-
}
|
|
1661
|
-
function shouldWarnSocketMutationGuard() {
|
|
1662
|
-
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
1663
|
-
return nodeEnv !== "production";
|
|
1664
|
-
}
|
|
1665
|
-
function safeDescribeHookValue2(value) {
|
|
1666
|
-
if (value == null) return value;
|
|
1667
|
-
const valueType = typeof value;
|
|
1668
|
-
if (valueType === "string" || valueType === "number" || valueType === "boolean") {
|
|
1669
|
-
return value;
|
|
1557
|
+
getVerboseDetails(details) {
|
|
1558
|
+
if (!this.debug.verbose) return void 0;
|
|
1559
|
+
return details;
|
|
1670
1560
|
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
debug.logger(event);
|
|
1703
|
-
}
|
|
1704
|
-
function trackHookTrigger2({
|
|
1705
|
-
ref,
|
|
1706
|
-
phase,
|
|
1707
|
-
debug,
|
|
1708
|
-
snapshot
|
|
1709
|
-
}) {
|
|
1710
|
-
const prev = ref.current;
|
|
1711
|
-
ref.current = snapshot;
|
|
1712
|
-
if (!debug?.logger || !debug?.hook) return;
|
|
1713
|
-
const event = createHookDebugEvent2(prev, snapshot, phase);
|
|
1714
|
-
if (event) dbg2(debug, event);
|
|
1715
|
-
}
|
|
1716
|
-
function isSocketClientUnavailableError(err) {
|
|
1717
|
-
return err instanceof Error && err.message.includes("SocketClient not found");
|
|
1718
|
-
}
|
|
1719
|
-
function mergeRoomState(prev, toRoomsResult) {
|
|
1720
|
-
const merged = new Set(prev.rooms);
|
|
1721
|
-
for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
|
|
1722
|
-
return {
|
|
1723
|
-
rooms: Array.from(merged),
|
|
1724
|
-
joinMeta: toRoomsResult.joinMeta ?? prev.joinMeta,
|
|
1725
|
-
leaveMeta: toRoomsResult.leaveMeta ?? prev.leaveMeta
|
|
1726
|
-
};
|
|
1727
|
-
}
|
|
1728
|
-
function roomsFromData(data, toRooms) {
|
|
1729
|
-
if (data == null) return { rooms: [] };
|
|
1730
|
-
let state = { rooms: [] };
|
|
1731
|
-
const add = (input) => {
|
|
1732
|
-
const mergeForValue = (value) => {
|
|
1733
|
-
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
|
|
1734
1592
|
};
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
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);
|
|
1745
1603
|
}
|
|
1746
|
-
mergeForValue(input);
|
|
1747
|
-
};
|
|
1748
|
-
const maybePages = data?.pages;
|
|
1749
|
-
if (Array.isArray(maybePages)) {
|
|
1750
|
-
for (const page of maybePages) add(page);
|
|
1751
|
-
return state;
|
|
1752
1604
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
const { built, toRooms, applySocket, useSocketClient: useSocketClient2, debug } = options;
|
|
1758
|
-
const { useEndpoint: useInnerEndpoint, ...rest } = built;
|
|
1759
|
-
const useEndpoint = (...useArgs) => {
|
|
1760
|
-
let client = null;
|
|
1761
|
-
let socketClientError = null;
|
|
1762
|
-
try {
|
|
1763
|
-
client = useSocketClient2();
|
|
1764
|
-
} catch (err) {
|
|
1765
|
-
if (!isSocketClientUnavailableError(err)) throw err;
|
|
1766
|
-
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);
|
|
1767
1609
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
const roomsKey = (0, import_react4.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
|
|
1783
|
-
const joinMetaKey = (0, import_react4.useMemo)(
|
|
1784
|
-
() => safeJsonKey(roomState.joinMeta ?? null),
|
|
1785
|
-
[roomState.joinMeta]
|
|
1786
|
-
);
|
|
1787
|
-
const leaveMetaKey = (0, import_react4.useMemo)(
|
|
1788
|
-
() => safeJsonKey(roomState.leaveMeta ?? null),
|
|
1789
|
-
[roomState.leaveMeta]
|
|
1790
|
-
);
|
|
1791
|
-
const hasClient = !!client;
|
|
1792
|
-
if (clientReadyRef.current !== hasClient) {
|
|
1793
|
-
clientReadyRef.current = hasClient;
|
|
1794
|
-
dbg2(debug, {
|
|
1795
|
-
type: "client",
|
|
1796
|
-
phase: hasClient ? "ready" : "missing",
|
|
1797
|
-
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"
|
|
1798
1624
|
});
|
|
1625
|
+
return;
|
|
1799
1626
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
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
|
+
}
|
|
1809
1635
|
});
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
endpointResultRef: describeObjectReference(endpointResult),
|
|
1817
|
-
endpointDataRef: describeObjectReference(endpointResult.data),
|
|
1818
|
-
toRoomsRef: describeObjectReference(toRooms)
|
|
1819
|
-
}
|
|
1820
|
-
});
|
|
1821
|
-
const unsubscribe = endpointResult.onReceive((data) => {
|
|
1822
|
-
setRoomState((prev) => {
|
|
1823
|
-
const next = mergeRoomState(prev, toRooms(data));
|
|
1824
|
-
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"
|
|
1825
1642
|
});
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const payload = await this.getSysEvent("sys:ping")({
|
|
1646
|
+
socket,
|
|
1647
|
+
client: this
|
|
1826
1648
|
});
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
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)
|
|
1656
|
+
});
|
|
1657
|
+
if (this.environment === "development") {
|
|
1658
|
+
console.warn(
|
|
1659
|
+
"[socket] ping schema validation failed",
|
|
1660
|
+
check.error.issues
|
|
1661
|
+
);
|
|
1837
1662
|
}
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
const dataToSend = check.data;
|
|
1666
|
+
socket.emit("sys:ping", dataToSend);
|
|
1667
|
+
this.dbg({
|
|
1668
|
+
type: "heartbeat",
|
|
1669
|
+
phase: "ping_emit",
|
|
1670
|
+
payload: dataToSend
|
|
1838
1671
|
});
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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) {
|
|
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) {
|
|
1703
|
+
this.dbg({
|
|
1704
|
+
type: "emit",
|
|
1705
|
+
event,
|
|
1706
|
+
err: "payload_validation_failed",
|
|
1707
|
+
details: this.getValidationDetails(parsed.error)
|
|
1857
1708
|
});
|
|
1858
|
-
|
|
1859
|
-
|
|
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) {
|
|
1720
|
+
this.dbg({
|
|
1721
|
+
type: "room",
|
|
1722
|
+
phase: "join",
|
|
1723
|
+
rooms: this.toArray(rooms),
|
|
1724
|
+
err: "Socket is null"
|
|
1725
|
+
});
|
|
1726
|
+
throw new Error("Socket is null in joinRooms method");
|
|
1727
|
+
}
|
|
1728
|
+
if (!await this.getSysEvent("sys:room_join")({
|
|
1729
|
+
rooms,
|
|
1730
|
+
meta,
|
|
1731
|
+
socket: this.socket,
|
|
1732
|
+
client: this
|
|
1733
|
+
})) {
|
|
1734
|
+
this.dbg({
|
|
1735
|
+
type: "room",
|
|
1736
|
+
phase: "join",
|
|
1737
|
+
rooms: this.toArray(rooms),
|
|
1738
|
+
err: "sys:room_join handler aborted join"
|
|
1739
|
+
});
|
|
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
|
|
1754
|
+
});
|
|
1755
|
+
if (!payloadResult.success) {
|
|
1756
|
+
this.rollbackJoinIncrement(toJoin);
|
|
1757
|
+
this.dbg({
|
|
1860
1758
|
type: "room",
|
|
1861
|
-
phase: "
|
|
1862
|
-
rooms:
|
|
1863
|
-
|
|
1759
|
+
phase: "join",
|
|
1760
|
+
rooms: toJoin,
|
|
1761
|
+
err: "payload validation failed",
|
|
1762
|
+
details: this.getValidationDetails(payloadResult.error)
|
|
1864
1763
|
});
|
|
1865
|
-
return
|
|
1764
|
+
return async () => {
|
|
1765
|
+
};
|
|
1866
1766
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
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) {
|
|
1778
|
+
this.dbg({
|
|
1779
|
+
type: "room",
|
|
1780
|
+
phase: "leave",
|
|
1781
|
+
rooms: this.toArray(rooms),
|
|
1782
|
+
err: "Socket is null"
|
|
1783
|
+
});
|
|
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
|
+
})) {
|
|
1792
|
+
this.dbg({
|
|
1793
|
+
type: "room",
|
|
1794
|
+
phase: "leave",
|
|
1795
|
+
rooms: this.toArray(rooms),
|
|
1796
|
+
err: "sys:room_leave handler aborted leave"
|
|
1797
|
+
});
|
|
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
|
|
1813
|
+
});
|
|
1814
|
+
if (!payloadResult.success) {
|
|
1815
|
+
this.rollbackLeaveDecrement(toLeave);
|
|
1816
|
+
this.dbg({
|
|
1869
1817
|
type: "room",
|
|
1870
|
-
phase: "
|
|
1871
|
-
rooms:
|
|
1872
|
-
|
|
1818
|
+
phase: "leave",
|
|
1819
|
+
rooms: toLeave,
|
|
1820
|
+
err: "payload validation failed",
|
|
1821
|
+
details: this.getValidationDetails(payloadResult.error)
|
|
1873
1822
|
});
|
|
1874
1823
|
return;
|
|
1875
1824
|
}
|
|
1876
|
-
const
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
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);
|
|
1849
|
+
if (!parsed.success) {
|
|
1850
|
+
this.dbg({
|
|
1851
|
+
type: "receive",
|
|
1852
|
+
event,
|
|
1853
|
+
err: "payload_validation_failed",
|
|
1854
|
+
details: this.getValidationDetails(parsed.error)
|
|
1883
1855
|
});
|
|
1884
1856
|
return;
|
|
1885
1857
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
return () => {
|
|
1905
|
-
if (!active) return;
|
|
1906
|
-
active = false;
|
|
1907
|
-
if (roomState.rooms.length === 0) {
|
|
1908
|
-
dbg2(debug, {
|
|
1909
|
-
type: "room",
|
|
1910
|
-
phase: "leave_skip",
|
|
1911
|
-
rooms: [],
|
|
1912
|
-
reason: "no_rooms"
|
|
1913
|
-
});
|
|
1914
|
-
return;
|
|
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
|
|
1915
1876
|
}
|
|
1916
|
-
dbg2(debug, {
|
|
1917
|
-
type: "room",
|
|
1918
|
-
phase: "leave_attempt",
|
|
1919
|
-
rooms: roomState.rooms
|
|
1920
|
-
});
|
|
1921
|
-
void client.leaveRooms(roomState.rooms, leaveMeta).catch((err) => {
|
|
1922
|
-
dbg2(debug, {
|
|
1923
|
-
type: "room",
|
|
1924
|
-
phase: "leave_error",
|
|
1925
|
-
rooms: roomState.rooms,
|
|
1926
|
-
err: String(err)
|
|
1927
|
-
});
|
|
1928
|
-
});
|
|
1929
|
-
};
|
|
1930
|
-
}, [client, roomsKey, joinMetaKey, leaveMetaKey, debug]);
|
|
1931
|
-
(0, import_react4.useEffect)(() => {
|
|
1932
|
-
trackHookTrigger2({
|
|
1933
|
-
ref: applySocketEffectDebugRef,
|
|
1934
|
-
phase: "apply_socket_effect",
|
|
1935
|
-
debug,
|
|
1936
|
-
snapshot: {
|
|
1937
|
-
hasClient: !!client,
|
|
1938
|
-
clientRef: describeObjectReference(client),
|
|
1939
|
-
applySocketKeys: Object.keys(applySocket).sort().join(","),
|
|
1940
|
-
argsKey,
|
|
1941
|
-
toRoomsRef: describeObjectReference(toRooms)
|
|
1942
|
-
}
|
|
1943
|
-
});
|
|
1944
|
-
if (!client) return;
|
|
1945
|
-
const queue = [];
|
|
1946
|
-
const sameRefWarnedEvents = /* @__PURE__ */ new Set();
|
|
1947
|
-
let draining = false;
|
|
1948
|
-
let active = true;
|
|
1949
|
-
const drainQueue = () => {
|
|
1950
|
-
if (!active || draining) return;
|
|
1951
|
-
draining = true;
|
|
1952
|
-
try {
|
|
1953
|
-
while (active && queue.length > 0) {
|
|
1954
|
-
const nextUpdate = queue.shift();
|
|
1955
|
-
if (!nextUpdate) continue;
|
|
1956
|
-
built.setData(
|
|
1957
|
-
(prev) => {
|
|
1958
|
-
const next = nextUpdate.fn(
|
|
1959
|
-
prev,
|
|
1960
|
-
nextUpdate.payload,
|
|
1961
|
-
nextUpdate.meta ? { ...nextUpdate.meta, args: useArgs } : { args: useArgs }
|
|
1962
|
-
);
|
|
1963
|
-
if (next === null) return prev;
|
|
1964
|
-
if (shouldWarnSocketMutationGuard() && isSameObjectReference(prev, next) && !sameRefWarnedEvents.has(nextUpdate.event)) {
|
|
1965
|
-
sameRefWarnedEvents.add(nextUpdate.event);
|
|
1966
|
-
console.warn(
|
|
1967
|
-
`[socketedRoute] applySocket("${nextUpdate.event}") returned the previous reference. Return a new object/array for updates, or return null for no change.`
|
|
1968
|
-
);
|
|
1969
|
-
}
|
|
1970
|
-
const nextRoomState = roomsFromData(
|
|
1971
|
-
next,
|
|
1972
|
-
toRooms
|
|
1973
|
-
);
|
|
1974
|
-
setRoomState(
|
|
1975
|
-
(prevRoomState) => roomStateEqual(prevRoomState, nextRoomState) ? prevRoomState : nextRoomState
|
|
1976
|
-
);
|
|
1977
|
-
return next;
|
|
1978
|
-
},
|
|
1979
|
-
...useArgs
|
|
1980
|
-
);
|
|
1981
|
-
}
|
|
1982
|
-
} finally {
|
|
1983
|
-
draining = false;
|
|
1984
|
-
if (active && queue.length > 0) drainQueue();
|
|
1985
|
-
}
|
|
1986
|
-
};
|
|
1987
|
-
const entries = Object.entries(applySocket).filter(
|
|
1988
|
-
([_event, fn]) => typeof fn === "function"
|
|
1989
|
-
);
|
|
1990
|
-
const unsubscribes = entries.map(
|
|
1991
|
-
([ev, fn]) => client.on(ev, (payload, meta) => {
|
|
1992
|
-
queue.push({
|
|
1993
|
-
event: ev,
|
|
1994
|
-
fn,
|
|
1995
|
-
payload,
|
|
1996
|
-
...meta ? { meta } : {}
|
|
1997
|
-
});
|
|
1998
|
-
drainQueue();
|
|
1999
|
-
})
|
|
2000
|
-
);
|
|
2001
|
-
return () => {
|
|
2002
|
-
active = false;
|
|
2003
|
-
queue.length = 0;
|
|
2004
|
-
unsubscribes.forEach((u) => u?.());
|
|
2005
1877
|
};
|
|
2006
|
-
}, [client, applySocket, built, argsKey, toRooms, debug]);
|
|
2007
|
-
return { ...endpointResult, rooms: roomState.rooms };
|
|
2008
|
-
};
|
|
2009
|
-
return {
|
|
2010
|
-
...rest,
|
|
2011
|
-
useEndpoint
|
|
2012
|
-
};
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
// src/sockets/socket.client.index.ts
|
|
2016
|
-
var SocketClient = class {
|
|
2017
|
-
constructor(events, opts) {
|
|
2018
|
-
this.hbTimer = null;
|
|
2019
|
-
// stats
|
|
2020
|
-
this.roomCounts = /* @__PURE__ */ new Map();
|
|
2021
|
-
this.handlerMap = /* @__PURE__ */ new Map();
|
|
2022
|
-
this.events = events;
|
|
2023
|
-
this.socket = opts.socket ?? null;
|
|
2024
|
-
this.environment = opts.environment ?? "development";
|
|
2025
|
-
this.debug = opts.debug ?? {};
|
|
2026
|
-
this.config = opts.config;
|
|
2027
|
-
this.sysEvents = opts.sys;
|
|
2028
|
-
this.roomJoinSchema = buildRoomPayloadSchema(this.config.joinMetaMessage);
|
|
2029
|
-
this.roomLeaveSchema = buildRoomPayloadSchema(this.config.leaveMetaMessage);
|
|
2030
|
-
const hb = opts.heartbeat ?? {};
|
|
2031
|
-
this.hb = {
|
|
2032
|
-
intervalMs: hb.intervalMs ?? 15e3,
|
|
2033
|
-
timeoutMs: hb.timeoutMs ?? 7500
|
|
2034
|
-
};
|
|
2035
|
-
if (!this.socket) {
|
|
2036
|
-
this.dbg({
|
|
2037
|
-
type: "lifecycle",
|
|
2038
|
-
phase: "init",
|
|
2039
|
-
err: "Socket reference is null during initialization"
|
|
2040
|
-
});
|
|
2041
|
-
} else {
|
|
2042
|
-
this.dbg({
|
|
2043
|
-
type: "lifecycle",
|
|
2044
|
-
phase: "init",
|
|
2045
|
-
heartbeatIntervalMs: this.hb.intervalMs,
|
|
2046
|
-
heartbeatTimeoutMs: this.hb.timeoutMs,
|
|
2047
|
-
environment: this.environment
|
|
2048
|
-
});
|
|
2049
|
-
this.logSocketConfigSnapshot("constructor");
|
|
2050
|
-
}
|
|
2051
|
-
this.onConnect = async () => {
|
|
2052
|
-
if (!this.socket) {
|
|
2053
|
-
this.dbg({
|
|
2054
|
-
type: "connection",
|
|
2055
|
-
phase: "connect_event",
|
|
2056
|
-
err: "Socket is null"
|
|
2057
|
-
});
|
|
2058
|
-
throw new Error("Socket is null in onConnect handler");
|
|
2059
|
-
}
|
|
2060
1878
|
this.dbg({
|
|
2061
|
-
type: "
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
await this.getSysEvent("sys:connect")({
|
|
2070
|
-
socket: this.socket,
|
|
2071
|
-
client: this
|
|
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
|
|
2072
1887
|
});
|
|
1888
|
+
handler(data, meta);
|
|
2073
1889
|
};
|
|
2074
|
-
|
|
2075
|
-
if (
|
|
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 {
|
|
2076
1894
|
this.dbg({
|
|
2077
|
-
type: "
|
|
2078
|
-
|
|
2079
|
-
|
|
1895
|
+
type: "receive",
|
|
1896
|
+
event,
|
|
1897
|
+
envelope: void 0
|
|
2080
1898
|
});
|
|
2081
|
-
|
|
1899
|
+
handler(envelopeOrRaw, void 0);
|
|
2082
1900
|
}
|
|
2083
|
-
this.dbg({
|
|
2084
|
-
type: "connection",
|
|
2085
|
-
phase: "reconnect_event",
|
|
2086
|
-
attempt,
|
|
2087
|
-
id: this.socket.id,
|
|
2088
|
-
details: {
|
|
2089
|
-
nsp: this.getNamespace(this.socket)
|
|
2090
|
-
}
|
|
2091
|
-
});
|
|
2092
|
-
this.logSocketConfigSnapshot("reconnect_event");
|
|
2093
|
-
await this.getSysEvent("sys:reconnect")({
|
|
2094
|
-
attempt,
|
|
2095
|
-
socket: this.socket,
|
|
2096
|
-
client: this
|
|
2097
|
-
});
|
|
2098
1901
|
};
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
this.dbg({
|
|
2102
|
-
type: "connection",
|
|
2103
|
-
phase: "disconnect_event",
|
|
2104
|
-
err: "Socket is null"
|
|
2105
|
-
});
|
|
2106
|
-
throw new Error("Socket is null in onDisconnect handler");
|
|
2107
|
-
}
|
|
2108
|
-
this.dbg({
|
|
2109
|
-
type: "connection",
|
|
2110
|
-
phase: "disconnect_event",
|
|
2111
|
-
reason: String(reason),
|
|
2112
|
-
details: {
|
|
2113
|
-
roomsTracked: this.roomCounts.size
|
|
2114
|
-
}
|
|
2115
|
-
});
|
|
2116
|
-
this.logSocketConfigSnapshot("disconnect_event");
|
|
2117
|
-
await this.getSysEvent("sys:disconnect")({
|
|
2118
|
-
reason: String(reason),
|
|
2119
|
-
socket: this.socket,
|
|
2120
|
-
client: this
|
|
2121
|
-
});
|
|
1902
|
+
const errorWrapped = (e) => {
|
|
1903
|
+
this.dbg({ type: "receive", event, err: String(e) });
|
|
2122
1904
|
};
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
phase: "connect_error_event",
|
|
2135
|
-
err: String(err),
|
|
2136
|
-
details: this.getVerboseDetails({ rawError: err })
|
|
2137
|
-
});
|
|
2138
|
-
this.logSocketConfigSnapshot("connect_error_event");
|
|
2139
|
-
await this.getSysEvent("sys:connect_error")({
|
|
2140
|
-
error: String(err),
|
|
2141
|
-
socket: this.socket,
|
|
2142
|
-
client: this
|
|
2143
|
-
});
|
|
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
|
|
2144
1916
|
};
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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));
|
|
2153
1925
|
}
|
|
2154
|
-
|
|
2155
|
-
if (!parsed.success) {
|
|
2156
|
-
this.dbg({
|
|
2157
|
-
type: "heartbeat",
|
|
2158
|
-
phase: "validation_failed",
|
|
2159
|
-
err: `pong payload validation failed: ${parsed.error.message}`,
|
|
2160
|
-
details: this.getValidationDetails(parsed.error)
|
|
2161
|
-
});
|
|
2162
|
-
return;
|
|
2163
|
-
}
|
|
2164
|
-
const validated = parsed.data;
|
|
2165
|
-
this.dbg({
|
|
2166
|
-
type: "heartbeat",
|
|
2167
|
-
phase: "pong_recv",
|
|
2168
|
-
payload: validated
|
|
2169
|
-
});
|
|
2170
|
-
await this.getSysEvent("sys:pong")({
|
|
2171
|
-
socket: this.socket,
|
|
2172
|
-
payload: validated,
|
|
2173
|
-
client: this
|
|
2174
|
-
});
|
|
2175
|
-
};
|
|
2176
|
-
if (this.socket) {
|
|
2177
|
-
this.socket.on("connect", this.onConnect);
|
|
2178
|
-
this.socket.on("reconnect", this.onReconnect);
|
|
2179
|
-
this.socket.on("disconnect", this.onDisconnect);
|
|
2180
|
-
this.socket.on("connect_error", this.onConnectError);
|
|
2181
|
-
this.socket.on("sys:pong", this.onPong);
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
snapshotSocketConfig(socket) {
|
|
2185
|
-
if (!socket) return null;
|
|
2186
|
-
const manager = socket.io ?? null;
|
|
2187
|
-
const base = {
|
|
2188
|
-
nsp: this.getNamespace(socket)
|
|
2189
|
-
};
|
|
2190
|
-
if (!manager) return base;
|
|
2191
|
-
const opts = manager.opts ?? {};
|
|
2192
|
-
const transports = Array.isArray(opts.transports) ? opts.transports.filter((t) => typeof t === "string") : void 0;
|
|
2193
|
-
const transportName = typeof manager.engine?.transport?.name === "string" ? manager.engine.transport?.name : void 0;
|
|
2194
|
-
const readyState = typeof manager._readyState === "string" ? manager._readyState : void 0;
|
|
2195
|
-
const uri = typeof manager.uri === "string" ? manager.uri : void 0;
|
|
2196
|
-
return {
|
|
2197
|
-
...base,
|
|
2198
|
-
url: uri,
|
|
2199
|
-
path: typeof opts.path === "string" ? opts.path : void 0,
|
|
2200
|
-
transport: transportName ?? transports?.[0],
|
|
2201
|
-
transports,
|
|
2202
|
-
readyState,
|
|
2203
|
-
autoConnect: typeof opts.autoConnect === "boolean" ? opts.autoConnect : void 0,
|
|
2204
|
-
reconnection: typeof opts.reconnection === "boolean" ? opts.reconnection : void 0,
|
|
2205
|
-
reconnectionAttempts: typeof opts.reconnectionAttempts === "number" ? opts.reconnectionAttempts : void 0,
|
|
2206
|
-
reconnectionDelay: typeof opts.reconnectionDelay === "number" ? opts.reconnectionDelay : void 0,
|
|
2207
|
-
reconnectionDelayMax: typeof opts.reconnectionDelayMax === "number" ? opts.reconnectionDelayMax : void 0,
|
|
2208
|
-
timeout: typeof opts.timeout === "number" ? opts.timeout : void 0,
|
|
2209
|
-
hostname: typeof opts.hostname === "string" ? opts.hostname : void 0,
|
|
2210
|
-
port: typeof opts.port === "number" || typeof opts.port === "string" ? opts.port : void 0,
|
|
2211
|
-
secure: typeof opts.secure === "boolean" ? opts.secure : void 0
|
|
1926
|
+
this.dbg({ type: "register", phase: "unregister", event });
|
|
2212
1927
|
};
|
|
2213
1928
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
1929
|
+
/**
|
|
1930
|
+
* Remove all listeners, stop timers, and leave rooms.
|
|
1931
|
+
* Call when disposing the client instance.
|
|
1932
|
+
*/
|
|
1933
|
+
async destroy(leaveMeta) {
|
|
1934
|
+
const socket = this.socket;
|
|
2217
1935
|
this.dbg({
|
|
2218
|
-
type: "
|
|
2219
|
-
phase:
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
1936
|
+
type: "lifecycle",
|
|
1937
|
+
phase: "destroy_begin",
|
|
1938
|
+
socketId: socket?.id,
|
|
1939
|
+
details: {
|
|
1940
|
+
roomsTracked: this.roomCounts.size,
|
|
1941
|
+
handlerEvents: this.handlerMap.size
|
|
2223
1942
|
}
|
|
2224
1943
|
});
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
}
|
|
2239
|
-
getSysEvent(name) {
|
|
2240
|
-
return this.sysEvents[name];
|
|
2241
|
-
}
|
|
2242
|
-
dbg(e) {
|
|
2243
|
-
const d = this.debug;
|
|
2244
|
-
if (!d.logger) return;
|
|
2245
|
-
if (!d[e.type]) return;
|
|
2246
|
-
if (d.only && "event" in e && !d.only.includes(e.event)) return;
|
|
2247
|
-
d.logger(e);
|
|
2248
|
-
}
|
|
2249
|
-
/** internal stats snapshot */
|
|
2250
|
-
stats() {
|
|
2251
|
-
const rooms = Array.from(this.roomCounts.entries()).map(
|
|
2252
|
-
([room, count]) => ({ room, count })
|
|
2253
|
-
);
|
|
2254
|
-
const handlers = Array.from(this.handlerMap.entries()).map(
|
|
2255
|
-
([event, set]) => ({
|
|
2256
|
-
event,
|
|
2257
|
-
handlers: set.size
|
|
2258
|
-
})
|
|
2259
|
-
);
|
|
2260
|
-
return {
|
|
2261
|
-
roomsCount: rooms.length,
|
|
2262
|
-
totalHandlers: handlers.reduce((a, b) => a + b.handlers, 0),
|
|
2263
|
-
rooms,
|
|
2264
|
-
handlers
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
toArray(rooms) {
|
|
2268
|
-
return rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
|
|
2269
|
-
}
|
|
2270
|
-
rollbackJoinIncrement(rooms) {
|
|
2271
|
-
for (const room of rooms) {
|
|
2272
|
-
const curr = this.roomCounts.get(room) ?? 0;
|
|
2273
|
-
const next = Math.max(0, curr - 1);
|
|
2274
|
-
if (next === 0) this.roomCounts.delete(room);
|
|
2275
|
-
else this.roomCounts.set(room, next);
|
|
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);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
2276
1957
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
this.roomCounts.set(room, curr + 1);
|
|
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);
|
|
2282
1962
|
}
|
|
1963
|
+
this.roomCounts.clear();
|
|
1964
|
+
this.dbg({
|
|
1965
|
+
type: "lifecycle",
|
|
1966
|
+
phase: "destroy_complete",
|
|
1967
|
+
socketId: socket?.id,
|
|
1968
|
+
details: {
|
|
1969
|
+
roomsTracked: this.roomCounts.size,
|
|
1970
|
+
handlerEvents: this.handlerMap.size
|
|
1971
|
+
}
|
|
1972
|
+
});
|
|
2283
1973
|
}
|
|
2284
|
-
/**
|
|
2285
|
-
|
|
2286
|
-
*
|
|
2287
|
-
* This is called by the default 'sys:connect' handler, but you can also
|
|
2288
|
-
* call it yourself from any sysHandler to change when heartbeats start.
|
|
2289
|
-
*/
|
|
2290
|
-
startHeartbeat() {
|
|
2291
|
-
this.stopHeartbeat("stop_before_start");
|
|
1974
|
+
/** Pass-throughs. Managing connection is the caller’s responsibility. */
|
|
1975
|
+
disconnect() {
|
|
2292
1976
|
if (!this.socket) {
|
|
2293
1977
|
this.dbg({
|
|
2294
|
-
type: "
|
|
2295
|
-
phase: "
|
|
1978
|
+
type: "connection",
|
|
1979
|
+
phase: "disconnect",
|
|
1980
|
+
reason: "client_disconnect",
|
|
2296
1981
|
err: "Socket is null"
|
|
2297
1982
|
});
|
|
2298
1983
|
return;
|
|
2299
1984
|
}
|
|
2300
|
-
|
|
1985
|
+
this.stopHeartbeat("disconnect");
|
|
1986
|
+
this.socket.disconnect();
|
|
2301
1987
|
this.dbg({
|
|
2302
|
-
type: "
|
|
2303
|
-
phase: "
|
|
1988
|
+
type: "connection",
|
|
1989
|
+
phase: "disconnect",
|
|
1990
|
+
reason: "client_disconnect",
|
|
2304
1991
|
details: {
|
|
2305
|
-
|
|
2306
|
-
timeoutMs: this.hb.timeoutMs
|
|
1992
|
+
nsp: this.getNamespace(this.socket)
|
|
2307
1993
|
}
|
|
2308
1994
|
});
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
phase: "tick_skip",
|
|
2314
|
-
err: "Socket missing during heartbeat tick"
|
|
2315
|
-
});
|
|
2316
|
-
return;
|
|
2317
|
-
}
|
|
2318
|
-
const payload = await this.getSysEvent("sys:ping")({
|
|
2319
|
-
socket,
|
|
2320
|
-
client: this
|
|
2321
|
-
});
|
|
2322
|
-
const check = this.config.pingPayload.safeParse(payload);
|
|
2323
|
-
if (!check.success) {
|
|
2324
|
-
this.dbg({
|
|
2325
|
-
type: "heartbeat",
|
|
2326
|
-
phase: "validation_failed",
|
|
2327
|
-
err: "ping payload validation failed",
|
|
2328
|
-
details: this.getValidationDetails(check.error)
|
|
2329
|
-
});
|
|
2330
|
-
if (this.environment === "development") {
|
|
2331
|
-
console.warn(
|
|
2332
|
-
"[socket] ping schema validation failed",
|
|
2333
|
-
check.error.issues
|
|
2334
|
-
);
|
|
2335
|
-
}
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
const dataToSend = check.data;
|
|
2339
|
-
socket.emit("sys:ping", dataToSend);
|
|
1995
|
+
this.logSocketConfigSnapshot("disconnect_call");
|
|
1996
|
+
}
|
|
1997
|
+
connect() {
|
|
1998
|
+
if (!this.socket) {
|
|
2340
1999
|
this.dbg({
|
|
2341
|
-
type: "
|
|
2342
|
-
phase: "
|
|
2343
|
-
|
|
2000
|
+
type: "connection",
|
|
2001
|
+
phase: "connect",
|
|
2002
|
+
err: "Socket is null"
|
|
2344
2003
|
});
|
|
2345
|
-
|
|
2346
|
-
this.hbTimer = setInterval(tick, this.hb.intervalMs);
|
|
2347
|
-
tick();
|
|
2348
|
-
}
|
|
2349
|
-
/**
|
|
2350
|
-
* Public: stop the heartbeat loop.
|
|
2351
|
-
*
|
|
2352
|
-
* This is called by the default 'sys:disconnect' handler, but you can also
|
|
2353
|
-
* call it yourself from any sysHandler to fully control heartbeat lifecycle.
|
|
2354
|
-
*/
|
|
2355
|
-
stopHeartbeat(reason) {
|
|
2356
|
-
const hadTimer = Boolean(this.hbTimer);
|
|
2357
|
-
if (this.hbTimer) {
|
|
2358
|
-
clearInterval(this.hbTimer);
|
|
2359
|
-
this.hbTimer = null;
|
|
2004
|
+
return;
|
|
2360
2005
|
}
|
|
2006
|
+
this.socket.connect();
|
|
2361
2007
|
this.dbg({
|
|
2362
|
-
type: "
|
|
2363
|
-
phase: "
|
|
2364
|
-
|
|
2365
|
-
|
|
2008
|
+
type: "connection",
|
|
2009
|
+
phase: "connect",
|
|
2010
|
+
id: this.socket.id ?? "",
|
|
2011
|
+
details: {
|
|
2012
|
+
nsp: this.getNamespace(this.socket)
|
|
2013
|
+
}
|
|
2366
2014
|
});
|
|
2015
|
+
this.logSocketConfigSnapshot("connect_call");
|
|
2367
2016
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
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);
|
|
2389
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;
|
|
2390
2088
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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)
|
|
2398
2139
|
});
|
|
2399
|
-
throw new Error("Socket is null in joinRooms method");
|
|
2400
2140
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
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
|
+
});
|
|
2412
2232
|
});
|
|
2413
|
-
return async () => {
|
|
2414
|
-
};
|
|
2415
2233
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
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)
|
|
2422
2246
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2247
|
+
});
|
|
2248
|
+
const client = React3.useMemo(() => {
|
|
2249
|
+
if (!socket) {
|
|
2250
|
+
dbg(providerDebugRef.current, {
|
|
2251
|
+
type: "client",
|
|
2252
|
+
phase: "init",
|
|
2253
|
+
missing: true
|
|
2427
2254
|
});
|
|
2428
|
-
|
|
2429
|
-
this.rollbackJoinIncrement(toJoin);
|
|
2430
|
-
this.dbg({
|
|
2431
|
-
type: "room",
|
|
2432
|
-
phase: "join",
|
|
2433
|
-
rooms: toJoin,
|
|
2434
|
-
err: "payload validation failed",
|
|
2435
|
-
details: this.getValidationDetails(payloadResult.error)
|
|
2436
|
-
});
|
|
2437
|
-
return async () => {
|
|
2438
|
-
};
|
|
2439
|
-
}
|
|
2440
|
-
const payload = payloadResult.data;
|
|
2441
|
-
const normalizedRooms = this.toArray(payload.rooms);
|
|
2442
|
-
this.socket.emit("sys:room_join", payload);
|
|
2443
|
-
this.dbg({ type: "room", phase: "join", rooms: normalizedRooms });
|
|
2255
|
+
return null;
|
|
2444
2256
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
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
|
+
}
|
|
2447
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);
|
|
2448
2308
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
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)
|
|
2456
2356
|
});
|
|
2457
|
-
throw new Error("Socket is null in leaveRooms method");
|
|
2458
2357
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
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));
|
|
2471
2423
|
return;
|
|
2472
2424
|
}
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
const next = Math.max(0, curr - 1);
|
|
2478
|
-
if (next === 0 && curr > 0) toLeave.push(r);
|
|
2479
|
-
if (next === 0) this.roomCounts.delete(r);
|
|
2480
|
-
else this.roomCounts.set(r, next);
|
|
2481
|
-
}
|
|
2482
|
-
if (toLeave.length > 0 && this.socket) {
|
|
2483
|
-
const payloadResult = this.roomLeaveSchema.safeParse({
|
|
2484
|
-
rooms: toLeave,
|
|
2485
|
-
meta
|
|
2486
|
-
});
|
|
2487
|
-
if (!payloadResult.success) {
|
|
2488
|
-
this.rollbackLeaveDecrement(toLeave);
|
|
2489
|
-
this.dbg({
|
|
2490
|
-
type: "room",
|
|
2491
|
-
phase: "leave",
|
|
2492
|
-
rooms: toLeave,
|
|
2493
|
-
err: "payload validation failed",
|
|
2494
|
-
details: this.getValidationDetails(payloadResult.error)
|
|
2495
|
-
});
|
|
2425
|
+
if (input && typeof input === "object") {
|
|
2426
|
+
const maybeItems = input.items;
|
|
2427
|
+
if (Array.isArray(maybeItems)) {
|
|
2428
|
+
maybeItems.forEach((entry) => mergeForValue(entry));
|
|
2496
2429
|
return;
|
|
2497
2430
|
}
|
|
2498
|
-
const payload = payloadResult.data;
|
|
2499
|
-
const normalizedRooms = this.toArray(payload.rooms);
|
|
2500
|
-
this.socket.emit("sys:room_leave", payload);
|
|
2501
|
-
this.dbg({ type: "room", phase: "leave", rooms: normalizedRooms });
|
|
2502
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;
|
|
2503
2438
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
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;
|
|
2528
2516
|
});
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
metadata: envelope?.metadata,
|
|
2541
|
-
rooms: toStringList(envelope?.rooms)
|
|
2542
|
-
},
|
|
2543
|
-
ctx: {
|
|
2544
|
-
receivedAt,
|
|
2545
|
-
latencyMs: sentAt ? Math.max(0, receivedAt.getTime() - sentAt.getTime()) : void 0,
|
|
2546
|
-
nsp: this.getNamespace(socket),
|
|
2547
|
-
socketId: socket.id,
|
|
2548
|
-
socket
|
|
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)
|
|
2549
2528
|
}
|
|
2550
|
-
};
|
|
2551
|
-
this.dbg({
|
|
2552
|
-
type: "receive",
|
|
2553
|
-
event,
|
|
2554
|
-
envelope: this.debug.verbose ? {
|
|
2555
|
-
eventName: meta.envelope.eventName,
|
|
2556
|
-
sentAt: meta.envelope.sentAt,
|
|
2557
|
-
sentTo: meta.envelope.sentTo,
|
|
2558
|
-
metadata: meta.envelope.metadata
|
|
2559
|
-
} : void 0
|
|
2560
2529
|
});
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
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"
|
|
2571
2555
|
});
|
|
2572
|
-
|
|
2573
|
-
}
|
|
2574
|
-
};
|
|
2575
|
-
const errorWrapped = (e) => {
|
|
2576
|
-
this.dbg({ type: "receive", event, err: String(e) });
|
|
2577
|
-
};
|
|
2578
|
-
socket.on(String(event), wrappedDispatcher);
|
|
2579
|
-
socket.on(`${String(event)}:error`, errorWrapped);
|
|
2580
|
-
let set = this.handlerMap.get(String(event));
|
|
2581
|
-
if (!set) {
|
|
2582
|
-
set = /* @__PURE__ */ new Set();
|
|
2583
|
-
this.handlerMap.set(String(event), set);
|
|
2584
|
-
}
|
|
2585
|
-
const entry = {
|
|
2586
|
-
orig: handler,
|
|
2587
|
-
wrapped: wrappedDispatcher,
|
|
2588
|
-
errorWrapped
|
|
2589
|
-
};
|
|
2590
|
-
set.add(entry);
|
|
2591
|
-
return () => {
|
|
2592
|
-
socket.off(String(event), wrappedDispatcher);
|
|
2593
|
-
socket.off(`${String(event)}:error`, errorWrapped);
|
|
2594
|
-
const s = this.handlerMap.get(String(event));
|
|
2595
|
-
if (s) {
|
|
2596
|
-
s.delete(entry);
|
|
2597
|
-
if (s.size === 0) this.handlerMap.delete(String(event));
|
|
2598
|
-
}
|
|
2599
|
-
this.dbg({ type: "register", phase: "unregister", event });
|
|
2600
|
-
};
|
|
2601
|
-
}
|
|
2602
|
-
/**
|
|
2603
|
-
* Remove all listeners, stop timers, and leave rooms.
|
|
2604
|
-
* Call when disposing the client instance.
|
|
2605
|
-
*/
|
|
2606
|
-
async destroy(leaveMeta) {
|
|
2607
|
-
const socket = this.socket;
|
|
2608
|
-
this.dbg({
|
|
2609
|
-
type: "lifecycle",
|
|
2610
|
-
phase: "destroy_begin",
|
|
2611
|
-
socketId: socket?.id,
|
|
2612
|
-
details: {
|
|
2613
|
-
roomsTracked: this.roomCounts.size,
|
|
2614
|
-
handlerEvents: this.handlerMap.size
|
|
2556
|
+
return;
|
|
2615
2557
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
for (const [event, set] of this.handlerMap.entries()) {
|
|
2625
|
-
for (const entry of set) {
|
|
2626
|
-
socket.off(String(event), entry.wrapped);
|
|
2627
|
-
socket.off(`${String(event)}:error`, entry.errorWrapped);
|
|
2628
|
-
}
|
|
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;
|
|
2629
2566
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
phase: "destroy_complete",
|
|
2640
|
-
socketId: socket?.id,
|
|
2641
|
-
details: {
|
|
2642
|
-
roomsTracked: this.roomCounts.size,
|
|
2643
|
-
handlerEvents: this.handlerMap.size
|
|
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;
|
|
2644
2576
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
this.dbg({
|
|
2651
|
-
type: "connection",
|
|
2652
|
-
phase: "disconnect",
|
|
2653
|
-
reason: "client_disconnect",
|
|
2654
|
-
err: "Socket is null"
|
|
2577
|
+
let active = true;
|
|
2578
|
+
dbg2(debug, {
|
|
2579
|
+
type: "room",
|
|
2580
|
+
phase: "join_attempt",
|
|
2581
|
+
rooms: roomState.rooms
|
|
2655
2582
|
});
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
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
|
+
}
|
|
2676
2634
|
});
|
|
2677
|
-
return;
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
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
|
+
}
|
|
2691
2705
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2692
2706
|
0 && (module.exports = {
|
|
2693
2707
|
HttpError,
|