@emeryld/rrroutes-client 2.0.11 → 2.0.13

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/dist/index.cjs CHANGED
@@ -473,6 +473,7 @@ var buildRoomPayloadSchema = (metaSchema) => import_zod.z.object({
473
473
 
474
474
  // src/sockets/socket.client.context.tsx
475
475
  var React = __toESM(require("react"), 1);
476
+ var import_socket = require("socket.io-client");
476
477
  var import_jsx_runtime = require("react/jsx-runtime");
477
478
  var SocketCtx = React.createContext(null);
478
479
  function dbg(dbgOpts, e) {
@@ -480,6 +481,43 @@ function dbg(dbgOpts, e) {
480
481
  if (!dbgOpts[e.type]) return;
481
482
  dbgOpts.logger(e);
482
483
  }
484
+ function safeDescribeHookValue(value) {
485
+ if (value == null) return value;
486
+ const valueType = typeof value;
487
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
488
+ if (valueType === "bigint" || valueType === "symbol") return String(value);
489
+ if (valueType === "function") return `[function ${value.name || "anonymous"}]`;
490
+ if (Array.isArray(value)) return `[array length=${value.length}]`;
491
+ if (value instanceof import_socket.Socket) {
492
+ const socket = value;
493
+ return `[Socket id=${socket.id ?? "unknown"} connected=${socket.connected ?? false}]`;
494
+ }
495
+ const ctorName = value.constructor?.name ?? "object";
496
+ const keys = Object.keys(value);
497
+ const keyPreview = keys.slice(0, 4).join(",");
498
+ const suffix = keys.length > 4 ? ",\u2026" : "";
499
+ return `[${ctorName} keys=${keyPreview}${suffix}]`;
500
+ }
501
+ function createHookDebugEvent(prev, next, hook) {
502
+ const reason = prev ? "change" : "init";
503
+ const changed = Object.keys(next).reduce((acc, dependency) => {
504
+ const prevValue = prev ? prev[dependency] : void 0;
505
+ const nextValue = next[dependency];
506
+ if (!prev || !Object.is(prevValue, nextValue)) {
507
+ acc.push({ dependency, previous: safeDescribeHookValue(prevValue), next: safeDescribeHookValue(nextValue) });
508
+ }
509
+ return acc;
510
+ }, []);
511
+ if (!changed.length) return null;
512
+ return { type: "hook", hook, reason, changes: changed };
513
+ }
514
+ function trackHookTrigger({ ref, hook, providerDebug, snapshot }) {
515
+ const prev = ref.current;
516
+ ref.current = snapshot;
517
+ if (!providerDebug?.logger || !providerDebug?.hook) return;
518
+ const event = createHookDebugEvent(prev, snapshot, hook);
519
+ if (event) dbg(providerDebug, event);
520
+ }
483
521
  function buildSocketProvider(args) {
484
522
  const { events, options: baseOptions } = args;
485
523
  return {
@@ -499,53 +537,76 @@ function SocketProvider(props) {
499
537
  const { events, baseOptions, children, fallback, providerDebug, destroyLeaveMeta } = props;
500
538
  const [resolvedSocket, setResolvedSocket] = React.useState(null);
501
539
  const socket = "socket" in props ? props.socket ?? null : resolvedSocket;
540
+ const providerDebugRef = React.useRef();
541
+ providerDebugRef.current = providerDebug;
542
+ const resolveEffectDebugRef = React.useRef(null);
543
+ const clientMemoDebugRef = React.useRef(null);
544
+ const destroyEffectDebugRef = React.useRef(null);
502
545
  React.useEffect(() => {
546
+ trackHookTrigger({
547
+ ref: resolveEffectDebugRef,
548
+ hook: "resolve_effect",
549
+ providerDebug: providerDebugRef.current,
550
+ snapshot: { resolvedSocket }
551
+ });
503
552
  if (!("getSocket" in props)) return;
504
553
  let cancelled = false;
505
- dbg(providerDebug, { type: "resolve", phase: "start" });
554
+ dbg(providerDebugRef.current, { type: "resolve", phase: "start" });
506
555
  if (!resolvedSocket) {
507
556
  Promise.resolve(props.getSocket()).then((s) => {
508
557
  if (cancelled) {
509
- dbg(providerDebug, { type: "resolve", phase: "cancelled" });
558
+ dbg(providerDebugRef.current, { type: "resolve", phase: "cancelled" });
510
559
  return;
511
560
  }
512
561
  if (!s) {
513
- dbg(providerDebug, { type: "resolve", phase: "missing" });
562
+ dbg(providerDebugRef.current, { type: "resolve", phase: "missing" });
514
563
  return;
515
564
  }
516
565
  setResolvedSocket(s);
517
- dbg(providerDebug, { type: "resolve", phase: "ok" });
566
+ dbg(providerDebugRef.current, { type: "resolve", phase: "ok" });
518
567
  }).catch((err) => {
519
568
  if (cancelled) return;
520
- dbg(providerDebug, { type: "resolve", phase: "error", err: String(err) });
569
+ dbg(providerDebugRef.current, { type: "resolve", phase: "error", err: String(err) });
521
570
  });
522
571
  }
523
572
  return () => {
524
573
  cancelled = true;
525
574
  };
526
575
  }, [resolvedSocket]);
576
+ trackHookTrigger({
577
+ ref: clientMemoDebugRef,
578
+ hook: "client_memo",
579
+ providerDebug: providerDebugRef.current,
580
+ snapshot: { events, baseOptions, socket }
581
+ });
527
582
  const client = React.useMemo(() => {
528
583
  if (!socket) {
529
- dbg(providerDebug, { type: "client", phase: "missing" });
584
+ dbg(providerDebugRef.current, { type: "client", phase: "missing" });
530
585
  return null;
531
586
  }
532
587
  const c = new SocketClient(events, { ...baseOptions, socket });
533
- dbg(providerDebug, { type: "client", phase: "ready" });
588
+ dbg(providerDebugRef.current, { type: "client", phase: "ready" });
534
589
  return c;
535
- }, [events, baseOptions, socket, providerDebug]);
590
+ }, [events, baseOptions, socket]);
536
591
  React.useEffect(() => {
592
+ trackHookTrigger({
593
+ ref: destroyEffectDebugRef,
594
+ hook: "destroy_effect",
595
+ providerDebug: providerDebugRef.current,
596
+ snapshot: { client, destroyLeaveMeta }
597
+ });
537
598
  return () => {
538
599
  if (client) {
539
600
  client.destroy(destroyLeaveMeta);
540
- dbg(providerDebug, { type: "client", phase: "destroy" });
601
+ dbg(providerDebugRef.current, { type: "client", phase: "destroy" });
541
602
  }
542
603
  };
543
- }, [client, providerDebug]);
604
+ }, [client, destroyLeaveMeta]);
544
605
  if (!client) {
545
- dbg(providerDebug, { type: "render", phase: "waiting_for_socket" });
606
+ dbg(providerDebugRef.current, { type: "render", phase: "waiting_for_socket" });
546
607
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback ?? children });
547
608
  }
