UNPKG

partysocket

Version:
1 lines 11.6 kB
{"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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"}