react-native-devtools-sync
Version:
A tool for syncing React Query state to an external Dev Tools
177 lines • 7.61 kB
JavaScript
import { useEffect, useRef, useState } from "react";
import { io as socketIO } from "socket.io-client";
import { getPlatformSpecificURL } from "./platformUtils";
import { log } from "./utils/logger";
/**
* 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, platform, enableLogs = 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}]`;
// Define event handlers at function root level to satisfy linter
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");
};
// 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;
// Get the platform-specific URL
const platformUrl = getPlatformSpecificURL(socketURL, platform);
currentSocketURL = platformUrl;
log(`${logPrefix} Platform: ${platform}, using URL: ${platformUrl}`, enableLogs);
try {
// Use existing global socket or create a new one
if (!globalSocketInstance) {
log(`${logPrefix} Creating new socket instance to ${platformUrl}`, enableLogs);
globalSocketInstance = socketIO(platformUrl, {
autoConnect: true,
query: Object.assign({ deviceName, deviceId: persistentDeviceId, platform }, (extraDeviceInfo && Object.keys(extraDeviceInfo).length > 0
? { extraDeviceInfo: JSON.stringify(extraDeviceInfo) }
: {})),
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");
}
}, [persistentDeviceId]);
// Update the socket query parameters when deviceName changes
useEffect(() => {
if (socketRef.current &&
socketRef.current.io.opts.query &&
persistentDeviceId) {
log(`${logPrefix} Updating device name in socket connection`, enableLogs);
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);
// 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,
},
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,
]);
/**
* 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