@emeryld/rrroutes-client 2.7.12 → 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.cjs CHANGED
@@ -40,7 +40,9 @@ __export(index_exports, {
40
40
  createRouteClient: () => createRouteClient,
41
41
  defaultFetcher: () => defaultFetcher,
42
42
  useSocketClient: () => useSocketClient,
43
- useSocketConnection: () => useSocketConnection
43
+ useSocketConnection: () => useSocketConnection,
44
+ useSocketEvent: () => useSocketEvent,
45
+ useSocketRooms: () => useSocketRooms
44
46
  });
45
47
  module.exports = __toCommonJS(index_exports);
46
48
 
@@ -1589,25 +1591,67 @@ function useSocketClient() {
1589
1591
 
1590
1592
  // src/sockets/socket.client.context.connection.ts
1591
1593
  var React2 = __toESM(require("react"), 1);
1592
- function useSocketConnection(args) {
1594
+ function normalizeRooms(rooms) {
1595
+ if (rooms == null) return [];
1596
+ return Array.isArray(rooms) ? rooms : [rooms];
1597
+ }
1598
+ function safeSerialize(value) {
1599
+ try {
1600
+ return JSON.stringify(value) ?? String(value);
1601
+ } catch {
1602
+ return "[unserializable]";
1603
+ }
1604
+ }
1605
+ function useLatestRef(value) {
1606
+ const ref = React2.useRef(value);
1607
+ React2.useEffect(() => {
1608
+ ref.current = value;
1609
+ }, [value]);
1610
+ return ref;
1611
+ }
1612
+ function useDebugEmitter(debug) {
1613
+ const debugRef = useLatestRef(debug);
1614
+ return React2.useCallback((event) => {
1615
+ const dbg3 = debugRef.current;
1616
+ if (!dbg3?.enabled) return;
1617
+ if (dbg3[event.type] === false) return;
1618
+ const logger = dbg3.logger;
1619
+ if (logger) {
1620
+ try {
1621
+ logger(event);
1622
+ } catch (error) {
1623
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
1624
+ console.warn("[socket] debug logger threw", error);
1625
+ }
1626
+ }
1627
+ return;
1628
+ }
1629
+ if (typeof console !== "undefined" && typeof console.log === "function") {
1630
+ console.log("[socket]", event);
1631
+ }
1632
+ }, []);
1633
+ }
1634
+ function useSocketRooms(args) {
1593
1635
  const {
1594
- event,
1595
1636
  rooms,
1596
- onMessage,
1597
- onCleanup,
1598
1637
  autoJoin = true,
1599
1638
  autoLeave = true,
1639
+ enabled = true,
1640
+ onCleanup,
1600
1641
  debug
1601
1642
  } = args;
1602
1643
  const client = useSocketClient();
1603
- const normalizedRooms = React2.useMemo(
1604
- () => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms],
1605
- [rooms]
1606
- );
1644
+ const normalizedRooms = React2.useMemo(() => normalizeRooms(rooms), [rooms]);
1607
1645
  const normalizedRoomsKey = React2.useMemo(
1608
1646
  () => normalizedRooms.join(""),
1609
1647
  [normalizedRooms]
1610
1648
  );
1649
+ const joinMetaRef = useLatestRef(args.joinMeta);
1650
+ const leaveMetaRef = useLatestRef(args.leaveMeta);
1651
+ const onCleanupRef = useLatestRef(onCleanup);
1652
+ const debugRef = useLatestRef(debug);
1653
+ const emitDebug = useDebugEmitter(debug);
1654
+ const missingClientWarnedRef = React2.useRef(false);
1611
1655
  const joinMetaKey = React2.useMemo(
1612
1656
  () => JSON.stringify(args.joinMeta ?? null),
1613
1657
  [args.joinMeta]
@@ -1616,107 +1660,48 @@ function useSocketConnection(args) {
1616
1660
  () => JSON.stringify(args.leaveMeta ?? null),
1617
1661
  [args.leaveMeta]
1618
1662
  );
1619
- const missingClientWarnedRef = React2.useRef(false);
1620
- const onMessageRef = React2.useRef(onMessage);
1621
- const onCleanupRef = React2.useRef(onCleanup);
1622
- const joinMetaRef = React2.useRef(args.joinMeta);
1623
- const leaveMetaRef = React2.useRef(args.leaveMeta);
1624
- const debugRef = React2.useRef(debug);
1625
- React2.useEffect(() => {
1626
- onMessageRef.current = onMessage;
1627
- }, [onMessage]);
1628
- React2.useEffect(() => {
1629
- onCleanupRef.current = onCleanup;
1630
- }, [onCleanup]);
1631
- React2.useEffect(() => {
1632
- joinMetaRef.current = args.joinMeta;
1633
- }, [args.joinMeta]);
1634
- React2.useEffect(() => {
1635
- leaveMetaRef.current = args.leaveMeta;
1636
- }, [args.leaveMeta]);
1637
- React2.useEffect(() => {
1638
- debugRef.current = debug;
1639
- }, [debug]);
1640
- const emitDebug = React2.useCallback(
1641
- (event2) => {
1642
- const dbg3 = debugRef.current;
1643
- if (!dbg3?.enabled) return;
1644
- if (dbg3[event2.type] === false) return;
1645
- const logger = dbg3.logger;
1646
- if (logger) {
1647
- try {
1648
- logger(event2);
1649
- } catch (error) {
1650
- if (typeof console !== "undefined" && typeof console.warn === "function") {
1651
- console.warn("[socket] useSocketConnection debug logger threw", error);
1652
- }
1653
- }
1654
- return;
1655
- }
1656
- if (typeof console !== "undefined" && typeof console.log === "function") {
1657
- console.log("[socket] useSocketConnection", event2);
1658
- }
1659
- },
1660
- []
1661
- );
1662
- const reportAsyncError = React2.useCallback(
1663
- (phase, error) => {
1664
- if (typeof console !== "undefined" && typeof console.warn === "function") {
1665
- console.warn(`[socket] useSocketConnection ${phase} failed`, error);
1666
- }
1667
- },
1668
- []
1669
- );
1670
1663
  React2.useEffect(() => {
1671
1664
  emitDebug({
1672
1665
  type: "lifecycle",
1673
1666
  phase: "effect_start",
1674
- event,
1675
1667
  rooms: normalizedRooms,
1676
1668
  autoJoin,
1677
1669
  autoLeave
1678
1670
  });
1671
+ if (!enabled) {
1672
+ emitDebug({
1673
+ type: "room",
1674
+ phase: "join_skip",
1675
+ rooms: normalizedRooms,
1676
+ reason: "disabled"
1677
+ });
1678
+ return;
1679
+ }
1679
1680
  if (!client) {
1680
1681
  emitDebug({
1681
1682
  type: "lifecycle",
1682
1683
  phase: "client_missing",
1683
- event,
1684
1684
  rooms: normalizedRooms,
1685
1685
  autoJoin,
1686
1686
  autoLeave
1687
1687
  });
1688
- if (debugRef.current?.throwIfClientMissing) {
1689
- throw new Error(
1690
- `useSocketConnection("${event}") missing SocketClient. Wrap with <SocketProvider>.`
1691
- );
1692
- }
1693
1688
  if (debugRef.current?.warnIfClientMissing && !missingClientWarnedRef.current && typeof console !== "undefined" && typeof console.warn === "function") {
1694
1689
  missingClientWarnedRef.current = true;
1695
- console.warn(
1696
- `[socket] useSocketConnection("${event}") skipped because SocketClient is null.`
1697
- );
1690
+ console.warn("[socket] useSocketRooms skipped because SocketClient is null.");
1698
1691
  }
1699
1692
  return;
1700
1693
  }
1701
1694
  missingClientWarnedRef.current = false;
1702
- emitDebug({ type: "subscription", phase: "register", event });
1703
1695
  if (autoJoin && normalizedRooms.length > 0) {
1704
- emitDebug({
1705
- type: "room",
1706
- phase: "join_attempt",
1707
- rooms: normalizedRooms
1708
- });
1709
- void client.joinRooms(normalizedRooms, joinMetaRef.current).then(() => {
1710
- emitDebug({ type: "room", phase: "join_ok", rooms: normalizedRooms });
1711
- }).catch((error) => {
1712
- emitDebug({
1696
+ emitDebug({ type: "room", phase: "join_attempt", rooms: normalizedRooms });
1697
+ void client.joinRooms(normalizedRooms, joinMetaRef.current).then(() => emitDebug({ type: "room", phase: "join_ok", rooms: normalizedRooms })).catch(
1698
+ (error) => emitDebug({
1713
1699
  type: "room",
1714
1700
  phase: "join_error",
1715
1701
  rooms: normalizedRooms,
1716
1702
  err: String(error)
1717
- });
1718
- reportAsyncError("joinRooms", error);
1719
- });
1703
+ })
1704
+ );
1720
1705
  } else {
1721
1706
  emitDebug({
1722
1707
  type: "room",
@@ -1725,30 +1710,23 @@ function useSocketConnection(args) {
1725
1710
  reason: autoJoin ? "no_rooms" : "auto_disabled"
1726
1711
  });
1727
1712
  }
1728
- const unsubscribe = client.on(event, (payload, meta) => {
1729
- emitDebug({ type: "subscription", phase: "message", event });
1730
- onMessageRef.current(payload, meta);
1731
- });
1732
1713
  return () => {
1733
- emitDebug({ type: "subscription", phase: "unregister", event });
1734
- unsubscribe();
1735
1714
  if (autoLeave && normalizedRooms.length > 0) {
1736
1715
  emitDebug({
1737
1716
  type: "room",
1738
1717
  phase: "leave_attempt",
1739
1718
  rooms: normalizedRooms
1740
1719
  });
1741
- void client.leaveRooms(normalizedRooms, leaveMetaRef.current).then(() => {
1742
- emitDebug({ type: "room", phase: "leave_ok", rooms: normalizedRooms });
1743
- }).catch((error) => {
1744
- emitDebug({
1720
+ void client.leaveRooms(normalizedRooms, leaveMetaRef.current).then(
1721
+ () => emitDebug({ type: "room", phase: "leave_ok", rooms: normalizedRooms })
1722
+ ).catch(
1723
+ (error) => emitDebug({
1745
1724
  type: "room",
1746
1725
  phase: "leave_error",
1747
1726
  rooms: normalizedRooms,
1748
1727
  err: String(error)
1749
- });
1750
- reportAsyncError("leaveRooms", error);
1751
- });
1728
+ })
1729
+ );
1752
1730
  } else {
1753
1731
  emitDebug({
1754
1732
  type: "room",
@@ -1757,11 +1735,10 @@ function useSocketConnection(args) {
1757
1735
  reason: autoLeave ? "no_rooms" : "auto_disabled"
1758
1736
  });
1759
1737
  }
1760
- if (onCleanupRef.current) onCleanupRef.current();
1738
+ onCleanupRef.current?.();
1761
1739
  emitDebug({
1762
1740
  type: "lifecycle",
1763
1741
  phase: "effect_cleanup",
1764
- event,
1765
1742
  rooms: normalizedRooms,
1766
1743
  autoJoin,
1767
1744
  autoLeave
@@ -1769,16 +1746,78 @@ function useSocketConnection(args) {
1769
1746
  };
1770
1747
  }, [
1771
1748
  client,
1772
- event,
1749
+ enabled,
1773
1750
  autoJoin,
1774
1751
  autoLeave,
1775
- emitDebug,
1776
- reportAsyncError,
1777
1752
  normalizedRoomsKey,
1778
1753
  joinMetaKey,
1779
- leaveMetaKey
1754
+ leaveMetaKey,
1755
+ emitDebug
1780
1756
  ]);
1781
1757
  }
1758
+ function useSocketEvent(args) {
1759
+ const {
1760
+ event,
1761
+ onMessage,
1762
+ onCleanup,
1763
+ enabled = true,
1764
+ messageDeduplication,
1765
+ debug
1766
+ } = args;
1767
+ const client = useSocketClient();
1768
+ const onMessageRef = useLatestRef(onMessage);
1769
+ const onCleanupRef = useLatestRef(onCleanup);
1770
+ const messageDeduplicationRef = useLatestRef(messageDeduplication);
1771
+ const debugRef = useLatestRef(debug);
1772
+ const emitDebug = useDebugEmitter(debug);
1773
+ const missingClientWarnedRef = React2.useRef(false);
1774
+ const seenMessageKeysRef = React2.useRef(/* @__PURE__ */ new Map());
1775
+ React2.useEffect(() => {
1776
+ emitDebug({ type: "lifecycle", phase: "effect_start", event });
1777
+ if (!enabled) return;
1778
+ if (!client) {
1779
+ emitDebug({ type: "lifecycle", phase: "client_missing", event });
1780
+ if (debugRef.current?.warnIfClientMissing && !missingClientWarnedRef.current && typeof console !== "undefined" && typeof console.warn === "function") {
1781
+ missingClientWarnedRef.current = true;
1782
+ console.warn(`[socket] useSocketEvent("${event}") skipped because SocketClient is null.`);
1783
+ }
1784
+ return;
1785
+ }
1786
+ missingClientWarnedRef.current = false;
1787
+ emitDebug({ type: "subscription", phase: "register", event });
1788
+ const unsubscribe = client.on(event, (payload, meta) => {
1789
+ const dedupeCfg = messageDeduplicationRef.current;
1790
+ if (dedupeCfg) {
1791
+ const ttlMs = Math.max(0, dedupeCfg.ttlMs ?? 1500);
1792
+ const now = Date.now();
1793
+ const dedupeKey = dedupeCfg.key?.(payload, meta) ?? (meta?.envelope ? `${event}|${String(meta.envelope.eventName)}|${String(meta.envelope.sentAt)}|${safeSerialize(payload)}` : `${event}|${safeSerialize(payload)}`);
1794
+ const prev = seenMessageKeysRef.current.get(dedupeKey);
1795
+ if (typeof prev === "number" && now - prev <= ttlMs) {
1796
+ emitDebug({
1797
+ type: "subscription",
1798
+ phase: "message_dropped_duplicate",
1799
+ event,
1800
+ dedupeKey
1801
+ });
1802
+ return;
1803
+ }
1804
+ seenMessageKeysRef.current.set(dedupeKey, now);
1805
+ }
1806
+ emitDebug({ type: "subscription", phase: "message", event });
1807
+ onMessageRef.current(payload, meta);
1808
+ });
1809
+ return () => {
1810
+ emitDebug({ type: "subscription", phase: "unregister", event });
1811
+ unsubscribe();
1812
+ onCleanupRef.current?.();
1813
+ emitDebug({ type: "lifecycle", phase: "effect_cleanup", event });
1814
+ };
1815
+ }, [client, enabled, event, emitDebug]);
1816
+ }
1817
+ function useSocketConnection(roomsArgs, eventArgs) {
1818
+ useSocketRooms(roomsArgs);
1819
+ useSocketEvent(eventArgs);
1820
+ }
1782
1821
 
1783
1822
  // src/sockets/socket.client.context.debug.ts
1784
1823
  function dbg(dbgOpts, e) {
@@ -2700,7 +2739,9 @@ function buildSocketProvider(args) {
2700
2739
  }
2701
2740
  ),
2702
2741
  useSocketClient: () => useSocketClient(),
2703
- useSocketConnection: (p) => useSocketConnection(p)
2742
+ useSocketRooms: (p) => useSocketRooms(p),
2743
+ useSocketEvent: (p) => useSocketEvent(p),
2744
+ useSocketConnection: (...args2) => useSocketConnection(...args2)
2704
2745
  };
2705
2746
  }
2706
2747
  function SocketProvider(props) {
@@ -2915,7 +2956,7 @@ function trackHookTrigger2({
2915
2956
  }
2916
2957
 
2917
2958
  // src/sockets/socketedRoute/socket.client.helper.rooms.ts
2918
- function normalizeRooms(rooms) {
2959
+ function normalizeRooms2(rooms) {
2919
2960
  if (rooms == null) return [];
2920
2961
  const list = Array.isArray(rooms) ? rooms : [rooms];
2921
2962
  const seen = /* @__PURE__ */ new Set();
@@ -2940,7 +2981,7 @@ function roomStateEqual(prev, next) {
2940
2981
  }
2941
2982
  function mergeRoomState(prev, toRoomsResult) {
2942
2983
  const merged = new Set(prev.rooms);
2943
- for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
2984
+ for (const r of normalizeRooms2(toRoomsResult.rooms)) merged.add(r);
2944
2985
  return {
2945
2986
  rooms: Array.from(merged),
2946
2987
  joinMeta: toRoomsResult.joinMeta ?? prev.joinMeta,
@@ -3302,6 +3343,8 @@ function buildSocketedRoute(options) {
3302
3343
  createRouteClient,
3303
3344
  defaultFetcher,
3304
3345
  useSocketClient,
3305
- useSocketConnection
3346
+ useSocketConnection,
3347
+ useSocketEvent,
3348
+ useSocketRooms
3306
3349
  });
3307
3350
  //# sourceMappingURL=index.cjs.map