import { useEffect, useRef, useState } from 'react'; const WS_URL = "wss://www.thirukalyanam.amrithaa.net/backend/reverb/app/xk30gjh2ggmel5szmm5w?protocol=7&client=js&version=1.0"; // SINGLETON state to share across all components let globalSocket = null; let globalMessages = []; let globalIsConnected = false; let globalActiveChannels = new Set(); let subscribers = new Set(); let reconnectTimer = null; let heartbeatTimer = null; const notifySubscribers = () => { subscribers.forEach(callback => callback({ messages: [...globalMessages], isConnected: globalIsConnected })); }; const connect = () => { if (globalSocket?.readyState === WebSocket.OPEN) return; if (globalSocket?.readyState === WebSocket.CONNECTING) return; console.log("[WS] Connecting to:", WS_URL); const socket = new WebSocket(WS_URL); globalSocket = socket; socket.onopen = () => { console.log("[WS] Open - Waiting for handshake..."); }; socket.onmessage = (event) => { try { const data = JSON.parse(event.data); console.log("[WS] RAW:", data); // 1. Handshake if (data.event === "pusher:connection_established") { console.log("[WS] Handshake Complete | Socket ID:", data.data ? JSON.parse(data.data).socket_id : "N/A"); globalIsConnected = true; // Auto-subscribe to all pending channels if (globalActiveChannels.size > 0) { console.log(`[WS] Re-subscribing to ${globalActiveChannels.size} channels...`); globalActiveChannels.forEach(channel => { socket.send(JSON.stringify({ event: "pusher:subscribe", data: { channel } })); console.log(`[WS] Subscribing to: ${channel}`); }); } notifySubscribers(); return; } // 2. Heartbeat if (data.event === "pusher:ping") { socket.send(JSON.stringify({ event: "pusher:pong", data: {} })); return; } if (data.event === "pusher:pong") return; // 3. Subscription Succeeded if (data.event === "pusher_internal:subscription_succeeded") { console.log(`[WS] ✅ Subscribed to ${data.channel}`); return; } // 4. Subscription Error if (data.event === "pusher:subscription_error") { console.error(`[WS] ❌ Subscription failed for ${data.channel}`, data.data); return; } // 5. Regular Events console.log(`[WS] 📩 Event: ${data.event} | Channel: ${data.channel}`); globalMessages = [...globalMessages, data]; notifySubscribers(); } catch (err) { console.error("[WS] Parse Error:", err, event.data); } }; socket.onclose = (event) => { console.log(`[WS] Disconnected (Code: ${event.code}) - Reconnecting in 5s...`); globalIsConnected = false; notifySubscribers(); if (reconnectTimer) clearTimeout(reconnectTimer); reconnectTimer = setTimeout(connect, 5000); }; socket.onerror = (err) => { console.error("[WS] Error:", err); socket.close(); }; // Heartbeat if (heartbeatTimer) clearInterval(heartbeatTimer); heartbeatTimer = setInterval(() => { if (socket.readyState === WebSocket.OPEN) { socket.send(JSON.stringify({ event: "pusher:ping", data: {} })); } }, 20000); }; export const useWebSocket = (channels = []) => { const [state, setState] = useState({ messages: globalMessages, isConnected: globalIsConnected }); useEffect(() => { // Add this component's listener const callback = (newState) => setState(newState); subscribers.add(callback); // Initialize connection if needed if (!globalSocket) { connect(); } return () => { subscribers.delete(callback); }; }, []); // Handle Dynamic Subscriptions useEffect(() => { if (!channels || channels.length === 0) return; channels.forEach(channel => { if (channel && !globalActiveChannels.has(channel)) { globalActiveChannels.add(channel); if (globalIsConnected && globalSocket?.readyState === WebSocket.OPEN) { globalSocket.send(JSON.stringify({ event: "pusher:subscribe", data: { channel } })); console.log(`[WS] Dynamic Subscribe: ${channel}`); } } }); // Optional: Cleanup old channels if they are no longer in the provided array // But for a chat app, we often want to keep listening to notification channels. // So we'll leave them for now unless we implement a more complex cleanup. }, [channels]); const unsubscribe = (channel) => { if (channel && globalActiveChannels.has(channel)) { globalActiveChannels.delete(channel); if (globalIsConnected && globalSocket?.readyState === WebSocket.OPEN) { globalSocket.send(JSON.stringify({ event: "pusher:unsubscribe", data: { channel } })); console.log(`[WS] Unsubscribed from: ${channel}`); } } }; return { ...state, unsubscribe }; };