@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.mjs CHANGED
@@ -1558,25 +1558,67 @@ function useSocketClient() {
1558
1558
 
1559
1559
  // src/sockets/socket.client.context.connection.ts
1560
1560
  import * as React2 from "react";
1561
- 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) {
1562
1602
  const {
1563
- event,
1564
1603
  rooms,
1565
- onMessage,
1566
- onCleanup,
1567
1604
  autoJoin = true,
1568
1605
  autoLeave = true,
1606
+ enabled = true,
1607
+ onCleanup,
1569
1608
  debug
1570
1609
  } = args;
1571
1610
  const client = useSocketClient();
1572
- const normalizedRooms = React2.useMemo(
1573
- () => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms],
1574
- [rooms]
1575
- );
1611
+ const normalizedRooms = React2.useMemo(() => normalizeRooms(rooms), [rooms]);
1576
1612
  const normalizedRoomsKey = React2.useMemo(
1577
1613
  () => normalizedRooms.join(""),
1578
1614
  [normalizedRooms]
1579
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);
1580
1622
  const joinMetaKey = React2.useMemo(
1581
1623
  () => JSON.stringify(args.joinMeta ?? null),
1582
1624
  [args.joinMeta]
@@ -1585,107 +1627,48 @@ function useSocketConnection(args) {
1585
1627
  () => JSON.stringify(args.leaveMeta ?? null),
1586
1628
  [args.leaveMeta]
1587
1629
  );
1588
- const missingClientWarnedRef = React2.useRef(false);
1589
- const onMessageRef = React2.useRef(onMessage);
1590
- const onCleanupRef = React2.useRef(onCleanup);
1591
- const joinMetaRef = React2.useRef(args.joinMeta);
1592
- const leaveMetaRef = React2.useRef(args.leaveMeta);
1593
- const debugRef = React2.useRef(debug);
1594
- React2.useEffect(() => {
1595
- onMessageRef.current = onMessage;
1596
- }, [onMessage]);
1597
- React2.useEffect(() => {
1598
- onCleanupRef.current = onCleanup;
1599
- }, [onCleanup]);
1600
- React2.useEffect(() => {
1601
- joinMetaRef.current = args.joinMeta;
1602
- }, [args.joinMeta]);
1603
- React2.useEffect(() => {
1604
- leaveMetaRef.current = args.leaveMeta;
1605
- }, [args.leaveMeta]);
1606
- React2.useEffect(() => {
1607
- debugRef.current = debug;
1608
- }, [debug]);
1609
- const emitDebug = React2.useCallback(
1610
- (event2) => {
1611
- const dbg3 = debugRef.current;
1612
- if (!dbg3?.enabled) return;
1613
- if (dbg3[event2.type] === false) return;
1614
- const logger = dbg3.logger;
1615
- if (logger) {
1616
- try {
1617
- logger(event2);
1618
- } catch (error) {
1619
- if (typeof console !== "undefined" && typeof console.warn === "function") {
1620
- console.warn("[socket] useSocketConnection debug logger threw", error);
1621
- }
1622
- }
1623
- return;
1624
- }
1625
- if (typeof console !== "undefined" && typeof console.log === "function") {
1626
- console.log("[socket] useSocketConnection", event2);
1627
- }
1628
- },
1629
- []
1630
- );
1631
- const reportAsyncError = React2.useCallback(
1632
- (phase, error) => {
1633
- if (typeof console !== "undefined" && typeof console.warn === "function") {
1634
- console.warn(`[socket] useSocketConnection ${phase} failed`, error);
1635
- }
1636
- },
1637
- []
1638
- );
1639
1630
  React2.useEffect(() => {
1640
1631
  emitDebug({
1641
1632
  type: "lifecycle",
1642
1633
  phase: "effect_start",
1643
- event,
1644
1634
  rooms: normalizedRooms,
1645
1635
  autoJoin,
1646
1636
  autoLeave
1647
1637
  });
1638
+ if (!enabled) {
1639
+ emitDebug({
1640
+ type: "room",
1641
+ phase: "join_skip",
1642
+ rooms: normalizedRooms,
1643
+ reason: "disabled"
1644
+ });
1645
+ return;
1646
+ }
1648
1647
  if (!client) {
1649
1648
  emitDebug({
1650
1649
  type: "lifecycle",
1651
1650
  phase: "client_missing",
1652
- event,
1653
1651
  rooms: normalizedRooms,
1654
1652
  autoJoin,
1655
1653
  autoLeave
1656
1654
  });
1657
- if (debugRef.current?.throwIfClientMissing) {
1658
- throw new Error(
1659
- `useSocketConnection("${event}") missing SocketClient. Wrap with <SocketProvider>.`
1660
- );
1661
- }
1662
1655
  if (debugRef.current?.warnIfClientMissing && !missingClientWarnedRef.current && typeof console !== "undefined" && typeof console.warn === "function") {
1663
1656
  missingClientWarnedRef.current = true;
1664
- console.warn(
1665
- `[socket] useSocketConnection("${event}") skipped because SocketClient is null.`
1666
- );
1657
+ console.warn("[socket] useSocketRooms skipped because SocketClient is null.");
1667
1658
  }
1668
1659
  return;
1669
1660
  }
1670
1661
  missingClientWarnedRef.current = false;
1671
- emitDebug({ type: "subscription", phase: "register", event });
1672
1662
  if (autoJoin && normalizedRooms.length > 0) {
1673
- emitDebug({
1674
- type: "room",
1675
- phase: "join_attempt",
1676
- rooms: normalizedRooms
1677
- });
1678
- void client.joinRooms(normalizedRooms, joinMetaRef.current).then(() => {
1679
- emitDebug({ type: "room", phase: "join_ok", rooms: normalizedRooms });
1680
- }).catch((error) => {
1681
- 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({
1682
1666
  type: "room",
1683
1667
  phase: "join_error",
1684
1668
  rooms: normalizedRooms,
1685
1669
  err: String(error)
1686
- });
1687
- reportAsyncError("joinRooms", error);
1688
- });
1670
+ })
1671
+ );
1689
1672
  } else {
1690
1673
  emitDebug({
1691
1674
  type: "room",
@@ -1694,30 +1677,23 @@ function useSocketConnection(args) {
1694
1677
  reason: autoJoin ? "no_rooms" : "auto_disabled"
1695
1678
  });
1696
1679
  }
1697
- const unsubscribe = client.on(event, (payload, meta) => {
1698
- emitDebug({ type: "subscription", phase: "message", event });
1699
- onMessageRef.current(payload, meta);
1700
- });
1701
1680
  return () => {
1702
- emitDebug({ type: "subscription", phase: "unregister", event });
1703
- unsubscribe();
1704
1681
  if (autoLeave && normalizedRooms.length > 0) {
1705
1682
  emitDebug({
1706
1683
  type: "room",
1707
1684
  phase: "leave_attempt",
1708
1685
  rooms: normalizedRooms
1709
1686
  });
1710
- void client.leaveRooms(normalizedRooms, leaveMetaRef.current).then(() => {
1711
- emitDebug({ type: "room", phase: "leave_ok", rooms: normalizedRooms });
1712
- }).catch((error) => {
1713
- emitDebug({
1687
+ void client.leaveRooms(normalizedRooms, leaveMetaRef.current).then(
1688
+ () => emitDebug({ type: "room", phase: "leave_ok", rooms: normalizedRooms })
1689
+ ).catch(
1690
+ (error) => emitDebug({
1714
1691
  type: "room",
1715
1692
  phase: "leave_error",
1716
1693
  rooms: normalizedRooms,
1717
1694
  err: String(error)
1718
- });
1719
- reportAsyncError("leaveRooms", error);
1720
- });
1695
+ })
1696
+ );
1721
1697
  } else {
1722
1698
  emitDebug({
1723
1699
  type: "room",
@@ -1726,11 +1702,10 @@ function useSocketConnection(args) {
1726
1702
  reason: autoLeave ? "no_rooms" : "auto_disabled"
1727
1703
  });
1728
1704
  }
1729
- if (onCleanupRef.current) onCleanupRef.current();
1705
+ onCleanupRef.current?.();
1730
1706
  emitDebug({
1731
1707
  type: "lifecycle",
1732
1708
  phase: "effect_cleanup",
1733
- event,
1734
1709
  rooms: normalizedRooms,
1735
1710
  autoJoin,
1736
1711
  autoLeave
@@ -1738,16 +1713,78 @@ function useSocketConnection(args) {
1738
1713
  };
1739
1714
  }, [
1740
1715
  client,
1741
- event,
1716
+ enabled,
1742
1717
  autoJoin,
1743
1718
  autoLeave,
1744
- emitDebug,
1745
- reportAsyncError,
1746
1719
  normalizedRoomsKey,
1747
1720
  joinMetaKey,
1748
- leaveMetaKey
1721
+ leaveMetaKey,
1722
+ emitDebug
1749
1723
  ]);
1750
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
+ }
1751
1788
 
1752
1789
  // src/sockets/socket.client.context.debug.ts
1753
1790
  function dbg(dbgOpts, e) {
@@ -2669,7 +2706,9 @@ function buildSocketProvider(args) {
2669
2706
  }
2670
2707
  ),
2671
2708
  useSocketClient: () => useSocketClient(),
2672
- useSocketConnection: (p) => useSocketConnection(p)
2709
+ useSocketRooms: (p) => useSocketRooms(p),
2710
+ useSocketEvent: (p) => useSocketEvent(p),
2711
+ useSocketConnection: (...args2) => useSocketConnection(...args2)
2673
2712
  };
2674
2713
  }
