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