Files
thirukalyanamweb/src/hooks/useWebSocket.js
T

169 lines
4.9 KiB
JavaScript

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 };
};