2675
2714
  function SocketProvider(props) {
@@ -2884,7 +2923,7 @@ function trackHookTrigger2({
2884
2923
  }
2885
2924
 
2886
2925
  // src/sockets/socketedRoute/socket.client.helper.rooms.ts
2887
- function normalizeRooms(rooms) {
2926
+ function normalizeRooms2(rooms) {
2888
2927
  if (rooms == null) return [];
2889
2928
  const list = Array.isArray(rooms) ? rooms : [rooms];
2890
2929
  const seen = /* @__PURE__ */ new Set();
@@ -2909,7 +2948,7 @@ function roomStateEqual(prev, next) {
2909
2948
  }
2910
2949
  function mergeRoomState(prev, toRoomsResult) {
2911
2950
  const merged = new Set(prev.rooms);
2912
- for (const r of normalizeRooms(toRoomsResult.rooms)) merged.add(r);
2951
+ for (const r of normalizeRooms2(toRoomsResult.rooms)) merged.add(r);
2913
2952
  return {
2914
2953
  rooms: Array.from(merged),
2915
2954
  joinMeta: toRoomsResult.joinMeta ?? prev.joinMeta,
@@ -3270,6 +3309,8 @@ export {
3270
3309
  createRouteClient,
3271
3310
  defaultFetcher,
3272
3311
  useSocketClient,
3273
- useSocketConnection
3312
+ useSocketConnection,
3313
+ useSocketEvent,
3314
+ useSocketRooms
3274
3315
  };
3275
3316
  //# sourceMappingURL=index.mjs.map