{"version":3,"file":"use-ws-CpH04vIA.cjs","names":["WebSocket"],"sources":["../src/use-handlers.ts","../src/use-socket.ts","../src/use-ws.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\nimport type WebSocket from \"./ws\";\n\nexport type EventHandlerOptions = {\n  onOpen?: (event: WebSocketEventMap[\"open\"]) => void;\n  onMessage?: (event: WebSocketEventMap[\"message\"]) => void;\n  onClose?: (event: WebSocketEventMap[\"close\"]) => void;\n  onError?: (event: WebSocketEventMap[\"error\"]) => void;\n};\n\n/** Attaches event handlers to a WebSocket in a React Lifecycle-friendly way */\nexport const useAttachWebSocketEventHandlers = (\n  socket: WebSocket,\n  options: EventHandlerOptions\n) => {\n  const handlersRef = useRef(options);\n  handlersRef.current = options;\n\n  useEffect(() => {\n    const onOpen: EventHandlerOptions[\"onOpen\"] = (event) =>\n      handlersRef.current?.onOpen?.(event);\n    const onMessage: EventHandlerOptions[\"onMessage\"] = (event) =>\n      handlersRef.current?.onMessage?.(event);\n    const onClose: EventHandlerOptions[\"onClose\"] = (event) =>\n      handlersRef.current?.onClose?.(event);\n    const onError: EventHandlerOptions[\"onError\"] = (event) =>\n      handlersRef.current?.onError?.(event);\n\n    socket.addEventListener(\"open\", onOpen);\n    socket.addEventListener(\"close\", onClose);\n    socket.addEventListener(\"error\", onError);\n    socket.addEventListener(\"message\", onMessage);\n\n    return () => {\n      socket.removeEventListener(\"open\", onOpen);\n      socket.removeEventListener(\"close\", onClose);\n      socket.removeEventListener(\"error\", onError);\n      socket.removeEventListener(\"message\", onMessage);\n    };\n  }, [socket]);\n};\n","import { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type WebSocket from \"./ws\";\nimport type { Options } from \"./ws\";\n\nexport type SocketOptions = Options & {\n  /** Whether the socket should be connected. Defaults to true. */\n  enabled?: boolean;\n};\n\n/** When any of the option values are changed, we should reinitialize the socket */\nexport const getOptionsThatShouldCauseRestartWhenChanged = (\n  options: SocketOptions\n) => [\n  // Note: enabled is handled separately to avoid creating a new socket on toggle\n  options.startClosed,\n  options.minUptime,\n  options.maxRetries,\n  options.connectionTimeout,\n  options.maxEnqueuedMessages,\n  options.maxReconnectionDelay,\n  options.minReconnectionDelay,\n  options.reconnectionDelayGrowFactor,\n  options.debug\n];\n\n/**\n * Initializes a PartySocket (or WebSocket) and keeps it stable across renders,\n * but reconnects and updates the reference when any of the connection args change.\n */\nexport function useStableSocket<\n  T extends WebSocket,\n  TOpts extends SocketOptions\n>({\n  options,\n  createSocket,\n  createSocketMemoKey: createOptionsMemoKey\n}: {\n  options: TOpts;\n  createSocket: (options: TOpts) => T;\n  createSocketMemoKey: (options: TOpts) => string;\n}) {\n  // extract enabled with default value of true\n  const { enabled = true } = options;\n\n  // Returns a stable reference to options, only updating when the serialized\n  // key changes. This avoids reconnecting on every render when callers pass\n  // an inline options object (new reference each time) whose values haven't\n  // actually changed.\n  const shouldReconnect = createOptionsMemoKey(options);\n\n  const socketOptions = useMemo(() => {\n    return options;\n    // oxlint-disable-next-line react-hooks/exhaustive-deps -- shouldReconnect is a serialized key derived from options — we intentionally memo on the key, not the object reference\n  }, [shouldReconnect]);\n\n  // this is the socket we return\n  const [socket, setSocket] = useState<T>(() =>\n    // only connect on first mount\n    createSocket({ ...socketOptions, startClosed: true })\n  );\n\n  // keep track of the socket we initialized\n  const socketInitializedRef = useRef<T | null>(null);\n\n  // allow changing the socket factory without reconnecting\n  const createSocketRef = useRef(createSocket);\n  createSocketRef.current = createSocket;\n\n  // track the previous enabled state to detect changes\n  const prevEnabledRef = useRef(enabled);\n\n  // track the previous socketOptions reference to distinguish option changes\n  // from HMR/StrictMode effect re-runs. useMemo returns the same reference\n  // when the memo key hasn't changed, so referential equality tells us\n  // whether the connection options actually changed.\n  const prevSocketOptionsRef = useRef(socketOptions);\n\n  // tracks whether options changed at any point while the socket was disabled.\n  // The disabled path early-returns without creating a new socket, so we need\n  // to remember that options drifted and create a new socket on re-enable.\n  const optionsChangedWhileDisabledRef = useRef(false);\n\n  // finally, initialize the socket\n  useEffect(() => {\n    const optionsChanged = prevSocketOptionsRef.current !== socketOptions;\n    prevSocketOptionsRef.current = socketOptions;\n\n    // if disabled, close the socket and don't proceed with connection logic\n    if (!enabled) {\n      socket.close();\n      prevEnabledRef.current = enabled;\n      if (optionsChanged) {\n        optionsChangedWhileDisabledRef.current = true;\n      }\n      return () => {\n        socket.close();\n      };\n    }\n\n    // if enabled just changed from false to true...\n    if (!prevEnabledRef.current && enabled) {\n      prevEnabledRef.current = enabled;\n      const needsNewSocket =\n        optionsChanged || optionsChangedWhileDisabledRef.current;\n      optionsChangedWhileDisabledRef.current = false;\n\n      if (!needsNewSocket) {\n        // options unchanged — reconnect existing socket\n        socket.reconnect();\n        return () => {\n          socket.close();\n        };\n      }\n\n      // options changed while disabled — create new socket with current config\n      const newSocket = createSocketRef.current({\n        ...socketOptions,\n        startClosed: true\n      });\n      setSocket(newSocket);\n      return () => {\n        newSocket.close();\n      };\n    }\n\n    prevEnabledRef.current = enabled;\n\n    // we haven't yet restarted the socket\n    if (socketInitializedRef.current === socket) {\n      if (optionsChanged) {\n        // connection options changed — create new socket with new config.\n        // startClosed: true so it's inert until the else branch below\n        // connects it on the next render. This ensures the socket is safe\n        // to clean up if the component unmounts before that re-render.\n        const newSocket = createSocketRef.current({\n          ...socketOptions,\n          startClosed: true\n        });\n\n        // update socket reference (this will cause the effect to run again)\n        setSocket(newSocket);\n        return () => {\n          newSocket.close();\n        };\n      } else {\n        // HMR or React Strict Mode effect re-run — reconnect the existing\n        // socket instead of creating a new instance. This preserves the\n        // socket identity (event listeners, _pk, etc.) across Hot Module\n        // Replacement, preventing downstream code from losing its reference\n        // to the live socket.\n        if (socketOptions.startClosed !== true) {\n          socket.reconnect();\n        }\n        return () => {\n          socket.close();\n        };\n      }\n    } else {\n      if (!socketInitializedRef.current) {\n        // first mount — respect the caller's startClosed preference\n        if (socketOptions.startClosed !== true) {\n          socket.reconnect();\n        }\n      } else if (socketInitializedRef.current !== socket) {\n        // replacement socket from an options change — always connect\n        socket.reconnect();\n      }\n      // track initialized socket so we know not to do it again\n      socketInitializedRef.current = socket;\n      // close the old socket the next time the socket changes or we unmount\n      return () => {\n        socket.close();\n      };\n    }\n  }, [socket, socketOptions, enabled]);\n\n  return socket;\n}\n","import { useAttachWebSocketEventHandlers } from \"./use-handlers\";\nimport {\n  getOptionsThatShouldCauseRestartWhenChanged,\n  useStableSocket\n} from \"./use-socket\";\nimport WebSocket from \"./ws\";\n\nimport type { EventHandlerOptions } from \"./use-handlers\";\nimport type { SocketOptions } from \"./use-socket\";\nimport type { ProtocolsProvider, UrlProvider } from \"./ws\";\n\ntype UseWebSocketOptions = SocketOptions & EventHandlerOptions;\n\n// A React hook that wraps PartySocket\nexport default function useWebSocket(\n  url: UrlProvider,\n  protocols?: ProtocolsProvider,\n  options: UseWebSocketOptions = {}\n) {\n  const socket = useStableSocket({\n    options,\n    createSocket: (options) => new WebSocket(url, protocols, options),\n    createSocketMemoKey: (options) =>\n      JSON.stringify([\n        // will reconnect if url or protocols are specified as a string.\n        // if they are functions, the WebSocket will handle reconnection\n        url,\n        protocols,\n        ...getOptionsThatShouldCauseRestartWhenChanged(options)\n      ])\n  });\n\n  useAttachWebSocketEventHandlers(socket, options);\n\n  return socket;\n}\n"],"mappings":"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"}