UNPKG

react-query-external-sync

Version:

A tool for syncing React Query state to an external Dev Tools

171 lines 7.7 kB
import { useEffect, useRef, useState } from 'react'; import { io as socketIO } from 'socket.io-client'; import { log } from './utils/logger'; import { getPlatformSpecificURL } from './platformUtils'; /** * Create a singleton socket instance that persists across component renders * This way multiple components can share the same socket connection */ let globalSocketInstance = null; let currentSocketURL = ''; /** * Hook that handles socket connection for device-dashboard communication * * Features: * - Singleton pattern for socket connection * - Platform-specific URL handling for iOS/Android/Web * - Device name identification * - Connection state tracking * - User list management */ export function useMySocket({ deviceName, socketURL, persistentDeviceId, extraDeviceInfo, envVariables, platform, enableLogs = false, isDevice = false }) { const socketRef = useRef(null); const [socket, setSocket] = useState(null); const [isConnected, setIsConnected] = useState(false); const initialized = useRef(false); // For logging clarity const logPrefix = `[${deviceName}]`; // Main socket initialization - runs only once useEffect(() => { // Wait until we have a persistent device ID if (!persistentDeviceId) { return; } // Only initialize socket once to prevent multiple connections if (initialized.current) { return; } initialized.current = true; // Define event handlers inside useEffect to avoid dependency issues const onConnect = () => { log(`${logPrefix} Socket connected successfully`, enableLogs); setIsConnected(true); }; const onDisconnect = (reason) => { log(`${logPrefix} Socket disconnected. Reason: ${reason}`, enableLogs); setIsConnected(false); }; const onConnectError = (error) => { log(`${logPrefix} Socket connection error: ${error.message}`, enableLogs, 'error'); }; const onConnectTimeout = () => { log(`${logPrefix} Socket connection timeout`, enableLogs, 'error'); }; // Get the platform-specific URL const platformUrl = getPlatformSpecificURL(socketURL, platform, isDevice); currentSocketURL = platformUrl; try { // Use existing global socket or create a new one if (!globalSocketInstance) { globalSocketInstance = socketIO(platformUrl, { autoConnect: true, query: { deviceName, deviceId: persistentDeviceId, platform, extraDeviceInfo: JSON.stringify(extraDeviceInfo), envVariables: JSON.stringify(envVariables), }, reconnection: false, transports: ['websocket'], // Prefer websocket transport for React Native }); } else { log(`${logPrefix} Reusing existing socket instance to ${platformUrl}`, enableLogs); } socketRef.current = globalSocketInstance; setSocket(socketRef.current); // Setup error event listener socketRef.current.on('connect_error', onConnectError); socketRef.current.on('connect_timeout', onConnectTimeout); // Check initial connection state if (socketRef.current.connected) { setIsConnected(true); log(`${logPrefix} Socket already connected on init`, enableLogs); } // Set up event handlers socketRef.current.on('connect', onConnect); socketRef.current.on('disconnect', onDisconnect); // Clean up event listeners on unmount but don't disconnect return () => { if (socketRef.current) { log(`${logPrefix} Cleaning up socket event listeners`, enableLogs); socketRef.current.off('connect', onConnect); socketRef.current.off('disconnect', onDisconnect); socketRef.current.off('connect_error', onConnectError); socketRef.current.off('connect_timeout', onConnectTimeout); // Don't disconnect socket on component unmount // We want it to remain connected for the app's lifetime } }; } catch (error) { log(`${logPrefix} Failed to initialize socket: ${error}`, enableLogs, 'error'); } // ## DON'T ADD ANYTHING ELSE TO THE DEPENDENCY ARRAY ### // eslint-disable-next-line react-hooks/exhaustive-deps }, [persistentDeviceId]); // Update the socket query parameters when deviceName changes useEffect(() => { if (socketRef.current && socketRef.current.io.opts.query && persistentDeviceId) { socketRef.current.io.opts.query = Object.assign(Object.assign({}, socketRef.current.io.opts.query), { deviceName, deviceId: persistentDeviceId, platform }); } }, [deviceName, logPrefix, persistentDeviceId, platform, enableLogs]); // Update the socket URL when socketURL changes useEffect(() => { // Get platform-specific URL for the new socketURL const platformUrl = getPlatformSpecificURL(socketURL, platform, isDevice); // Compare with last known URL to avoid direct property access if (socketRef.current && currentSocketURL !== platformUrl && persistentDeviceId) { log(`${logPrefix} Socket URL changed from ${currentSocketURL} to ${platformUrl}`, enableLogs); try { // Only recreate socket if URL actually changed socketRef.current.disconnect(); currentSocketURL = platformUrl; log(`${logPrefix} Creating new socket connection to ${platformUrl}`, enableLogs); globalSocketInstance = socketIO(platformUrl, { autoConnect: true, query: { deviceName, deviceId: persistentDeviceId, platform, extraDeviceInfo: JSON.stringify(extraDeviceInfo), envVariables: JSON.stringify(envVariables), }, reconnection: false, transports: ['websocket'], // Prefer websocket transport for React Native }); socketRef.current = globalSocketInstance; setSocket(socketRef.current); } catch (error) { log(`${logPrefix} Failed to update socket connection: ${error}`, enableLogs, 'error'); } } }, [socketURL, deviceName, logPrefix, persistentDeviceId, platform, enableLogs, extraDeviceInfo, envVariables]); /** * Manually connect to the socket server */ function connect() { if (socketRef.current && !socketRef.current.connected) { log(`${logPrefix} Manually connecting to socket server`, enableLogs); socketRef.current.connect(); } } /** * Manually disconnect from the socket server */ function disconnect() { if (socketRef.current && socketRef.current.connected) { log(`${logPrefix} Manually disconnecting from socket server`, enableLogs); socketRef.current.disconnect(); } } return { socket, connect, disconnect, isConnected, }; } //# sourceMappingURL=useMySocket.js.map