@emeryld/rrroutes-client 2.7.11 → 2.7.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.mjs CHANGED
@@ -1550,10 +1550,7 @@ import * as React from "react";
1550
1550
  var SocketCtx = React.createContext(void 0);
1551
1551
  function useSocketClient() {
1552
1552
  const ctx = React.useContext(SocketCtx);
1553
- if (typeof ctx === "undefined") {
1554
- throw new Error("SocketClient not found. Wrap with <SocketProvider>.");
1555
- }
1556
- if (!ctx) {
1553
+ if (typeof ctx === "undefined" || !ctx) {
1557
1554
  return null;
1558
1555
  }
1559
1556
  return ctx;
@@ -1561,25 +1558,67 @@ function useSocketClient() {
1561
1558
 
1562
1559
  // src/sockets/socket.client.context.connection.ts
1563
1560
  import * as React2 from "react";
1564
- function useSocketConnection(args) {
1561
+ function normalizeRooms(rooms) {
1562
+ if (rooms == null) return [];
1563
+ return Array.isArray(rooms) ? rooms : [rooms];
1564
+ }
1565
+ function safeSerialize(value) {
1566
+ try {
1567
+ return JSON.stringify(value) ?? String(value);
1568
+ } catch {
1569
+ return "[unserializable]";
1570
+ }
1571
+ }
1572
+ function useLatestRef(value) {
1573
+ const ref = React2.useRef(value);
1574
+ React2.useEffect(() => {
1575
+ ref.current = value;
1576
+ }, [value]);
1577
+ return ref;
1578
+ }
1579
+ function useDebugEmitter(debug) {
1580
+ const debugRef = useLatestRef(debug);
1581
+ return React2.useCallback((event) => {
1582
+ const dbg3 = debugRef.current;
1583
+ if (!dbg3?.enabled) return;
1584
+ if (dbg3[event.type] === false) return;
1585
+ const logger = dbg3.logger;
1586
+ if (logger) {
1587
+ try {
1588
+ logger(event);
1589
+ } catch (error) {
1590
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
1591
+ console.warn("[socket] debug logger threw", error);
1592
+ }
1593
+ }
1594
+ return;
1595
+ }
1596
+ if (typeof console !== "undefined" && typeof console.log === "function") {
1597
+ console.log("[socket]", event);
1598
+ }
1599
+ }, []);
1600
+ }
1601
+ function useSocketRooms(args) {
1565
1602
  const {
1566
- event,
1567
1603
  rooms,
1568
- onMessage,
1569
- onCleanup,
1570
1604
  autoJoin = true,
1571
1605
  autoLeave = true,
1606
+ enabled = true,
1607
+ onCleanup,
1572
1608
  debug
1573
1609
  } = args;
1574
1610
  const client = useSocketClient();
1575
- const normalizedRooms = React2.useMemo(
1576
- () => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms],
1577
- [rooms]
1578
- );
1611
+ const normalizedRooms = React2.useMemo(() => normalizeRooms(rooms), [rooms]);
1579
1612
  const normalizedRoomsKey = React2.useMemo(
1580
1613
  () => normalizedRooms.join(""),
1581
1614
  [normalizedRooms]
1582
1615
  );
1616
+ const joinMetaRef = useLatestRef(args.joinMeta);
1617
+ const leaveMetaRef = useLatestRef(args.leaveMeta);
1618
+ const onCleanupRef = useLatestRef(onCleanup);
1619
+ const debugRef = useLatestRef(debug);
1620
+ const emitDebug = useDebugEmitter(debug);
1621
+ const missingClientWarnedRef = React2.useRef(false);
1583
1622
  const joinMetaKey = React2.useMemo(
1584
1623
  () => JSON.stringify(args.joinMeta ?? null),
1585
1624
  [args.joinMeta]
@@ -1588,86 +1627,48 @@ function useSocketConnection(args) {
1588
1627
  () => JSON.stringify(args.leaveMeta ?? null),
1589
1628
  [args.leaveMeta]
1590
1629
  );
1591
- const missingClientWarnedRef = React2.useRef(false);
1592
- const emitDebug = React2.useCallback(
1593
- (event2) => {
1594
- if (!debug?.enabled) return;
1595
- if (debug[event2.type] === false) return;
1596
- const logger = debug.logger;
1597
- if (logger) {
1598
- try {
1599
- logger(event2);
1600
- } catch (error) {
1601
- if (typeof console !== "undefined" && typeof console.warn === "function") {
1602
- console.warn("[socket] useSocketConnection debug logger threw", error);
1603
- }
1604
- }
1605
- return;
1606
- }
1607
- if (typeof console !== "undefined" && typeof console.log === "function") {
1608
- console.log("[socket] useSocketConnection", event2);
1609
- }
1610
- },
1611
- [debug]
1612
- );
1613
- const reportAsyncError = React2.useCallback(
1614
- (phase, error) => {
1615
- if (typeof console !== "undefined" && typeof console.warn === "function") {
1616
- console.warn(`[socket] useSocketConnection ${phase} failed`, error);
1617
- }
1618
- },
1619
- []
1620
- );
1621
1630
  React2.useEffect(() => {
1622
1631
  emitDebug({
1623
1632
  type: "lifecycle",
1624
1633
  phase: "effect_start",
1625
- event,
1626
1634
  rooms: normalizedRooms,
1627
1635
  autoJoin,
1628
1636
  autoLeave
1629
1637
  });
1638
+ if (!enabled) {
1639
+ emitDebug({
1640
+ type: "room",
1641
+ phase: "join_skip",
1642
+ rooms: normalizedRooms,
1643
+ reason: "disabled"
1644
+ });
1645
+ return;
1646
+ }
1630
1647
  if (!client) {
1631
1648
  emitDebug({
1632
1649
  type: "lifecycle",
1633
1650
  phase: "client_missing",
1634
- event,
1635
1651
  rooms: normalizedRooms,
1636
1652
  autoJoin,
1637
1653
  autoLeave
1638
1654
  });
1639
- if (debug?.throwIfClientMissing) {
1640
- throw new Error(
1641
- `useSocketConnection("${event}") missing SocketClient. Wrap with <SocketProvider>.`
1642
- );
1643
- }
1644
- if (debug?.warnIfClientMissing && !missingClientWarnedRef.current && typeof console !== "undefined" && typeof console.warn === "function") {
1655
+ if (debugRef.current?.warnIfClientMissing && !missingClientWarnedRef.current && typeof console !== "undefined" && typeof console.warn === "function") {
1645
1656
  missingClientWarnedRef.current = true;
1646
- console.warn(
1647
- `[socket] useSocketConnection("${event}") skipped because SocketClient is null.`
1648
- );
1657
+ console.warn("[socket] useSocketRooms skipped because SocketClient is null.");
1649
1658
  }
1650
1659
  return;
1651
1660
  }
1652
1661
  missingClientWarnedRef.current = false;
1653
- emitDebug({ type: "subscription", phase: "register", event });
1654
1662
  if (autoJoin && normalizedRooms.length > 0) {
1655
- emitDebug({
1656
- type: "room",
1657
- phase: "join_attempt",
1658
- rooms: normalizedRooms
1659
- });
1660
- void client.joinRooms(normalizedRooms, args.joinMeta).then(() => {
1661
- emitDebug({ type: "room", phase: "join_ok", rooms: normalizedRooms });
1662
- }).catch((error) => {
1663
- emitDebug({
1663
+ emitDebug({ type: "room", phase: "join_attempt", rooms: normalizedRooms });
1664
+ void client.joinRooms(normalizedRooms, joinMetaRef.current).then(() => emitDebug({ type: "room", phase: "join_ok", rooms: normalizedRooms })).catch(
1665
+ (error) => emitDebug({
1664
1666
  type: "room",
1665
1667
  phase: "join_error",
1666
1668
  rooms: normalizedRooms,
1667
1669
  err: String(error)
1668
- });
1669
- reportAsyncError("joinRooms", error);
1670
- });
1670
+ })
1671
+ );
1671
1672
  } else {
1672
1673
  emitDebug({
1673
1674
  type: "room",
@@ -1676,30 +1677,23 @@ function useSocketConnection(args) {
1676
1677
  reason: autoJoin ? "no_rooms" : "auto_disabled"
1677
1678
  });
1678
1679
  }
1679
- const unsubscribe = client.on(event, (payload, meta) => {
1680
- emitDebug({ type: "subscription", phase: "message", event });
1681
- onMessage(payload, meta);
1682
- });
1683
1680
  return () => {
1684
- emitDebug({ type: "subscription", phase: "unregister", event });
1685
- unsubscribe();
1686
1681
  if (autoLeave && normalizedRooms.length > 0) {
1687
1682
  emitDebug({
1688
1683
  type: "room",
1689
1684
  phase: "leave_attempt",
1690
1685
  rooms: normalizedRooms
1691
1686
  });
1692
- void client.leaveRooms(normalizedRooms, args.leaveMeta).then(() => {
1693
- emitDebug({ type: "room", phase: "leave_ok", rooms: normalizedRooms });
1694
- }).catch((error) => {
1695
- emitDebug({
1687
+ void client.leaveRooms(normalizedRooms, leaveMetaRef.current).then(
1688
+ () => emitDebug({ type: "room", phase: "leave_ok", rooms: normalizedRooms })
1689
+ ).catch(
1690
+ (error) => emitDebug({
1696
1691
  type: "room",
1697
1692
  phase: "leave_error",
1698
1693
  rooms: normalizedRooms,
1699
1694
  err: String(error)
1700
- });
1701
- reportAsyncError("leaveRooms", error);
1702
- });
1695
+ })
1696
+ );
1703
1697
  } else {
1704
1698
  emitDebug({
1705
1699
  type: "room",
@@ -1708,11 +1702,10 @@ function useSocketConnection(args) {
1708
1702
  reason: autoLeave ? "no_rooms" : "auto_disabled"
1709
1703
  });
1710
1704
  }
1711
- if (onCleanup) onCleanup();
1705
+ onCleanupRef.current?.();
1712
1706
  emitDebug({
1713
1707
  type: "lifecycle",
1714
1708
  phase: "effect_cleanup",
1715
- event,
1716
1709
  rooms: normalizedRooms,
1717
1710
  autoJoin,
1718
1711
  autoLeave
@@ -1720,19 +1713,78 @@ function useSocketConnection(args) {
1720
1713
  };
1721
1714
  }, [
1722
1715
  client,
1723
- event,
1724
- onMessage,
1725
- onCleanup,
1716
+ enabled,
1726
1717
  autoJoin,
1727
1718
  autoLeave,
1728
- debug,
1729
- emitDebug,
1730
- reportAsyncError,
1731
1719
  normalizedRoomsKey,
1732
1720
  joinMetaKey,
1733
- leaveMetaKey
1721
+ leaveMetaKey,
1722
+ emitDebug
1734
1723
  ]);
1735
1724
  }
1725
+ function useSocketEvent(args) {
1726
+ const {
1727
+ event,
1728
+ onMessage,
1729
+ onCleanup,
1730
+ enabled = true,
1731
+ messageDeduplication,
1732
+ debug
1733
+ } = args;
1734
+ const client = useSocketClient();
1735
+ const onMessageRef = useLatestRef(onMessage);
1736
+ const onCleanupRef = useLatestRef(onCleanup);
1737
+ const messageDeduplicationRef = useLatestRef(messageDeduplication);
1738
+ const debugRef = useLatestRef(debug);
1739
+ const emitDebug = useDebugEmitter(debug);
1740
+ const missingClientWarnedRef = React2.useRef(false);
1741
+ const seenMessageKeysRef = React2.useRef(/* @__PURE__ */ new Map());
1742
+ React2.useEffect(() => {
1743
+ emitDebug({ type: "lifecycle", phase: "effect_start", event });
1744
+ if (!enabled) return;
1745
+ if (!client) {
1746
+ emitDebug({ type: "lifecycle", phase: "client_missing", event });
1747
+ if (debugRef.current?.warnIfClientMissing && !missingClientWarnedRef.current && typeof console !== "undefined" && typeof console.warn === "function") {
1748
+ missingClientWarnedRef.current = true;
1749
+ console.warn(`[socket] useSocketEvent("${event}") skipped because SocketClient is null.`);
1750
+ }
1751
+ return;
1752
+ }
1753
+ missingClientWarnedRef.current = false;
1754
+ emitDebug({ type: "subscription", phase: "register", event });
1755
+ const unsubscribe = client.on(event, (payload, meta) => {
1756
+ const dedupeCfg = messageDeduplicationRef.current;
1757
+ if (dedupeCfg) {
1758
+ const ttlMs = Math.max(0, dedupeCfg.ttlMs ?? 1500);
1759
+ const now = Date.now();
1760
+ const dedupeKey = dedupeCfg.key?.(payload, meta) ?? (meta?.envelope ? `${event}|${String(meta.envelope.eventName)}|${String(meta.envelope.sentAt)}|${safeSerialize(payload)}` : `${event}|${safeSerialize(payload)}`);
1761
+ const prev = seenMessageKeysRef.current.get(dedupeKey);
1762
+ if (typeof prev === "number" && now - prev <= ttlMs) {
1763
+ emitDebug({
1764
+ type: "subscription",
1765
+ phase: "message_dropped_duplicate",
1766
+ event,
1767
+ dedupeKey
1768
+ });
1769
+ return;
1770
+ }
1771
+ seenMessageKeysRef.current.set(dedupeKey, now);
1772
+ }
1773
+ emitDebug({ type: "subscription", phase: "message", event });
1774
+ onMessageRef.current(payload, meta);
1775
+ });
1776
+ return () => {
1777
+ emitDebug({ type: "subscription", phase: "unregister", event });
1778
+ unsubscribe();
1779
+ onCleanupRef.current?.();
1780
+ emitDebug({ type: "lifecycle", phase: "effect_cleanup", event });
1781
+ };
1782
+ }, [client, enabled, event, emitDebug]);
1783
+ }
1784
+ function useSocketConnection(roomsArgs, eventArgs) {
1785
+ useSocketRooms(roomsArgs);
1786
+ useSocketEvent(eventArgs);
1787
+ }
1736
1788
 
1737
1789
  // src/sockets/socket.client.context.debug.ts
1738
1790
  function dbg(dbgOpts, e) {
@@ -2654,7 +2706,9 @@ function buildSocketProvider(args) {
2654
2706
  }
2655
2707
  ),
2656
2708
  useSocketClient: () => useSocketClient(),
2657
- useSocketConnection: (p) => useSocketConnection(p)
2709
+ useSocketRooms: (p) => useSocketRooms(p),
2710
+ useSocketEvent: (p) => useSocketEvent(p),
2711
+ useSocketConnection: (...args2) => useSocketConnection(...args2)
2658
2712
  };
2659
2713
  }
2660
2714
  function SocketProvider(props) {
@@ -2869,7 +2923,7 @@ function trackHookTrigger2({
2869
2923
  }
2870
2924
 
2871
2925
  // src/sockets/socketedRoute/socket.client.helper.rooms.ts
2872
- function normalizeRooms(rooms) {
2926
+ function normalizeRooms2(rooms) {
2873
2927
  if (rooms == null) return [];
2874
2928
  const list = Array.isArray(rooms) ? rooms : [rooms];
2875
2929
  const seen = /* @__PURE__ */ new Set();
@@ -2894,7 +2948,7 @@ function roomStateEqual(prev, next) {
2894
2948
  }
2895
2949
  function mergeRoomState(prev, toRoomsResult) {
2896
2950
  const merged = new Set(prev.rooms);
2897
- for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
2951
+ for (const r of normalizeRooms2(toRoomsResult.rooms)) merged.add(r);
2898
2952
  return {
2899
2953
  rooms: Array.from(merged),
2900
2954
  joinMeta: toRoomsResult.joinMeta ?? prev.joinMeta,
@@ -3255,6 +3309,8 @@ export {
3255
3309
  createRouteClient,
3256
3310
  defaultFetcher,
3257
3311
  useSocketClient,
3258
- useSocketConnection
3312
+ useSocketConnection,
3313
+ useSocketEvent,
3314
+ useSocketRooms
3259
3315
  };
3260
3316
  //# sourceMappingURL=index.mjs.map