Async-Redis

 view release on metacpan or  search on metacpan

examples/pagi-chat/public/js/app.js  view on Meta::CPAN

        setConnectionStatus(state.reconnectAttempts > 0 ? 'reconnecting' : 'connecting');

        state.ws = new WebSocket(wsUrl);

        state.ws.onopen = () => {
            setConnectionStatus('connected');
            state.reconnectAttempts = 0;
            state.lastPongTime = Date.now();

            // Start keepalive ping interval
            if (state.pingInterval) {
                clearInterval(state.pingInterval);
            }
            state.pingInterval = setInterval(() => {
                if (state.ws && state.ws.readyState === WebSocket.OPEN) {
                    sendMessage({ type: 'ping' });
                }
            }, state.pingIntervalMs);

            // Start heartbeat timeout check
            if (state.heartbeatCheckInterval) {
                clearInterval(state.heartbeatCheckInterval);
            }
            state.heartbeatCheckInterval = setInterval(() => {
                const timeSinceLastPong = Date.now() - state.lastPongTime;
                if (timeSinceLastPong > state.heartbeatTimeoutMs) {
                    console.warn('Heartbeat timeout - connection appears dead, reconnecting...');
                    if (state.ws) {
                        state.ws.close();
                    }
                }
            }, 5000); // Check every 5 seconds
        };

        state.ws.onclose = (event) => {
            setConnectionStatus('disconnected');

            // Clear intervals
            if (state.pingInterval) {
                clearInterval(state.pingInterval);
                state.pingInterval = null;
            }
            if (state.heartbeatCheckInterval) {
                clearInterval(state.heartbeatCheckInterval);
                state.heartbeatCheckInterval = null;
            }

            // Always try to reconnect with exponential backoff
            state.reconnectAttempts++;
            const delay = calculateReconnectDelay();
            console.log(`Reconnecting in ${Math.round(delay)}ms (attempt ${state.reconnectAttempts})...`);
            setConnectionStatus('reconnecting');
            setTimeout(connectWebSocket, delay);
        };

        state.ws.onerror = (error) => {
            console.error('WebSocket error:', error);
        };

        state.ws.onmessage = (event) => {
            // Any message resets the heartbeat timer
            state.lastPongTime = Date.now();

            try {
                const data = JSON.parse(event.data);
                handleWebSocketMessage(data);
            } catch (e) {
                console.error('Failed to parse message:', e);
            }
        };
    }

    function handleWebSocketMessage(data) {
        // Track message IDs for catch-up on reconnect
        if (data.id && data.id > state.lastMsgId) {
            state.lastMsgId = data.id;
        }

        switch (data.type) {
            case 'connected':
                // New connection - store session info
                state.userId = data.user_id;
                state.sessionId = data.session_id;
                state.username = data.name;
                localStorage.setItem('chat-session-id', data.session_id);
                updateUserInfo();
                updateRoomsList(data.rooms.map(name => ({ name, users: 0 })));
                break;

            case 'resumed':
                // Session resumed after reconnect
                state.userId = data.session_id;  // session_id is the user_id
                state.sessionId = data.session_id;
                state.username = data.name;
                localStorage.setItem('chat-session-id', data.session_id);
                updateUserInfo();

                // Restore room state
                state.rooms = {};
                data.rooms.forEach(room => state.rooms[room] = true);

                // Apply missed messages
                if (data.missedMessages) {
                    for (const [room, messages] of Object.entries(data.missedMessages)) {
                        if (room === state.currentRoom) {
                            messages.forEach(msg => {
                                if (msg.type === 'system') {
                                    addSystemMessage(msg.text, false);
                                } else {
                                    addMessage(msg, false);
                                }
                                // Track highest message ID
                                if (msg.id && msg.id > state.lastMsgId) {
                                    state.lastMsgId = msg.id;
                                }
                            });
                            scrollToBottom();
                        }
                    }
                }



( run in 1.494 second using v1.01-cache-2.11-cpan-bbcb1afb8fc )