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