T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/context/EnhancedWebSocketContext.tsx
import React, { createContext, useContext, useEffect, useRef, useState, ReactNode, useCallback } from 'react';
import { useSession } from 'next-auth/react';

// Enhanced Types
interface WebSocketMessage {
  id: string;
  type: string;
  data: any;
  timestamp: number;
  requiresAck?: boolean;
}

interface PendingMessage {
  message: WebSocketMessage;
  resolve: (value: any) => void;
  reject: (error: Error) => void;
  retryCount: number;
  timeout: NodeJS.Timeout;
}

interface UserPresence {
  userId: string;
  status: 'online' | 'away' | 'offline';
  lastSeen: number;
  currentRoom?: string;
}

interface TypingState {
  roomId: string;
  userId: string;
  userName: string;
  timestamp: number;
}

interface WebSocketContextValue {
  ws: WebSocket | null;
  connected: boolean;
  connectionState: 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
  
  // Message handling
  sendMessage: (type: string, data: any, requiresAck?: boolean) => Promise<any>;
  sendReliableMessage: (type: string, data: any) => Promise<any>;
  
  // Presence
  userPresence: Map<string, UserPresence>;
  setUserStatus: (status: 'online' | 'away') => void;
  
  // Typing indicators
  typingUsers: Map<string, TypingState[]>;
  sendTyping: (roomId: string, isTyping: boolean) => void;
  
  // Room management
  joinRoom: (roomId: string) => Promise<void>;
  leaveRoom: (roomId: string) => Promise<void>;
  
  // Case chat management
  joinCaseChat: (caseId: string) => Promise<void>;
  leaveCaseChat: (caseId: string) => Promise<void>;
  sendCaseTyping: (caseId: string, isTyping: boolean) => void;
  
  // Connection stats
  connectionStats: {
    reconnectAttempts: number;
    lastConnected: number | null;
    messagesSent: number;
    messagesReceived: number;
    latency: number | null;
  };
}

