diff --git a/src/static/rooms.js b/src/static/rooms.js index 6465c02..504659b 100644 --- a/src/static/rooms.js +++ b/src/static/rooms.js @@ -3,23 +3,63 @@ let roomsRefreshInterval = null; let currentSortBy = 'activity'; let currentMinUsers = 0; let isSubscribedToRooms = false; +let isRoomsBrowserOpen = false; +let retryAttempts = 0; +const MAX_RETRY_ATTEMPTS = 3; + +// Configuration +const ROOMS_CONFIG = { + refreshInterval: 30000, // 30 seconds + maxRetries: 3, + retryDelay: 2000, // 2 seconds + socketTimeout: 25000, // 25 seconds to wait for socket + fallbackToHttp: true, + autoRefresh: true +}; // Show the public rooms browser function showPublicRoomsBrowser() { + console.log('Opening public rooms browser'); const browserElement = document.getElementById('publicRoomsBrowser'); - if (browserElement) { - browserElement.style.display = 'block'; - loadPublicRoomsWebSocket(); - subscribeToPublicRooms(); + if (!browserElement) { + console.error('Public rooms browser element not found'); + return; + } + + browserElement.style.display = 'flex'; + isRoomsBrowserOpen = true; + retryAttempts = 0; + + // Reset scroll position + const browserContent = document.getElementById('browserContent'); + if (browserContent) { + browserContent.scrollTop = 0; + } + + // Load rooms and subscribe to updates + loadPublicRoomsWebSocket(); + subscribeToPublicRooms(); + + // Set up auto-refresh if enabled + if (ROOMS_CONFIG.autoRefresh && !roomsRefreshInterval) { + roomsRefreshInterval = setInterval(() => { + if (isRoomsBrowserOpen) { + console.log('Auto-refreshing public rooms'); + loadPublicRoomsWebSocket(); + } + }, ROOMS_CONFIG.refreshInterval); } } // Close the public rooms browser function closePublicRoomsBrowser() { + console.log('Closing public rooms browser'); const browserElement = document.getElementById('publicRoomsBrowser'); if (browserElement) { browserElement.style.display = 'none'; } + + isRoomsBrowserOpen = false; unsubscribeFromPublicRooms(); // Clear auto-refresh @@ -31,114 +71,232 @@ function closePublicRoomsBrowser() { // Subscribe to live public rooms updates function subscribeToPublicRooms() { - if (typeof socket !== 'undefined' && socket !== null && socket.connected && !isSubscribedToRooms) { - socket.emit('subscribe_public_rooms'); - isSubscribedToRooms = true; - console.log('Subscribed to public rooms updates'); + if (isSocketAvailable() && !isSubscribedToRooms) { + try { + socket.emit('subscribe_public_rooms'); + isSubscribedToRooms = true; + console.log('Subscribed to public rooms updates'); + } catch (error) { + console.error('Error subscribing to public rooms:', error); + } } } // Unsubscribe from public rooms updates function unsubscribeFromPublicRooms() { - if (typeof socket !== 'undefined' && socket !== null && socket.connected && isSubscribedToRooms) { - socket.emit('unsubscribe_public_rooms'); - isSubscribedToRooms = false; - console.log('Unsubscribed from public rooms updates'); + if (isSocketAvailable() && isSubscribedToRooms) { + try { + socket.emit('unsubscribe_public_rooms'); + isSubscribedToRooms = false; + console.log('Unsubscribed from public rooms updates'); + } catch (error) { + console.error('Error unsubscribing from public rooms:', error); + } } } -// Load public rooms via WebSocket +// Check if socket is available and connected +function isSocketAvailable() { + return typeof socket !== 'undefined' && + socket !== null && + socket.connected; +} + +// Load public rooms via WebSocket with fallback function loadPublicRoomsWebSocket() { + if (!isRoomsBrowserOpen) { + console.log('Rooms browser not open, skipping load'); + return; + } + try { showLoadingState(); + updateFiltersFromUI(); - const sortSelect = document.getElementById('roomsSortSelect'); - const minUsersFilter = document.getElementById('minUsersFilter'); - - currentSortBy = sortSelect ? sortSelect.value : 'activity'; - currentMinUsers = minUsersFilter ? (minUsersFilter.value || 0) : 0; - - if (typeof socket !== 'undefined' && socket !== null && socket.connected) { + if (isSocketAvailable()) { + console.log('Loading rooms via WebSocket'); socket.emit('request_public_rooms', { sort: currentSortBy, min_users: currentMinUsers, limit: 50 }); + + // Set a timeout for WebSocket response + setTimeout(() => { + if (document.getElementById('roomsLoading')?.style.display !== 'none') { + console.warn('WebSocket timeout, falling back to HTTP'); + loadPublicRoomsHTTP(); + } + }, 5000); } else { - // Fallback to HTTP API if WebSocket not available + console.log('WebSocket not available, using HTTP fallback'); loadPublicRoomsHTTP(); } } catch (error) { console.error('Error requesting public rooms via WebSocket:', error); - loadPublicRoomsHTTP(); // Fallback to HTTP + loadPublicRoomsHTTP(); } } -// Fallback HTTP method +// Update filters from UI elements +function updateFiltersFromUI() { + const sortSelect = document.getElementById('roomsSortSelect'); + const minUsersFilter = document.getElementById('minUsersFilter'); + + currentSortBy = sortSelect?.value || 'activity'; + currentMinUsers = parseInt(minUsersFilter?.value || '0') || 0; + + console.log(`Updated filters: sort=${currentSortBy}, minUsers=${currentMinUsers}`); +} + +// Enhanced HTTP fallback with retry logic async function loadPublicRoomsHTTP() { + if (!isRoomsBrowserOpen) { + console.log('Rooms browser not open, skipping HTTP load'); + return; + } + try { showLoadingState(); + updateFiltersFromUI(); - const sortSelect = document.getElementById('roomsSortSelect'); - const minUsersFilter = document.getElementById('minUsersFilter'); + console.log(`Loading rooms via HTTP (attempt ${retryAttempts + 1}/${MAX_RETRY_ATTEMPTS})`); - const sortBy = sortSelect ? sortSelect.value : 'activity'; - const minUsers = minUsersFilter ? (minUsersFilter.value || 0) : 0; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout - const response = await fetch(`/api/rooms/public?sort=${sortBy}&min_users=${minUsers}&limit=50`); + const response = await fetch( + `/api/rooms/public?sort=${encodeURIComponent(currentSortBy)}&min_users=${currentMinUsers}&limit=50`, + { signal: controller.signal } + ); + + clearTimeout(timeoutId); if (!response.ok) { - throw new Error(`HTTP ${response.status}`); + throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); - publicRoomsData = data.rooms || []; - // Also load stats - try { - const statsResponse = await fetch('/api/rooms/stats'); - if (statsResponse.ok) { - const stats = await statsResponse.json(); - updateStatsDisplay(stats); - } - } catch (statsError) { - console.warn('Failed to load room stats:', statsError); + if (!data || !Array.isArray(data.rooms)) { + throw new Error('Invalid response format'); } + publicRoomsData = data.rooms; + console.log(`Loaded ${publicRoomsData.length} rooms via HTTP`); + + // Load stats separately + await loadRoomStats(); + displayRooms(); + retryAttempts = 0; // Reset on success } catch (error) { console.error('Error loading public rooms via HTTP:', error); - showErrorState(); + + if (error.name === 'AbortError') { + console.error('Request timed out'); + } + + retryAttempts++; + + if (retryAttempts < MAX_RETRY_ATTEMPTS) { + console.log(`Retrying in ${ROOMS_CONFIG.retryDelay}ms...`); + setTimeout(() => { + if (isRoomsBrowserOpen) { + loadPublicRoomsHTTP(); + } + }, ROOMS_CONFIG.retryDelay); + } else { + showErrorState(); + retryAttempts = 0; + } } } -// Refresh public rooms -function refreshPublicRooms() { - loadPublicRoomsWebSocket(); +// Load room statistics +async function loadRoomStats() { + try { + const statsResponse = await fetch('/api/rooms/stats'); + if (statsResponse.ok) { + const stats = await statsResponse.json(); + updateStatsDisplay(stats); + } + } catch (error) { + console.warn('Failed to load room stats:', error); + } } -// Update stats display +// Refresh public rooms with user feedback +function refreshPublicRooms() { + console.log('Manual refresh requested'); + retryAttempts = 0; // Reset retry counter on manual refresh + + // Show brief loading indicator + const refreshBtn = document.getElementById('refreshPublicRoomsBtn'); + const originalText = refreshBtn?.textContent; + + if (refreshBtn) { + refreshBtn.textContent = 'Refreshing...'; + refreshBtn.disabled = true; + } + + loadPublicRoomsWebSocket(); + + // Reset button after delay + setTimeout(() => { + if (refreshBtn) { + refreshBtn.textContent = originalText || 'Refresh'; + refreshBtn.disabled = false; + } + }, 2000); +} + +// Update stats display with enhanced formatting function updateStatsDisplay(stats) { const statsElement = document.getElementById('roomsStats'); - if (statsElement && stats) { - statsElement.textContent = `${stats.public_rooms || 0} public rooms • ${stats.total_users || 0} users online`; - } + if (!statsElement || !stats) return; + + const publicRooms = stats.public_rooms || 0; + const totalUsers = stats.total_users || 0; + const lastUpdated = new Date().toLocaleTimeString(); + + statsElement.innerHTML = ` + ${publicRooms} public room${publicRooms !== 1 ? 's' : ''} + • + ${totalUsers} user${totalUsers !== 1 ? 's' : ''} online + • Updated ${lastUpdated} + `; } -// Show loading state +// Enhanced loading state function showLoadingState() { const loadingElement = document.getElementById('roomsLoading'); const listElement = document.getElementById('roomsList'); const emptyElement = document.getElementById('roomsEmpty'); - if (loadingElement) loadingElement.style.display = 'flex'; + if (loadingElement) { + loadingElement.style.display = 'flex'; + loadingElement.innerHTML = ` +
Unable to connect to the server. Please try again.
- ++ Unable to connect to the server. This could be due to network issues or server maintenance. +
++ Attempted ${retryAttempts}/${MAX_RETRY_ATTEMPTS} retries +
++ ${currentMinUsers > 0 ? `Try reducing the minimum users filter (currently ${currentMinUsers})` : 'Check back later or create your own room'} +
+ +