548
- dbg(providerDebug, { type: "render", phase: "provide" });
609
+ dbg(providerDebugRef.current, { type: "render", phase: "provide" });
549
610
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SocketCtx.Provider, { value: client, children });
550
611
  }
551
612
  function useSocketClient() {
@@ -591,6 +652,32 @@ var SocketClient = class {
591
652
  intervalMs: hb.intervalMs ?? 15e3,
592
653
  timeoutMs: hb.timeoutMs ?? 7500
593
654
  };
655
+ this.dbg({
656
+ type: "lifecycle",
657
+ phase: "init_start",
658
+ socketId: this.socket?.id ?? void 0,
659
+ details: {
660
+ environment: this.environment
661
+ }
662
+ });
663
+ if (!this.socket) {
664
+ this.dbg({
665
+ type: "lifecycle",
666
+ phase: "init_socket_missing",
667
+ err: "Socket reference is null during initialization"
668
+ });
669
+ } else {
670
+ this.dbg({
671
+ type: "lifecycle",
672
+ phase: "init_ready",
673
+ socketId: this.socket.id,
674
+ details: {
675
+ heartbeatIntervalMs: this.hb.intervalMs,
676
+ heartbeatTimeoutMs: this.hb.timeoutMs
677
+ }
678
+ });
679
+ this.logSocketConfigSnapshot("init", "constructor");
680
+ }
594
681
  this.onConnect = () => {
595
682
  if (!this.socket) {
596
683
  this.dbg({ type: "connection", phase: "connect", err: "Socket is null" });
@@ -599,8 +686,12 @@ var SocketClient = class {
599
686
  this.dbg({
600
687
  type: "connection",
601
688
  phase: "connect",
602
- id: this.socket.id
689
+ id: this.socket.id,
690
+ details: {
691
+ nsp: this.getNamespace(this.socket)
692
+ }
603
693
  });
694
+ this.logSocketConfigSnapshot("update", "connect_event");
604
695
  this.getSysEvent("sys:connect")({
605
696
  socket: this.socket,
606
697
  client: this
@@ -615,8 +706,12 @@ var SocketClient = class {
615
706
  type: "connection",
616
707
  phase: "reconnect",
617
708
  attempt,
618
- id: this.socket.id
709
+ id: this.socket.id,
710
+ details: {
711
+ nsp: this.getNamespace(this.socket)
712
+ }
619
713
  });
714
+ this.logSocketConfigSnapshot("update", "reconnect_event");
620
715
  this.getSysEvent("sys:reconnect")({
621
716
  attempt,
622
717
  socket: this.socket,
@@ -631,8 +726,12 @@ var SocketClient = class {
631
726
  this.dbg({
632
727
  type: "connection",
633
728
  phase: "disconnect",
634
- reason: String(reason)
729
+ reason: String(reason),
730
+ details: {
731
+ roomsTracked: this.roomCounts.size
732
+ }
635
733
  });
734
+ this.logSocketConfigSnapshot("update", "disconnect_event");
636
735
  this.getSysEvent("sys:disconnect")({
637
736
  reason: String(reason),
638
737
  socket: this.socket,
@@ -647,8 +746,10 @@ var SocketClient = class {
647
746
  this.dbg({
648
747
  type: "connection",
649
748
  phase: "connect_error",
650
- err: String(err)
749
+ err: String(err),
750
+ details: this.getVerboseDetails({ rawError: err })
651
751
  });
752
+ this.logSocketConfigSnapshot("update", "connect_error_event");
652
753
  this.getSysEvent("sys:connect_error")({
653
754
  error: String(err),
654
755
  socket: this.socket,
@@ -664,8 +765,9 @@ var SocketClient = class {
664
765
  if (!parsed.success) {
665
766
  this.dbg({
666
767
  type: "heartbeat",
667
- action: "pong_recv",
668
- err: `pong payload validation failed: ${parsed.error.message}`
768
+ action: "validation_failed",
769
+ err: `pong payload validation failed: ${parsed.error.message}`,
770
+ details: this.getValidationDetails(parsed.error)
669
771
  });
670
772
  return;
671
773
  }
@@ -689,6 +791,61 @@ var SocketClient = class {
689
791
  this.socket.on("sys:pong", this.onPong);
690
792
  }
691
793
  }
794
+ snapshotSocketConfig(socket) {
795
+ if (!socket) return null;
796
+ const manager = socket.io ?? null;
797
+ const base = {
798
+ nsp: this.getNamespace(socket)
799
+ };
800
+ if (!manager) return base;
801
+ const opts = manager.opts ?? {};
802
+ const transports = Array.isArray(opts.transports) ? opts.transports.filter((t) => typeof t === "string") : void 0;
803
+ const transportName = typeof manager.engine?.transport?.name === "string" ? manager.engine.transport?.name : void 0;
804
+ const readyState = typeof manager._readyState === "string" ? manager._readyState : void 0;
805
+ const uri = typeof manager.uri === "string" ? manager.uri : void 0;
806
+ return {
807
+ ...base,
808
+ url: uri,
809
+ path: typeof opts.path === "string" ? opts.path : void 0,
810
+ transport: transportName ?? transports?.[0],
811
+ transports,
812
+ readyState,
813
+ autoConnect: typeof opts.autoConnect === "boolean" ? opts.autoConnect : void 0,
814
+ reconnection: typeof opts.reconnection === "boolean" ? opts.reconnection : void 0,
815
+ reconnectionAttempts: typeof opts.reconnectionAttempts === "number" ? opts.reconnectionAttempts : void 0,
816
+ reconnectionDelay: typeof opts.reconnectionDelay === "number" ? opts.reconnectionDelay : void 0,
817
+ reconnectionDelayMax: typeof opts.reconnectionDelayMax === "number" ? opts.reconnectionDelayMax : void 0,
818
+ timeout: typeof opts.timeout === "number" ? opts.timeout : void 0,
819
+ hostname: typeof opts.hostname === "string" ? opts.hostname : void 0,
820
+ port: typeof opts.port === "number" || typeof opts.port === "string" ? opts.port : void 0,
821
+ secure: typeof opts.secure === "boolean" ? opts.secure : void 0
822
+ };
823
+ }
824
+ logSocketConfigSnapshot(phase, reason) {
825
+ if (!this.debug.logger || !this.debug.config) return;
826
+ const snapshot = this.snapshotSocketConfig(this.socket);
827
+ this.dbg({
828
+ type: "config",
829
+ phase,
830
+ reason,
831
+ socketId: this.socket?.id,
832
+ snapshot: snapshot ?? void 0,
833
+ err: snapshot ? void 0 : "Socket unavailable for config snapshot"
834
+ });
835
+ }
836
+ getValidationDetails(error) {
837
+ if (!this.debug.verbose) return void 0;
838
+ return { issues: error.issues };
839
+ }
840
+ getVerboseDetails(details) {
841
+ if (!this.debug.verbose) return void 0;
842
+ return details;
843
+ }
844
+ getNamespace(socket) {
845
+ if (!socket) return void 0;
846
+ const nsp = socket.nsp;
847
+ return typeof nsp === "string" ? nsp : void 0;
848
+ }
692
849
  getSysEvent(name) {
693
850
  return this.sysEvents[name];
694
851
  }
@@ -738,16 +895,44 @@ var SocketClient = class {
738
895
  */
739
896
  startHeartbeat() {
740
897
  this.stopHeartbeat();
741
- if (!this.socket) return;
898
+ if (!this.socket) {
899
+ this.dbg({
900
+ type: "heartbeat",
901
+ action: "start",
902
+ err: "Socket is null"
903
+ });
904
+ return;
905
+ }
742
906
  const socket = this.socket;
907
+ this.dbg({
908
+ type: "heartbeat",
909
+ action: "start",
910
+ details: {
911
+ intervalMs: this.hb.intervalMs,
912
+ timeoutMs: this.hb.timeoutMs
913
+ }
914
+ });
743
915
  const tick = () => {
744
- if (!socket) return;
916
+ if (!socket) {
917
+ this.dbg({
918
+ type: "heartbeat",
919
+ action: "tick_skip",
920
+ err: "Socket missing during heartbeat tick"
921
+ });
922
+ return;
923
+ }
745
924
  const payload = this.getSysEvent("sys:ping")({
746
925
  socket,
747
926
  client: this
748
927
  });
749
928
  const check = this.config.pingPayload.safeParse(payload);
750
929
  if (!check.success) {
930
+ this.dbg({
931
+ type: "heartbeat",
932
+ action: "validation_failed",
933
+ err: "ping payload validation failed",
934
+ details: this.getValidationDetails(check.error)
935
+ });
751
936
  if (this.environment === "development") {
752
937
  console.warn("[socket] ping schema validation failed", check.error.issues);
753
938
  }
@@ -771,10 +956,18 @@ var SocketClient = class {
771
956
  * call it yourself from any sysHandler to fully control heartbeat lifecycle.
772
957
  */
773
958
  stopHeartbeat() {
959
+ const hadTimer = Boolean(this.hbTimer);
774
960
  if (this.hbTimer) {
775
961
  clearInterval(this.hbTimer);
776
962
  this.hbTimer = null;
777
963
  }
964
+ this.dbg({
965
+ type: "heartbeat",
966
+ action: "stop",
967
+ details: {
968
+ hadTimer
969
+ }
970
+ });
778
971
  }
779
972
  emit(event, payload, metadata) {
780
973
  if (!this.socket) {
@@ -783,7 +976,15 @@ var SocketClient = class {
783
976
  }
784
977
  const schema = this.events[event].message;
785
978
  const parsed = schema.safeParse(payload);
786
- if (!parsed.success) throw new Error(`Invalid payload for "${event}": ${parsed.error.message}`);
979
+ if (!parsed.success) {
980
+ this.dbg({
981
+ type: "emit",
982
+ event,
983
+ err: "payload_validation_failed",
984
+ details: this.getValidationDetails(parsed.error)
985
+ });
986
+ throw new Error(`Invalid payload for "${event}": ${parsed.error.message}`);
987
+ }
787
988
  this.socket.emit(String(event), parsed.data);
788
989
  this.dbg({
789
990
  type: "emit",
@@ -819,7 +1020,13 @@ var SocketClient = class {
819
1020
  });
820
1021
  if (!payloadResult.success) {
821
1022
  this.rollbackJoinIncrement(toJoin);
822
- this.dbg({ type: "room", action: "join", rooms: toJoin, err: "payload validation failed" });
1023
+ this.dbg({
1024
+ type: "room",
1025
+ action: "join",
1026
+ rooms: toJoin,
1027
+ err: "payload validation failed",
1028
+ details: this.getValidationDetails(payloadResult.error)
1029
+ });
823
1030
  return;
824
1031
  }
825
1032
  const payload = payloadResult.data;
@@ -858,7 +1065,13 @@ var SocketClient = class {
858
1065
  });
859
1066
  if (!payloadResult.success) {
860
1067
  this.rollbackLeaveDecrement(toLeave);
861
- this.dbg({ type: "room", action: "leave", rooms: toLeave, err: "payload validation failed" });
1068
+ this.dbg({
1069
+ type: "room",
1070
+ action: "leave",
1071
+ rooms: toLeave,
1072
+ err: "payload validation failed",
1073
+ details: this.getValidationDetails(payloadResult.error)
1074
+ });
862
1075
  return;
863
1076
  }
864
1077
  const payload = payloadResult.data;
@@ -880,7 +1093,15 @@ var SocketClient = class {
880
1093
  const maybeEnvelope = envelopeOrRaw;
881
1094
  const rawData = maybeEnvelope?.data ?? maybeEnvelope;
882
1095
  const parsed = schema.safeParse(rawData);
883
- if (!parsed.success) return;
1096
+ if (!parsed.success) {
1097
+ this.dbg({
1098
+ type: "receive",
1099
+ event,
1100
+ err: "payload_validation_failed",
1101
+ details: this.getValidationDetails(parsed.error)
1102
+ });
1103
+ return;
1104
+ }
884
1105
  const receivedAt = /* @__PURE__ */ new Date();
885
1106
  const sentAt = maybeEnvelope?.sentAt ? new Date(maybeEnvelope.sentAt) : void 0;
886
1107
  const meta = {
@@ -894,7 +1115,7 @@ var SocketClient = class {
894
1115
  ctx: {
895
1116
  receivedAt,
896
1117
  latencyMs: sentAt ? Math.max(0, receivedAt.getTime() - sentAt.getTime()) : void 0,
897
- nsp: socket.nsp,
1118
+ nsp: this.getNamespace(socket),
898
1119
  socketId: socket.id,
899
1120
  socket
900
1121
  }
@@ -941,6 +1162,15 @@ var SocketClient = class {
941
1162
  destroy(leaveMeta) {
942
1163
  this.stopHeartbeat();
943
1164
  const socket = this.socket;
1165
+ this.dbg({
1166
+ type: "lifecycle",
1167
+ phase: "destroy_begin",
1168
+ socketId: socket?.id,
1169
+ details: {
1170
+ roomsTracked: this.roomCounts.size,
1171
+ handlerEvents: this.handlerMap.size
1172
+ }
1173
+ });
944
1174
  if (socket) {
945
1175
  socket.off("connect", this.onConnect);
946
1176
  socket.off("reconnect", this.onReconnect);
@@ -960,18 +1190,58 @@ var SocketClient = class {
960
1190
  this.leaveRooms(toLeave, leaveMeta);
961
1191
  }
962
1192
  this.roomCounts.clear();
1193
+ this.dbg({
1194
+ type: "lifecycle",
1195
+ phase: "destroy_complete",
1196
+ socketId: socket?.id,
1197
+ details: {
1198
+ roomsTracked: this.roomCounts.size,
1199
+ handlerEvents: this.handlerMap.size
1200
+ }
1201
+ });
963
1202
  }
964
1203
  /** Pass-throughs. Managing connection is the caller’s responsibility. */
965
1204
  disconnect() {
966
- if (!this.socket) return;
1205
+ if (!this.socket) {
1206
+ this.dbg({
1207
+ type: "connection",
1208
+ phase: "disconnect",
1209
+ reason: "client_disconnect",
1210
+ err: "Socket is null"
1211
+ });
1212
+ return;
1213
+ }
967
1214
  this.stopHeartbeat();
968
1215
  this.socket.disconnect();
969
- this.dbg({ type: "connection", phase: "disconnect", reason: "client_disconnect" });
1216
+ this.dbg({
1217
+ type: "connection",
1218
+ phase: "disconnect",
1219
+ reason: "client_disconnect",
1220
+ details: {
1221
+ nsp: this.getNamespace(this.socket)
1222
+ }
1223
+ });
1224
+ this.logSocketConfigSnapshot("update", "disconnect_call");
970
1225
  }
971
1226
  connect() {
972
- if (!this.socket) return;
1227
+ if (!this.socket) {
1228
+ this.dbg({
1229
+ type: "connection",
1230
+ phase: "connect",
1231
+ err: "Socket is null"
1232
+ });
1233
+ return;
1234
+ }
973
1235
  this.socket.connect();
974
- this.dbg({ type: "connection", phase: "connect", id: this.socket.id ?? "" });
1236
+ this.dbg({
1237
+ type: "connection",
1238
+ phase: "connect",
1239
+ id: this.socket.id ?? "",
1240
+ details: {
1241
+ nsp: this.getNamespace(this.socket)
1242
+ }
1243
+ });
1244
+ this.logSocketConfigSnapshot("update", "connect_call");
975
1245
  }
976
1246
  };
977
1247
  // Annotate the CommonJS export names for ESM import in node: