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