169 lines
4.9 KiB
JavaScript
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 };
|
|
};
|