const WebSocketContext = createContext<WebSocketContextValue>({
  ws: null,
  connected: false,
  connectionState: 'disconnected',
  sendMessage: async () => {},
  sendReliableMessage: async () => {},
  userPresence: new Map(),
  setUserStatus: () => {},
  typingUsers: new Map(),
  sendTyping: () => {},
  joinRoom: async () => {},
  leaveRoom: async () => {},
  joinCaseChat: async () => {},
  leaveCaseChat: async () => {},
  sendCaseTyping: () => {},
  connectionStats: {
    reconnectAttempts: 0,
    lastConnected: null,
    messagesSent: 0,
    messagesReceived: 0,
    latency: 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);
  
  // Message handling
  const pendingMessages = useRef<Map<string, PendingMessage>>(new Map());
  const messageQueue = useRef<WebSocketMessage[]>([]);
  
  // Presence and typing
  const [userPresence, setUserPresence] = useState<Map<string, UserPresence>>(new Map());
  const [typingUsers, setTypingUsers] = useState<Map<string, TypingState[]>>(new Map());
  const typingTimeouts = useRef<Map<string, NodeJS.Timeout>>(new Map());
  
  // Connection stats
  const [connectionStats, setConnectionStats] = useState({
    reconnectAttempts: 0,
    lastConnected: null as number | null,
    messagesSent: 0,
    messagesReceived: 0,
    latency: null as number | null,
  });
  
  // Ping/Pong for latency measurement
  const lastPingTime = useRef<number | null>(null);
  const pingInterval = useRef<NodeJS.Timeout | null>(null);

  // Generate unique message ID
  const generateMessageId = useCallback(() => {
    return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }, []);

  // Enhanced message sending with acknowledgments
  const sendMessage = useCallback(async (type: string, data: any, requiresAck: boolean = false): Promise<any> => {
    return new Promise((resolve, reject) => {
      if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
        const message: WebSocketMessage = {
          id: generateMessageId(),
          type,
          data,
          timestamp: Date.now(),
          requiresAck,
        };
        
        // Queue message for when connection is restored
        messageQueue.current.push(message);
        
        if (requiresAck) {
          reject(new Error('WebSocket not connected'));
        } else {
          resolve(null);
        }
        return;
      }

      const message: WebSocketMessage = {
        id: generateMessageId(),
        type,
        data,
        timestamp: Date.now(),
        requiresAck,
      };

      try {
        wsRef.current.send(JSON.stringify(message));
        
        setConnectionStats(prev => ({
          ...prev,
          messagesSent: prev.messagesSent + 1,
        }));

        if (requiresAck) {
          // Set up acknowledgment handling
          const timeout = setTimeout(() => {
            const pending = pendingMessages.current.get(message.id);
            if (pending) {
              pendingMessages.current.delete(message.id);
              pending.reject(new Error('Message acknowledgment timeout'));
            }
          }, 10000); // 10 second timeout

          pendingMessages.current.set(message.id, {
            message,
            resolve,
            reject,
            retryCount: 0,
            timeout,
          });
        } else {
          resolve(null);
        }
      } catch (error) {
        reject(error);
      }
    });
  }, [generateMessageId]);

  // Reliable message sending with retries
  const sendReliableMessage = useCallback(async (type: string, data: any): Promise<any> => {
    return sendMessage(type, data, true);
  }, [sendMessage]);

  // Presence management
  const setUserStatus = useCallback((status: 'online' | 'away') => {
    if (session?.user?.id) {
      sendMessage('PRESENCE_UPDATE', {
        userId: session.user.id,
        status,
        timestamp: Date.now(),
      });
    }
  }, [sendMessage]);

  // Typing indicators
  const sendTyping = useCallback((roomId: string, isTyping: boolean) => {
    if (!session?.user?.id) return;

    const key = `${roomId}_${session.user.id}`;
    
    // Clear existing timeout
    if (typingTimeouts.current.has(key)) {
      clearTimeout(typingTimeouts.current.get(key)!);
      typingTimeouts.current.delete(key);
    }

    sendMessage('TYPING', {
      roomId,
      userId: session.user.id,
      userName: session.user.name,
      isTyping,
      timestamp: Date.now(),
    });

    if (isTyping) {
      // Auto-stop typing after 3 seconds
      const timeout = setTimeout(() => {
        sendMessage('TYPING', {
          roomId,
          userId: session.user.id,
          userName: session.user.name,
          isTyping: false,
          timestamp: Date.now(),
        });
      }, 3000);
      
      typingTimeouts.current.set(key, timeout);
    }
  }, [sendMessage]);

  // Room management
  const joinRoom = useCallback(async (roomId: string): Promise<void> => {
    return sendReliableMessage('JOIN_ROOM', { chatRoomId: roomId });
  }, [sendReliableMessage]);

  const leaveRoom = useCallback(async (roomId: string): Promise<void> => {
    return sendReliableMessage('LEAVE_ROOM', { chatRoomId: roomId });
  }, [sendReliableMessage]);

  // Case chat management
  const joinCaseChat = useCallback(async (caseId: string): Promise<void> => {
    return sendReliableMessage('JOIN_CASE_CHAT', { caseId });
  }, [sendReliableMessage]);

  const leaveCaseChat = useCallback(async (caseId: string): Promise<void> => {
    return sendReliableMessage('LEAVE_CASE_CHAT', { caseId });
  }, [sendReliableMessage]);

  const sendCaseTyping = useCallback((caseId: string, isTyping: boolean) => {
    if (!session?.user?.id) return;

    const key = `case_${caseId}_${session.user.id}`;
    
    // Clear existing timeout
    if (typingTimeouts.current.has(key)) {
      clearTimeout(typingTimeouts.current.get(key)!);
      typingTimeouts.current.delete(key);
    }

    sendMessage('CASE_TYPING', {
      caseId,
      userId: session.user.id,
      userName: session.user.name,
      isTyping,
      timestamp: Date.now(),
    });

    if (isTyping) {
      // Auto-stop typing after 3 seconds
      const timeout = setTimeout(() => {
        sendMessage('CASE_TYPING', {
          caseId,
          userId: session.user.id,
          userName: session.user.name,
          isTyping: false,
          timestamp: Date.now(),
        });
      }, 3000);
      
      typingTimeouts.current.set(key, timeout);
    }
  }, [sendMessage]);

  // Process queued messages when connection is restored
  const processMessageQueue = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN && messageQueue.current.length > 0) {
      console.log(`[WebSocket] 📤 Processing ${messageQueue.current.length} queued messages`);
      
      const messages = [...messageQueue.current];
      messageQueue.current = [];
      
      messages.forEach(message => {
        try {
          wsRef.current!.send(JSON.stringify(message));
          setConnectionStats(prev => ({
            ...prev,
            messagesSent: prev.messagesSent + 1,
          }));
        } catch (error) {
          console.error('[WebSocket] ❌ Failed to send queued message:', error);
          // Re-queue the message
          messageQueue.current.push(message);
        }
      });
    }
  }, []);

  // Handle incoming messages
  const handleMessage = useCallback((event: MessageEvent) => {
    try {
      const message = JSON.parse(event.data);
      
      setConnectionStats(prev => ({
        ...prev,
        messagesReceived: prev.messagesReceived + 1,
      }));

      // Handle different message types
      switch (message.type) {
        case 'pong':
          if (lastPingTime.current) {
            const latency = Date.now() - lastPingTime.current;
            setConnectionStats(prev => ({ ...prev, latency }));
            lastPingTime.current = null;
          }
          break;

        case 'ping':
          // Respond to server ping
          wsRef.current?.send(JSON.stringify({ type: 'pong' }));
          break;

        case 'MESSAGE_ACK':
          // Handle message acknowledgment
          const pending = pendingMessages.current.get(message.data.messageId);
          if (pending) {
            clearTimeout(pending.timeout);
            pendingMessages.current.delete(message.data.messageId);
            pending.resolve(message.data);
          }
          break;

        case 'PRESENCE_UPDATE':
          setUserPresence(prev => {
            const newMap = new Map(prev);
            newMap.set(message.data.userId, {
              userId: message.data.userId,
              status: message.data.status,
              lastSeen: message.data.timestamp,
              currentRoom: message.data.currentRoom,
            });
            return newMap;
          });
          break;

        case 'TYPING':
          setTypingUsers(prev => {
            const newMap = new Map(prev);
            const roomTyping = newMap.get(message.data.roomId) || [];
            
            if (message.data.isTyping) {
              // Add or update typing user
              const existingIndex = roomTyping.findIndex(t => t.userId === message.data.userId);
              const typingState: TypingState = {
                roomId: message.data.roomId,
                userId: message.data.userId,
                userName: message.data.userName,
                timestamp: message.data.timestamp,
              };
              
              if (existingIndex >= 0) {
                roomTyping[existingIndex] = typingState;
              } else {
                roomTyping.push(typingState);
              }
            } else {
              // Remove typing user
              const filteredTyping = roomTyping.filter(t => t.userId !== message.data.userId);
              newMap.set(message.data.roomId, filteredTyping);
              return newMap;
            }
            
            newMap.set(message.data.roomId, roomTyping);
            return newMap;
          });
          break;

        case 'CASE_TYPING':
          setTypingUsers(prev => {
            const newMap = new Map(prev);
            const caseKey = `case_${message.data.caseId}`;
            const caseTyping = newMap.get(caseKey) || [];
            
            if (message.data.isTyping) {
              // Add or update typing user
              const existingIndex = caseTyping.findIndex(t => t.userId === message.data.userId);
              const typingState: TypingState = {
                roomId: caseKey,
                userId: message.data.userId,
                userName: message.data.userName,
                timestamp: message.data.timestamp,
              };
              
              if (existingIndex >= 0) {
                caseTyping[existingIndex] = typingState;
              } else {
                caseTyping.push(typingState);
              }
            } else {
              // Remove typing user
              const filteredTyping = caseTyping.filter(t => t.userId !== message.data.userId);
              newMap.set(caseKey, filteredTyping);
              return newMap;
            }
            
            newMap.set(caseKey, caseTyping);
            return newMap;
          });
          break;

        default:
          // Let other components handle the message via custom events
          window.dispatchEvent(new CustomEvent('websocket-message', { detail: message }));
          break;
      }
    } catch (error) {
      console.error('[WebSocket] ❌ Failed to parse message:', error);
    }
  }, []);

  // Enhanced connection logic with exponential backoff
  const connect = useCallback(() => {
    if (!session?.user?.id || status !== 'authenticated') return;
    
    // If already connected or connecting, don't create new connection
    if (wsRef.current && wsRef.current.readyState < 2) return;
    
    setConnectionState('connecting');
    
    const userId = session.user.id;
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const userPayload = encodeURIComponent(JSON.stringify({
      ...session.user,
      id: userId,
    }));
    const wsUrl = `${protocol}//${window.location.host}/_ws?userId=${userId}&user=${userPayload}`;
    
    console.log(`[WebSocket] 🔄 Connecting for user: ${session.user.name}`);
    
    try {
      const ws = new WebSocket(wsUrl);
      wsRef.current = ws;

      ws.onopen = () => {
        console.log(`[WebSocket] ✅ Connected for ${session.user.name}`);
        setConnected(true);
        setConnectionState('connected');
        setWsInstance(ws);
        
        setConnectionStats(prev => ({
          ...prev,
          reconnectAttempts: 0,
          lastConnected: Date.now(),
        }));

        // Clear any reconnect timeout
        if (reconnectTimeoutRef.current) {
          clearTimeout(reconnectTimeoutRef.current);
          reconnectTimeoutRef.current = null;
        }

        // Process any queued messages
        processMessageQueue();

        // Start ping interval for latency measurement
        if (pingInterval.current) clearInterval(pingInterval.current);
        pingInterval.current = setInterval(() => {
          if (ws.readyState === WebSocket.OPEN) {
            lastPingTime.current = Date.now();
            ws.send(JSON.stringify({ type: 'ping' }));
          }
        }, 30000);

        // Update presence to online
        setUserStatus('online');
      };

      ws.onmessage = handleMessage;

      ws.onclose = (event) => {
        console.log(`[WebSocket] 🔌 Disconnected (code: ${event.code}, reason: ${event.reason})`);
        
        if (wsRef.current === ws) {
          setConnected(false);
          setConnectionState('disconnected');
          setWsInstance(null);
          wsRef.current = null;
        }

        // Clear ping interval
        if (pingInterval.current) {
          clearInterval(pingInterval.current);
          pingInterval.current = null;
        }

        // Don't reconnect if it was a clean close or user logged out
        if (event.code === 1000 || event.code === 1001 || status !== 'authenticated') {
          return;
        }

        // Exponential backoff reconnection
        if (status === 'authenticated' && session?.user?.id) {
          setConnectionState('reconnecting');
          const attempts = connectionStats.reconnectAttempts;
          const delay = Math.min(1000 * Math.pow(2, attempts), 30000); // Max 30 seconds
          
          console.log(`[WebSocket] 🔄 Reconnecting in ${delay}ms (attempt ${attempts + 1})`);
          
          setConnectionStats(prev => ({
            ...prev,
            reconnectAttempts: prev.reconnectAttempts + 1,
          }));

          reconnectTimeoutRef.current = setTimeout(connect, delay);
        }
      };

      ws.onerror = (error) => {
        console.error('[WebSocket] ❌ Connection error:', error);
      };

    } catch (error) {
      console.error('[WebSocket] ❌ Failed to create connection:', error);
      setConnectionState('disconnected');
    }
  }, [status, connectionStats.reconnectAttempts, handleMessage, processMessageQueue, setUserStatus]);

  // Main connection effect
  useEffect(() => {
    if (status === 'authenticated' && session?.user?.id) {
      connect();
    } else {
      // Clean up when user logs out
      if (wsRef.current) {
        wsRef.current.close(1000, 'User logged out');
        wsRef.current = null;
      }
      setConnected(false);
      setConnectionState('disconnected');
      setWsInstance(null);
    }

    return () => {
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }
      if (pingInterval.current) {
        clearInterval(pingInterval.current);
      }
      if (wsRef.current) {
        wsRef.current.close(1000, 'Component unmounting');
        wsRef.current = null;
      }
    };
  }, [status, session?.user?.id, connect]);

  // Handle page visibility changes
  useEffect(() => {
    const handleVisibilityChange = () => {
      if (document.hidden) {
        setUserStatus('away');
      } else {
        setUserStatus('online');
        // Ensure connection is active when page becomes visible
        if (status === 'authenticated' && (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN)) {
          connect();
        }
      }
    };

    document.addEventListener('visibilitychange', handleVisibilityChange);
    return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
  }, [status, connect, setUserStatus]);

  // Clean up typing timeouts on unmount
  useEffect(() => {
    return () => {
      typingTimeouts.current.forEach(timeout => clearTimeout(timeout));
      typingTimeouts.current.clear();
    };
  }, []);

  // Cleanup function to prevent memory leaks
  const cleanup = useCallback(() => {
    if (wsRef.current) {
      wsRef.current.close();
      wsRef.current = null;
    }
    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
      reconnectTimeoutRef.current = null;
    }
    if (pingInterval.current) {
      clearInterval(pingInterval.current);
      pingInterval.current = null;
    }
    
    // Clear all pending messages
    pendingMessages.current.forEach(pending => {
      clearTimeout(pending.timeout);
    });
    pendingMessages.current.clear();
    
    // Clear typing timeouts
    typingTimeouts.current.forEach(timeout => {
      clearTimeout(timeout);
    });
    typingTimeouts.current.clear();
    
    setConnected(false);
    setConnectionState('disconnected');
  }, []);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      cleanup();
    };
  }, [cleanup]);

  const contextValue: WebSocketContextValue = {
    ws: wsInstance,
    connected,
    connectionState,
    sendMessage,
    sendReliableMessage,
    userPresence,
    setUserStatus,
    typingUsers,
    sendTyping,
    joinRoom,
    leaveRoom,
    joinCaseChat,
    leaveCaseChat,
    sendCaseTyping,
    connectionStats,
  };

  return (
    <WebSocketContext.Provider value={contextValue}>
      {children}
    </WebSocketContext.Provider>
  );
};

export const useWebSocket = () => useContext(WebSocketContext); 

CasperSecurity Mini