![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/public_html/src/context/ |
import React, { createContext, useContext, useEffect, useRef, useState, ReactNode, useCallback } from 'react';
import { useSession } from 'next-auth/react';
import { performanceMonitor } from '@/utils/performance';
// Type declarations for browser APIs used in incognito detection
declare global {
interface Window {
chrome?: {
app?: any;
runtime?: any;
};
webkitRequestFileSystem?: (type: number, size: number, successCallback: () => void, errorCallback: () => void) => void;
}
}
interface DirectMessageNotification {
senderId: string;
senderName: string;
lastMessage: string;
timestamp: number;
unreadCount: number;
}
interface IncomingVideoCall {
senderId: string;
senderName: string;
signal: any;
timestamp: number;
}
interface WebSocketContextValue {
ws: WebSocket | null;
connected: boolean;
connectionState: 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
// Message handling
sendMessage: (type: string, data: any) => void;
// Enhanced features
sendTyping: (roomId: string, isTyping: boolean) => void;
joinRoom: (roomId: string) => Promise<void>;
leaveRoom: (roomId: string) => Promise<void>;
// Direct message notifications
directMessageNotifications: Map<string, DirectMessageNotification>;
markDirectMessagesAsRead: (senderId: string) => void;
getTotalUnreadDirectMessages: () => number;
// Video call notifications
incomingVideoCall: IncomingVideoCall | null;
acceptVideoCall: (callData: IncomingVideoCall) => void;
declineVideoCall: () => void;
// Manual controls
reconnect: () => void;
disconnect: () => void;
// Connection stats
connectionStats: {
reconnectAttempts: number;
lastConnected: number | null;
messagesSent: number;
messagesReceived: number;
};
// โ
ADD SIMPLE VIDEO CALL STATE
videoCallActive: boolean;
startVideoCall: (recipientId: string, recipientName: string) => void;
endVideoCall: () => void;
currentVideoCall: {
recipientId: string;
recipientName: string;
isInitiator: boolean;
} | null;
}
const WebSocketContext = createContext<WebSocketContextValue>({
ws: null,
connected: false,
connectionState: 'disconnected',
sendMessage: () => {},
sendTyping: () => {},
joinRoom: async () => {},
leaveRoom: async () => {},
directMessageNotifications: new Map(),
markDirectMessagesAsRead: () => {},
getTotalUnreadDirectMessages: () => 0,
incomingVideoCall: null,
acceptVideoCall: () => {},
declineVideoCall: () => {},
reconnect: () => {},
disconnect: () => {},
connectionStats: {
reconnectAttempts: 0,
lastConnected: null,
messagesSent: 0,
messagesReceived: 0,
},
videoCallActive: false,
startVideoCall: () => {},
endVideoCall: () => {},
currentVideoCall: null,
});
export const WebSocketProvider = ({ children }: { children: ReactNode }) => {
const { data: session, status } = useSession();
// WebSocket connection
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const [connected, setConnected] = useState(false);
const [connectionState, setConnectionState] = useState<'disconnected' | 'connecting' | 'connected' | 'reconnecting'>('disconnected');
const [wsInstance, setWsInstance] = useState<WebSocket | null>(null);
// Direct message notifications
const [directMessageNotifications, setDirectMessageNotifications] = useState<Map<string, DirectMessageNotification>>(new Map());
// Video call notifications
const [incomingVideoCall, setIncomingVideoCall] = useState<IncomingVideoCall | null>(null);
// โ
SIMPLE VIDEO CALL STATE - NO MORE COMPLEX SHIT
const [videoCallActive, setVideoCallActive] = useState(false);
const [currentVideoCall, setCurrentVideoCall] = useState<{
recipientId: string;
recipientName: string;
isInitiator: boolean;
} | null>(null);
// Connection state tracking with refs to avoid stale closures
const connectionId = useRef<string | null>(null);
const isIntentionalDisconnect = useRef(false);
const lastSessionId = useRef<string | null>(null);
const reconnectAttempts = useRef(0);
// Connection stats
const [connectionStats, setConnectionStats] = useState({
reconnectAttempts: 0,
lastConnected: null as number | null,
messagesSent: 0,
messagesReceived: 0,
});
// SECURITY: Rate limiting for message handling
const messageRateLimit = useRef({
count: 0,
lastReset: Date.now(),
maxMessages: 100, // Max 100 messages per minute
windowMs: 60000 // 1 minute window
});
// SECURITY: Input validation and sanitization
const validateMessage = (message: any): boolean => {
try {
// Check message structure
if (!message || typeof message !== 'object') {
console.warn('[WebSocket] ๐จ Invalid message structure');
return false;
}
// Check required fields
if (!message.type || typeof message.type !== 'string') {
console.warn('[WebSocket] ๐จ Missing or invalid message type');
return false;
}
// Validate message type (whitelist approach)
const allowedTypes = [
'pong', 'ping', 'DIRECT_MESSAGE', 'webrtc-offer', 'webrtc-answer',
'webrtc-ice-candidate', 'webrtc-end-call', 'webrtc-call-rejected',
'webrtc-call-accepted', 'PARTICIPANT_LIST_UPDATE', 'PRESENCE_UPDATE'
];
if (!allowedTypes.includes(message.type)) {
console.warn('[WebSocket] ๐จ Unauthorized message type:', message.type);
return false;
}
// Sanitize string fields to prevent XSS
if (message.data && typeof message.data === 'object') {
for (const key in message.data) {
if (typeof message.data[key] === 'string') {
// Basic XSS prevention - remove script tags and javascript: URLs
message.data[key] = message.data[key]
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/on\w+\s*=/gi, '');
}
}
}
return true;
} catch (error) {
console.error('[WebSocket] ๐จ Message validation error:', error);
return false;
}
};
// SECURITY: Rate limiting check
const checkRateLimit = (): boolean => {
const now = Date.now();
const rateLimit = messageRateLimit.current;
// Reset counter if window has passed
if (now - rateLimit.lastReset > rateLimit.windowMs) {
rateLimit.count = 0;
rateLimit.lastReset = now;
}
// Check if rate limit exceeded
if (rateLimit.count >= rateLimit.maxMessages) {
console.warn('[WebSocket] ๐จ Rate limit exceeded - blocking message');
return false;
}
rateLimit.count++;
return true;
};
// Generate unique connection ID
const generateConnectionId = () => {
return `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
// Mark direct messages as read
const markDirectMessagesAsRead = useCallback((senderId: string) => {
setDirectMessageNotifications(prev => {
const newMap = new Map(prev);
const notification = newMap.get(senderId);
if (notification) {
newMap.set(senderId, { ...notification, unreadCount: 0 });
}
return newMap;
});
}, []);
// Get total unread direct messages
const getTotalUnreadDirectMessages = useCallback(() => {
let total = 0;
directMessageNotifications.forEach(notification => {
total += notification.unreadCount;
});
return total;
}, [directMessageNotifications]);
// Video call functions
const acceptVideoCall = useCallback((callData: IncomingVideoCall) => {
console.log('[WebSocket] ๐ Video call accepted:', callData.senderName);
// Send acceptance signal to caller
if (wsRef.current?.readyState === WebSocket.OPEN) {
const acceptMessage = {
type: 'webrtc-call-accepted',
data: {
recipientId: callData.senderId,
signal: callData.signal // Forward the original signal
},
senderId: session?.user?.id,
senderName: session?.user?.name || 'Unknown User'
};
wsRef.current.send(JSON.stringify(acceptMessage));
console.log('[WebSocket] ๐ Sent call acceptance to:', callData.senderId);
}
// โ
SIMPLE: JUST START THE FUCKING VIDEO CALL
setVideoCallActive(true);
setCurrentVideoCall({
recipientId: callData.senderId,
recipientName: callData.senderName,
isInitiator: false // We're answering the call
});
// Clear the notification
setIncomingVideoCall(null);
}, [session?.user?.id, session?.user?.name]);
const declineVideoCall = useCallback(() => {
console.log('[WebSocket] ๐ Video call declined');
// Send rejection signal to caller
if (incomingVideoCall && wsRef.current?.readyState === WebSocket.OPEN) {
const rejectMessage = {
type: 'webrtc-call-rejected',
data: {
recipientId: incomingVideoCall.senderId,
reason: 'declined'
},
senderId: session?.user?.id,
senderName: session?.user?.name || 'Unknown User'
};
wsRef.current.send(JSON.stringify(rejectMessage));
console.log('[WebSocket] ๐ Sent call rejection to:', incomingVideoCall.senderId);
}
setIncomingVideoCall(null);
}, [incomingVideoCall, session?.user?.id, session?.user?.name]);
// โ
SIMPLE VIDEO CALL FUNCTIONS - NO BULLSHIT
const startVideoCall = useCallback((recipientId: string, recipientName: string) => {
console.log('๐ [SIMPLE] Starting video call with:', recipientName);
setVideoCallActive(true);
setCurrentVideoCall({
recipientId,
recipientName,
isInitiator: true
});
}, []);
const endVideoCall = useCallback(() => {
console.log('๐ [SIMPLE] Ending video call');
setVideoCallActive(false);
setCurrentVideoCall(null);
}, []);
// โ
SILENCED - No more annoying buzzing sounds!
const playIncomingCallSound = useCallback(() => {
console.log('๐ Sound disabled - no more buzzing!');
// Sound disabled to prevent annoying buzzing
}, []);
// Message handler
const handleMessage = useCallback((event: MessageEvent) => {
// SECURITY: Rate limiting
if (!checkRateLimit()) {
return;
}
try {
const message = JSON.parse(event.data);
// SECURITY: Input validation
if (!validateMessage(message)) {
console.warn('[WebSocket] ๐จ Message validation failed, ignoring message');
return;
}
setConnectionStats(prev => ({
...prev,
messagesReceived: prev.messagesReceived + 1,
}));
switch (message.type) {
case 'pong':
// Handle pong - connection is healthy
console.log('[WebSocket] ๐ Received pong from server - connection healthy');
break;
case 'ping':
// Respond to server ping immediately
console.log('[WebSocket] ๐ Received ping from server, sending pong');
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify({
type: 'pong',
timestamp: Date.now()
}));
}
break;
case 'DIRECT_MESSAGE':
// Handle direct message notifications
if (message.data && message.data.senderId && message.data.senderId !== session?.user?.id) {
console.log('[WebSocket] ๐ฌ Received direct message from:', message.data.sender?.name);
setDirectMessageNotifications(prev => {
const newMap = new Map(prev);
const senderId = message.data.senderId;
const existing = newMap.get(senderId);
newMap.set(senderId, {
senderId,
senderName: message.data.sender?.name || 'Unknown User',
lastMessage: message.data.content,
timestamp: Date.now(),
unreadCount: (existing?.unreadCount || 0) + 1,
});
return newMap;
});
// Show browser notification if permission granted
if (Notification.permission === 'granted') {
new Notification(`New message from ${message.data.sender?.name || 'Unknown User'}`, {
body: message.data.content,
icon: '/icons/apple-touch-icon-180x180.png'
});
}
}
break;
case 'webrtc-offer':
// Handle incoming video call
console.log('[WebSocket] ๐ Received video call offer from:', message.senderId);
if (message.senderId && message.senderId !== session?.user?.id) {
// Try to get the sender name from the message or use a fallback
const senderName = message.senderName || message.data?.senderName || `User ${message.senderId.slice(-4)}`;
// Only show notification if there isn't already one from the same sender
if (!incomingVideoCall || incomingVideoCall.senderId !== message.senderId) {
const callData: IncomingVideoCall = {
senderId: message.senderId,
senderName,
signal: message.data?.signal || message.signal,
timestamp: Date.now(),
};
setIncomingVideoCall(callData);
playIncomingCallSound();
// Show browser notification if permission granted
if (Notification.permission === 'granted') {
new Notification(`๐ Incoming Video Call`, {
body: `${senderName} is calling you`,
icon: '/icons/apple-touch-icon-180x180.png'
});
}
} else {
console.log('[WebSocket] ๐ Ignoring duplicate call from same sender');
}
}
break;
case 'webrtc-answer':
case 'webrtc-ice-candidate':
case 'webrtc-end-call':
// These are handled by individual DirectMessage components
console.log('[WebSocket] ๐ก WebRTC signaling:', message.type);
break;
case 'webrtc-call-rejected':
// Handle call rejection notification
if (message.senderId && message.senderId !== session?.user?.id) {
console.log('[WebSocket] ๐ Call was rejected by:', message.senderName);
// Dispatch event for DirectMessage components to handle
window.dispatchEvent(new CustomEvent('video-call-rejected', {
detail: {
senderId: message.senderId,
senderName: message.senderName,
reason: message.data?.reason || 'declined'
}
}));
}
break;
case 'webrtc-call-accepted':
// Handle call acceptance notification
if (message.senderId && message.senderId !== session?.user?.id) {
console.log('[WebSocket] ๐ Call was accepted by:', message.senderName);
// Dispatch event for DirectMessage components to handle
window.dispatchEvent(new CustomEvent('video-call-accepted', {
detail: {
senderId: message.senderId,
senderName: message.senderName,
signal: message.data?.signal
}
}));
}
break;
case 'webrtc-call-cancelled':
// โ
Handle call cancellation - STOP INCOMING NOTIFICATIONS
if (message.senderId && message.senderId !== session?.user?.id) {
console.log('[WebSocket] ๐ Call was cancelled by:', message.senderName, 'Reason:', message.data?.reason);
// Clear the incoming video call notification immediately
setIncomingVideoCall(null);
// Also dispatch event for DirectMessage components
window.dispatchEvent(new CustomEvent('video-call-cancelled', {
detail: {
senderId: message.senderId,
senderName: message.senderName,
reason: message.data?.reason || 'cancelled'
}
}));
}
break;
default:
// Dispatch to other components
window.dispatchEvent(new CustomEvent('websocket-message', { detail: message }));
break;
}
} catch (error) {
console.error('[WebSocket] โ Failed to parse message:', error);
}
}, [session?.user?.id, playIncomingCallSound]);
// Stable message sending
const sendMessage = useCallback((type: string, data: any) => {
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
console.warn('[WebSocket] Cannot send message - not connected');
return;
}
try {
const message = JSON.stringify({ type, data });
wsRef.current.send(message);
setConnectionStats(prev => ({
...prev,
messagesSent: prev.messagesSent + 1,
}));
console.log(`[WebSocket] ๐ค Sent: ${type}`, data);
} catch (error) {
console.error('[WebSocket] โ Failed to send message:', error);
}
}, []);
// Enhanced messaging functions
const sendTyping = useCallback((roomId: string, isTyping: boolean) => {
sendMessage('TYPING', { roomId, isTyping, timestamp: Date.now() });
}, [sendMessage]);
const joinRoom = useCallback(async (roomId: string): Promise<void> => {
return new Promise((resolve) => {
sendMessage('JOIN_ROOM', { chatRoomId: roomId });
// Simple resolution - server will confirm via broadcast
setTimeout(resolve, 100);
});
}, [sendMessage]);
const leaveRoom = useCallback(async (roomId: string): Promise<void> => {
return new Promise((resolve) => {
sendMessage('LEAVE_ROOM', { chatRoomId: roomId });
setTimeout(resolve, 100);
});
}, [sendMessage]);
// Clean disconnect function
const disconnect = useCallback(() => {
console.log('[WebSocket] ๐ Manual disconnect requested');
isIntentionalDisconnect.current = true;
if (wsRef.current) {
wsRef.current.close(1000, 'Manual disconnect');
wsRef.current = null;
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
setConnected(false);
setConnectionState('disconnected');
performanceMonitor.trackWebSocketDisconnection();
setWsInstance(null);
}, []);
// Enhanced connection logic with incognito mode handling
const connect = useCallback(() => {
// Prevent duplicate connections
if (wsRef.current && (wsRef.current.readyState === WebSocket.CONNECTING || wsRef.current.readyState === WebSocket.OPEN)) {
console.log('[WebSocket] ๐ Connection already exists or connecting');
return;
}
if (!session?.user?.id || status !== 'authenticated') {
console.log('[WebSocket] โ Cannot connect - not authenticated');
console.log('[WebSocket] ๐ Debug - Status:', status, 'Session:', session);
return;
}
// Simplified incognito detection (less aggressive)
const isLikelyIncognito = () => {
try {
// Simple storage detection
if (!window.localStorage || !window.sessionStorage) return true;
return false;
} catch (e) {
return true;
}
};
const incognitoMode = isLikelyIncognito();
if (incognitoMode) {
console.log('[WebSocket] โ ๏ธ Incognito/Private browsing detected - using optimized settings');
}
// Check if session changed
const currentSessionId = session.user.id;
if (lastSessionId.current && lastSessionId.current !== currentSessionId) {
console.log('[WebSocket] ๐ Session changed, forcing clean reconnect');
isIntentionalDisconnect.current = true;
disconnect();
}
lastSessionId.current = currentSessionId;
isIntentionalDisconnect.current = false;
setConnectionState('connecting');
const newConnectionId = generateConnectionId();
connectionId.current = newConnectionId;
const userId = session.user.id;
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
// SECURITY: Create secure token instead of exposing user data in query string
const secureToken = btoa(JSON.stringify({
userId,
name: session.user.name,
timestamp: Date.now(),
connId: newConnectionId
}));
const wsUrl = `${protocol}//${window.location.host}/_ws?token=${secureToken}`;
console.log(`[WebSocket] ๐ Connecting for user: ${session.user.name} (${newConnectionId})`);
try {
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
// PERFORMANCE: Reduced timeout from 15-20s to 8-10s for faster failure detection
const connectionTimeout = incognitoMode ? 8000 : 10000;
const connectionTimeoutRef = setTimeout(() => {
if (ws.readyState === WebSocket.CONNECTING) {
console.log('[WebSocket] โฐ Connection timeout - closing');
ws.close(1000, 'Connection timeout');
setConnectionState('disconnected');
}
}, connectionTimeout);
// PERFORMANCE: Reduced ready state checks from 5s to 3s total
let readyStateCheckCount = 0;
const readyStateCheck = setInterval(() => {
readyStateCheckCount++;
console.log(`[WebSocket] Ready State Check #${readyStateCheckCount}: ${ws.readyState} (${newConnectionId})`);
if (ws.readyState !== WebSocket.CONNECTING || readyStateCheckCount >= 3) {
clearInterval(readyStateCheck);
}
}, 1000);
ws.onopen = () => {
clearInterval(readyStateCheck);
clearTimeout(connectionTimeoutRef);
console.log(`[WebSocket] โ
Connected for ${session.user.name} (${newConnectionId})`);
setConnected(true);
setConnectionState('connected');
performanceMonitor.trackWebSocketConnection();
setWsInstance(ws);
// Reset reconnection attempts on successful connection
reconnectAttempts.current = 0;
setConnectionStats(prev => ({
...prev,
reconnectAttempts: 0,
lastConnected: Date.now(),
}));
// Clear any reconnect timeout
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
};
ws.onmessage = handleMessage;
ws.onclose = (event) => {
clearInterval(readyStateCheck);
clearTimeout(connectionTimeoutRef);
console.log(`[WebSocket] ๐ Disconnected (code: ${event.code}, reason: ${event.reason}) (${newConnectionId})`);
setConnected(false);
setConnectionState('disconnected');
setWsInstance(null);
if (wsRef.current === ws) {
wsRef.current = null;
}
// Don't reconnect if intentional, authentication lost, or certain error codes
if (isIntentionalDisconnect.current ||
event.code === 1000 ||
event.code === 1001 ||
status !== 'authenticated') {
console.log('[WebSocket] โ Clean close or not authenticated - not reconnecting');
return;
}
// PERFORMANCE: Faster reconnection with reduced retry attempts (5 instead of 8)
if (status === 'authenticated' && session?.user?.id) {
const attempts = reconnectAttempts.current;
if (attempts >= 5) { // Reduced from 8 to 5 attempts
console.log(`[WebSocket] โ Max reconnection attempts reached (${attempts}). Manual reconnect required.`);
setConnectionState('disconnected');
return;
}
setConnectionState('reconnecting');
// PERFORMANCE: Faster delays - max 2s instead of 3s
const delay = event.code === 1006 ? 0 : Math.min(300 * (attempts + 1), 2000);
console.log(`[WebSocket] ๐ Reconnecting in ${delay}ms (attempt ${attempts + 1}/5) - Code: ${event.code}`);
reconnectAttempts.current += 1;
setConnectionStats(prev => ({
...prev,
reconnectAttempts: reconnectAttempts.current,
}));
if (delay === 0) {
// Immediate reconnection for HMR
if (!isIntentionalDisconnect.current) {
connect();
}
} else {
reconnectTimeoutRef.current = setTimeout(() => {
if (!isIntentionalDisconnect.current) {
connect();
}
}, delay);
}
}
};
ws.onerror = (error) => {
clearInterval(readyStateCheck);
clearTimeout(connectionTimeoutRef);
console.error(`[WebSocket] โ Connection error (${newConnectionId}):`, error);
};
} catch (error) {
console.error('[WebSocket] โ Failed to create connection:', error);
setConnectionState('disconnected');
}
}, [session?.user?.id, session?.user?.name, status]);
// Manual reconnection function
const reconnect = useCallback(() => {
console.log('[WebSocket] ๐ Manual reconnection requested');
// Reset reconnection attempts
reconnectAttempts.current = 0;
setConnectionStats(prev => ({
...prev,
reconnectAttempts: 0,
}));
// Clean disconnect and reconnect immediately
disconnect();
// Connect immediately without any delay
connect();
}, [connect, disconnect]);
// Main connection effect - only trigger on authentication changes
useEffect(() => {
console.log('[WebSocket] ๐ Connection effect triggered:', { status, userId: session?.user?.id, currentWS: wsRef.current?.readyState });
if (status === 'authenticated' && session?.user?.id) {
// Only connect if we don't have an active connection
if (!wsRef.current || wsRef.current.readyState === WebSocket.CLOSED) {
console.log('[WebSocket] ๐ Connecting immediately (no delay)');
// Connect immediately without any delay
connect();
} else {
console.log('[WebSocket] โ
Connection already exists, skipping');
}
} else if (status === 'unauthenticated') {
console.log('[WebSocket] ๐ Unauthenticated, disconnecting');
// Clean disconnect when logged out
disconnect();
} else {
console.log('[WebSocket] โณ Auth status loading or unknown:', status);
}
return () => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
};
}, [status, session?.user?.id]);
// Cleanup function to clear all timers and references
const cleanup = useCallback(() => {
// Clear all timeouts
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
// Reset rate limiting
messageRateLimit.current = {
count: 0,
lastReset: Date.now(),
maxMessages: 100,
windowMs: 60000
};
// Clear connection tracking
isIntentionalDisconnect.current = false;
connectionId.current = null;
lastSessionId.current = null;
reconnectAttempts.current = 0;
console.log('[WebSocket] ๐งน Cleanup completed');
}, []);
// Cleanup on unmount
useEffect(() => {
return () => {
cleanup();
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
};
}, [cleanup]);
const contextValue: WebSocketContextValue = {
ws: wsInstance,
connected,
connectionState,
sendMessage,
sendTyping,
joinRoom,
leaveRoom,
reconnect,
disconnect,
connectionStats,
directMessageNotifications,
markDirectMessagesAsRead,
getTotalUnreadDirectMessages,
incomingVideoCall,
acceptVideoCall,
declineVideoCall,
videoCallActive,
startVideoCall,
endVideoCall,
currentVideoCall,
};
return (
<WebSocketContext.Provider value={contextValue}>
{children}
</WebSocketContext.Provider>
);
};
export const useWebSocket = () => useContext(WebSocketContext);