partysocket
Version:
A better WebSocket that Just Works™
1 lines • 9.51 kB
Source Map (JSON)
{"version":3,"file":"use-ws-BBm1tFJe.cjs","names":["onOpen: EventHandlerOptions[\"onOpen\"]","onMessage: EventHandlerOptions[\"onMessage\"]","onClose: EventHandlerOptions[\"onClose\"]","onError: EventHandlerOptions[\"onError\"]","WebSocket","options"],"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 // ensure we only reconnect when necessary\n const shouldReconnect = createOptionsMemoKey(options);\n const socketOptions = useMemo(() => {\n return options;\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 // 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 return;\n }\n\n // if enabled just changed from false to true, reconnect\n if (!prevEnabledRef.current && enabled) {\n socket.reconnect();\n prevEnabledRef.current = enabled;\n return;\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 const newSocket = createSocketRef.current({\n ...socketOptions,\n // when reconnecting because of options change, we always reconnect\n // (startClosed only applies to initial mount)\n startClosed: false\n });\n\n // update socket reference (this will cause the effect to run again)\n setSocket(newSocket);\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 this is the first time we are running the hook, connect...\n if (!socketInitializedRef.current && socketOptions.startClosed !== true) {\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":";;;;;AAYA,MAAa,mCACX,QACA,YACG;CACH,MAAM,gCAAqB,QAAQ;AACnC,aAAY,UAAU;AAEtB,4BAAgB;EACd,MAAMA,UAAyC,UAC7C,YAAY,SAAS,SAAS,MAAM;EACtC,MAAMC,aAA+C,UACnD,YAAY,SAAS,YAAY,MAAM;EACzC,MAAMC,WAA2C,UAC/C,YAAY,SAAS,UAAU,MAAM;EACvC,MAAMC,WAA2C,UAC/C,YAAY,SAAS,UAAU,MAAM;AAEvC,SAAO,iBAAiB,QAAQ,OAAO;AACvC,SAAO,iBAAiB,SAAS,QAAQ;AACzC,SAAO,iBAAiB,SAAS,QAAQ;AACzC,SAAO,iBAAiB,WAAW,UAAU;AAE7C,eAAa;AACX,UAAO,oBAAoB,QAAQ,OAAO;AAC1C,UAAO,oBAAoB,SAAS,QAAQ;AAC5C,UAAO,oBAAoB,SAAS,QAAQ;AAC5C,UAAO,oBAAoB,WAAW,UAAU;;IAEjD,CAAC,OAAO,CAAC;;;;;;AC7Bd,MAAa,+CACX,YACG;CAEH,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACT;;;;;AAMD,SAAgB,gBAGd,EACA,SACA,cACA,qBAAqB,wBAKpB;CAED,MAAM,EAAE,UAAU,SAAS;CAI3B,MAAM,yCAA8B;AAClC,SAAO;IACN,CAHqB,qBAAqB,QAAQ,CAGjC,CAAC;CAGrB,MAAM,CAAC,QAAQ,uCAEb,aAAa;EAAE,GAAG;EAAe,aAAa;EAAM,CAAC,CACtD;CAGD,MAAM,yCAAwC,KAAK;CAGnD,MAAM,oCAAyB,aAAa;AAC5C,iBAAgB,UAAU;CAG1B,MAAM,mCAAwB,QAAQ;CAMtC,MAAM,yCAA8B,cAAc;AAGlD,4BAAgB;EACd,MAAM,iBAAiB,qBAAqB,YAAY;AACxD,uBAAqB,UAAU;AAG/B,MAAI,CAAC,SAAS;AACZ,UAAO,OAAO;AACd,kBAAe,UAAU;AACzB;;AAIF,MAAI,CAAC,eAAe,WAAW,SAAS;AACtC,UAAO,WAAW;AAClB,kBAAe,UAAU;AACzB;;AAGF,iBAAe,UAAU;AAGzB,MAAI,qBAAqB,YAAY,OACnC,KAAI,eAUF,WARkB,gBAAgB,QAAQ;GACxC,GAAG;GAGH,aAAa;GACd,CAAC,CAGkB;OACf;AAML,OAAI,cAAc,gBAAgB,KAChC,QAAO,WAAW;AAEpB,gBAAa;AACX,WAAO,OAAO;;;OAGb;AAEL,OAAI,CAAC,qBAAqB,WAAW,cAAc,gBAAgB,KACjE,QAAO,WAAW;AAGpB,wBAAqB,UAAU;AAE/B,gBAAa;AACX,WAAO,OAAO;;;IAGjB;EAAC;EAAQ;EAAe;EAAQ,CAAC;AAEpC,QAAO;;;;;ACxHT,SAAwB,aACtB,KACA,WACA,UAA+B,EAAE,EACjC;CACA,MAAM,SAAS,gBAAgB;EAC7B;EACA,eAAe,cAAY,IAAIC,iCAAU,KAAK,WAAWC,UAAQ;EACjE,sBAAsB,cACpB,KAAK,UAAU;GAGb;GACA;GACA,GAAG,4CAA4CA,UAAQ;GACxD,CAAC;EACL,CAAC;AAEF,iCAAgC,QAAQ,QAAQ;AAEhD,QAAO"}