partysocket
Version:
A better WebSocket that Just Works™
150 lines (148 loc) • 4.8 kB
JavaScript
import ReconnectingWebSocket from "./ws.js";
import { useEffect, useMemo, useRef, useState } from "react";
//#region src/use-handlers.ts
/** Attaches event handlers to a WebSocket in a React Lifecycle-friendly way */
const useAttachWebSocketEventHandlers = (socket, options) => {
const handlersRef = useRef(options);
handlersRef.current = options;
useEffect(() => {
const onOpen = (event) => handlersRef.current?.onOpen?.(event);
const onMessage = (event) => handlersRef.current?.onMessage?.(event);
const onClose = (event) => handlersRef.current?.onClose?.(event);
const onError = (event) => handlersRef.current?.onError?.(event);
socket.addEventListener("open", onOpen);
socket.addEventListener("close", onClose);
socket.addEventListener("error", onError);
socket.addEventListener("message", onMessage);
return () => {
socket.removeEventListener("open", onOpen);
socket.removeEventListener("close", onClose);
socket.removeEventListener("error", onError);
socket.removeEventListener("message", onMessage);
};
}, [socket]);
};
//#endregion
//#region src/use-socket.ts
/** When any of the option values are changed, we should reinitialize the socket */
const getOptionsThatShouldCauseRestartWhenChanged = (options) => [
options.startClosed,
options.minUptime,
options.maxRetries,
options.connectionTimeout,
options.maxEnqueuedMessages,
options.maxReconnectionDelay,
options.minReconnectionDelay,
options.reconnectionDelayGrowFactor,
options.debug
];
/**
* Initializes a PartySocket (or WebSocket) and keeps it stable across renders,
* but reconnects and updates the reference when any of the connection args change.
*/
function useStableSocket({
options,
createSocket,
createSocketMemoKey: createOptionsMemoKey
}) {
const { enabled = true } = options;
const socketOptions = useMemo(() => {
return options;
}, [createOptionsMemoKey(options)]);
const [socket, setSocket] = useState(() =>
createSocket({
...socketOptions,
startClosed: true
})
);
const socketInitializedRef = useRef(null);
const createSocketRef = useRef(createSocket);
createSocketRef.current = createSocket;
const prevEnabledRef = useRef(enabled);
const prevSocketOptionsRef = useRef(socketOptions);
const optionsChangedWhileDisabledRef = useRef(false);
useEffect(() => {
const optionsChanged = prevSocketOptionsRef.current !== socketOptions;
prevSocketOptionsRef.current = socketOptions;
if (!enabled) {
socket.close();
prevEnabledRef.current = enabled;
if (optionsChanged) optionsChangedWhileDisabledRef.current = true;
return () => {
socket.close();
};
}
if (!prevEnabledRef.current && enabled) {
prevEnabledRef.current = enabled;
const needsNewSocket =
optionsChanged || optionsChangedWhileDisabledRef.current;
optionsChangedWhileDisabledRef.current = false;
if (!needsNewSocket) {
socket.reconnect();
return () => {
socket.close();
};
}
const newSocket = createSocketRef.current({
...socketOptions,
startClosed: true
});
setSocket(newSocket);
return () => {
newSocket.close();
};
}
prevEnabledRef.current = enabled;
if (socketInitializedRef.current === socket)
if (optionsChanged) {
const newSocket = createSocketRef.current({
...socketOptions,
startClosed: true
});
setSocket(newSocket);
return () => {
newSocket.close();
};
} else {
if (socketOptions.startClosed !== true) socket.reconnect();
return () => {
socket.close();
};
}
else {
if (!socketInitializedRef.current) {
if (socketOptions.startClosed !== true) socket.reconnect();
} else if (socketInitializedRef.current !== socket) socket.reconnect();
socketInitializedRef.current = socket;
return () => {
socket.close();
};
}
}, [socket, socketOptions, enabled]);
return socket;
}
//#endregion
//#region src/use-ws.ts
function useWebSocket(url, protocols, options = {}) {
const socket = useStableSocket({
options,
createSocket: (options) =>
new ReconnectingWebSocket(url, protocols, options),
createSocketMemoKey: (options) =>
JSON.stringify([
url,
protocols,
...getOptionsThatShouldCauseRestartWhenChanged(options)
])
});
useAttachWebSocketEventHandlers(socket, options);
return socket;
}
//#endregion
export {
useAttachWebSocketEventHandlers as i,
getOptionsThatShouldCauseRestartWhenChanged as n,
useStableSocket as r,
useWebSocket as t
};
//# sourceMappingURL=use-ws-dP3zNXFn.js.map