fixed the javascript for @rooms.js
This commit is contained in:
@@ -3,23 +3,63 @@ let roomsRefreshInterval = null;
|
|||||||
let currentSortBy = 'activity';
|
let currentSortBy = 'activity';
|
||||||
let currentMinUsers = 0;
|
let currentMinUsers = 0;
|
||||||
let isSubscribedToRooms = false;
|
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
|
// Show the public rooms browser
|
||||||
function showPublicRoomsBrowser() {
|
function showPublicRoomsBrowser() {
|
||||||
|
console.log('Opening public rooms browser');
|
||||||
const browserElement = document.getElementById('publicRoomsBrowser');
|
const browserElement = document.getElementById('publicRoomsBrowser');
|
||||||
if (browserElement) {
|
if (!browserElement) {
|
||||||
browserElement.style.display = 'block';
|
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();
|
loadPublicRoomsWebSocket();
|
||||||
subscribeToPublicRooms();
|
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
|
// Close the public rooms browser
|
||||||
function closePublicRoomsBrowser() {
|
function closePublicRoomsBrowser() {
|
||||||
|
console.log('Closing public rooms browser');
|
||||||
const browserElement = document.getElementById('publicRoomsBrowser');
|
const browserElement = document.getElementById('publicRoomsBrowser');
|
||||||
if (browserElement) {
|
if (browserElement) {
|
||||||
browserElement.style.display = 'none';
|
browserElement.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isRoomsBrowserOpen = false;
|
||||||
unsubscribeFromPublicRooms();
|
unsubscribeFromPublicRooms();
|
||||||
|
|
||||||
// Clear auto-refresh
|
// Clear auto-refresh
|
||||||
@@ -31,114 +71,232 @@ function closePublicRoomsBrowser() {
|
|||||||
|
|
||||||
// Subscribe to live public rooms updates
|
// Subscribe to live public rooms updates
|
||||||
function subscribeToPublicRooms() {
|
function subscribeToPublicRooms() {
|
||||||
if (typeof socket !== 'undefined' && socket !== null && socket.connected && !isSubscribedToRooms) {
|
if (isSocketAvailable() && !isSubscribedToRooms) {
|
||||||
|
try {
|
||||||
socket.emit('subscribe_public_rooms');
|
socket.emit('subscribe_public_rooms');
|
||||||
isSubscribedToRooms = true;
|
isSubscribedToRooms = true;
|
||||||
console.log('Subscribed to public rooms updates');
|
console.log('Subscribed to public rooms updates');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error subscribing to public rooms:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsubscribe from public rooms updates
|
// Unsubscribe from public rooms updates
|
||||||
function unsubscribeFromPublicRooms() {
|
function unsubscribeFromPublicRooms() {
|
||||||
if (typeof socket !== 'undefined' && socket !== null && socket.connected && isSubscribedToRooms) {
|
if (isSocketAvailable() && isSubscribedToRooms) {
|
||||||
|
try {
|
||||||
socket.emit('unsubscribe_public_rooms');
|
socket.emit('unsubscribe_public_rooms');
|
||||||
isSubscribedToRooms = false;
|
isSubscribedToRooms = false;
|
||||||
console.log('Unsubscribed from public rooms updates');
|
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() {
|
function loadPublicRoomsWebSocket() {
|
||||||
|
if (!isRoomsBrowserOpen) {
|
||||||
|
console.log('Rooms browser not open, skipping load');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
showLoadingState();
|
showLoadingState();
|
||||||
|
updateFiltersFromUI();
|
||||||
|
|
||||||
const sortSelect = document.getElementById('roomsSortSelect');
|
if (isSocketAvailable()) {
|
||||||
const minUsersFilter = document.getElementById('minUsersFilter');
|
console.log('Loading rooms via WebSocket');
|
||||||
|
|
||||||
currentSortBy = sortSelect ? sortSelect.value : 'activity';
|
|
||||||
currentMinUsers = minUsersFilter ? (minUsersFilter.value || 0) : 0;
|
|
||||||
|
|
||||||
if (typeof socket !== 'undefined' && socket !== null && socket.connected) {
|
|
||||||
socket.emit('request_public_rooms', {
|
socket.emit('request_public_rooms', {
|
||||||
sort: currentSortBy,
|
sort: currentSortBy,
|
||||||
min_users: currentMinUsers,
|
min_users: currentMinUsers,
|
||||||
limit: 50
|
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 {
|
} else {
|
||||||
// Fallback to HTTP API if WebSocket not available
|
console.log('WebSocket not available, using HTTP fallback');
|
||||||
loadPublicRoomsHTTP();
|
loadPublicRoomsHTTP();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error requesting public rooms via WebSocket:', error);
|
console.error('Error requesting public rooms via WebSocket:', error);
|
||||||
loadPublicRoomsHTTP(); // Fallback to HTTP
|
loadPublicRoomsHTTP();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback HTTP method
|
// Update filters from UI elements
|
||||||
async function loadPublicRoomsHTTP() {
|
function updateFiltersFromUI() {
|
||||||
try {
|
|
||||||
showLoadingState();
|
|
||||||
|
|
||||||
const sortSelect = document.getElementById('roomsSortSelect');
|
const sortSelect = document.getElementById('roomsSortSelect');
|
||||||
const minUsersFilter = document.getElementById('minUsersFilter');
|
const minUsersFilter = document.getElementById('minUsersFilter');
|
||||||
|
|
||||||
const sortBy = sortSelect ? sortSelect.value : 'activity';
|
currentSortBy = sortSelect?.value || 'activity';
|
||||||
const minUsers = minUsersFilter ? (minUsersFilter.value || 0) : 0;
|
currentMinUsers = parseInt(minUsersFilter?.value || '0') || 0;
|
||||||
|
|
||||||
const response = await fetch(`/api/rooms/public?sort=${sortBy}&min_users=${minUsers}&limit=50`);
|
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();
|
||||||
|
|
||||||
|
console.log(`Loading rooms via HTTP (attempt ${retryAttempts + 1}/${MAX_RETRY_ATTEMPTS})`);
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/rooms/public?sort=${encodeURIComponent(currentSortBy)}&min_users=${currentMinUsers}&limit=50`,
|
||||||
|
{ signal: controller.signal }
|
||||||
|
);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP ${response.status}`);
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
publicRoomsData = data.rooms || [];
|
|
||||||
|
|
||||||
// Also load stats
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load room statistics
|
||||||
|
async function loadRoomStats() {
|
||||||
try {
|
try {
|
||||||
const statsResponse = await fetch('/api/rooms/stats');
|
const statsResponse = await fetch('/api/rooms/stats');
|
||||||
if (statsResponse.ok) {
|
if (statsResponse.ok) {
|
||||||
const stats = await statsResponse.json();
|
const stats = await statsResponse.json();
|
||||||
updateStatsDisplay(stats);
|
updateStatsDisplay(stats);
|
||||||
}
|
}
|
||||||
} catch (statsError) {
|
|
||||||
console.warn('Failed to load room stats:', statsError);
|
|
||||||
}
|
|
||||||
|
|
||||||
displayRooms();
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading public rooms via HTTP:', error);
|
console.warn('Failed to load room stats:', error);
|
||||||
showErrorState();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh public rooms
|
// Refresh public rooms with user feedback
|
||||||
function refreshPublicRooms() {
|
function refreshPublicRooms() {
|
||||||
loadPublicRoomsWebSocket();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stats display
|
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) {
|
function updateStatsDisplay(stats) {
|
||||||
const statsElement = document.getElementById('roomsStats');
|
const statsElement = document.getElementById('roomsStats');
|
||||||
if (statsElement && stats) {
|
if (!statsElement || !stats) return;
|
||||||
statsElement.textContent = `${stats.public_rooms || 0} public rooms • ${stats.total_users || 0} users online`;
|
|
||||||
}
|
const publicRooms = stats.public_rooms || 0;
|
||||||
|
const totalUsers = stats.total_users || 0;
|
||||||
|
const lastUpdated = new Date().toLocaleTimeString();
|
||||||
|
|
||||||
|
statsElement.innerHTML = `
|
||||||
|
<span>${publicRooms} public room${publicRooms !== 1 ? 's' : ''}</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>${totalUsers} user${totalUsers !== 1 ? 's' : ''} online</span>
|
||||||
|
<span style="color: #666; font-size: 0.85em;">• Updated ${lastUpdated}</span>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loading state
|
// Enhanced loading state
|
||||||
function showLoadingState() {
|
function showLoadingState() {
|
||||||
const loadingElement = document.getElementById('roomsLoading');
|
const loadingElement = document.getElementById('roomsLoading');
|
||||||
const listElement = document.getElementById('roomsList');
|
const listElement = document.getElementById('roomsList');
|
||||||
const emptyElement = document.getElementById('roomsEmpty');
|
const emptyElement = document.getElementById('roomsEmpty');
|
||||||
|
|
||||||
if (loadingElement) loadingElement.style.display = 'flex';
|
if (loadingElement) {
|
||||||
|
loadingElement.style.display = 'flex';
|
||||||
|
loadingElement.innerHTML = `
|
||||||
|
<div style="display: flex; align-items: center; gap: 1rem; color: #888;">
|
||||||
|
<div class="loading-spinner" style="
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
border-top: 2px solid #00ff88;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
"></div>
|
||||||
|
<span>Loading public rooms...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
if (listElement) listElement.style.display = 'none';
|
if (listElement) listElement.style.display = 'none';
|
||||||
if (emptyElement) emptyElement.style.display = 'none';
|
if (emptyElement) emptyElement.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show error state
|
// Enhanced error state with retry options
|
||||||
function showErrorState() {
|
function showErrorState() {
|
||||||
const loadingElement = document.getElementById('roomsLoading');
|
const loadingElement = document.getElementById('roomsLoading');
|
||||||
const listElement = document.getElementById('roomsList');
|
const listElement = document.getElementById('roomsList');
|
||||||
@@ -146,18 +304,29 @@ function showErrorState() {
|
|||||||
|
|
||||||
if (loadingElement) loadingElement.style.display = 'none';
|
if (loadingElement) loadingElement.style.display = 'none';
|
||||||
if (listElement) listElement.style.display = 'none';
|
if (listElement) listElement.style.display = 'none';
|
||||||
|
|
||||||
if (emptyElement) {
|
if (emptyElement) {
|
||||||
emptyElement.style.display = 'flex';
|
emptyElement.style.display = 'flex';
|
||||||
emptyElement.innerHTML = `
|
emptyElement.innerHTML = `
|
||||||
|
<div style="text-align: center; max-width: 400px;">
|
||||||
<div style="font-size: 3rem; margin-bottom: 1rem;">⚠️</div>
|
<div style="font-size: 3rem; margin-bottom: 1rem;">⚠️</div>
|
||||||
<h3 style="margin: 0 0 0.5rem 0; color: #ffffff;">Failed to Load Rooms</h3>
|
<h3 style="margin: 0 0 0.5rem 0; color: #ffffff;">Failed to Load Rooms</h3>
|
||||||
<p style="margin: 0 0 1rem 0;">Unable to connect to the server. Please try again.</p>
|
<p style="margin: 0 0 1.5rem 0; color: #888; line-height: 1.5;">
|
||||||
<button class="btn" onclick="refreshPublicRooms()">Retry</button>
|
Unable to connect to the server. This could be due to network issues or server maintenance.
|
||||||
|
</p>
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap;">
|
||||||
|
<button class="btn" onclick="refreshPublicRooms()">Try Again</button>
|
||||||
|
<button class="btn btn-secondary" onclick="loadPublicRoomsHTTP()">Force HTTP</button>
|
||||||
|
</div>
|
||||||
|
<p style="margin-top: 1rem; color: #666; font-size: 0.85em;">
|
||||||
|
Attempted ${retryAttempts}/${MAX_RETRY_ATTEMPTS} retries
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display rooms
|
// Enhanced room display with better error handling
|
||||||
function displayRooms() {
|
function displayRooms() {
|
||||||
const roomsList = document.getElementById('roomsList');
|
const roomsList = document.getElementById('roomsList');
|
||||||
const roomsLoading = document.getElementById('roomsLoading');
|
const roomsLoading = document.getElementById('roomsLoading');
|
||||||
@@ -165,8 +334,20 @@ function displayRooms() {
|
|||||||
|
|
||||||
if (roomsLoading) roomsLoading.style.display = 'none';
|
if (roomsLoading) roomsLoading.style.display = 'none';
|
||||||
|
|
||||||
if (!publicRoomsData || publicRoomsData.length === 0) {
|
if (!Array.isArray(publicRoomsData) || publicRoomsData.length === 0) {
|
||||||
if (roomsEmpty) roomsEmpty.style.display = 'flex';
|
if (roomsEmpty) {
|
||||||
|
roomsEmpty.style.display = 'flex';
|
||||||
|
roomsEmpty.innerHTML = `
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<div style="font-size: 2.5rem; margin-bottom: 1rem;">🏠</div>
|
||||||
|
<h3 style="margin: 0 0 0.5rem 0; color: #ffffff;">No Public Rooms Found</h3>
|
||||||
|
<p style="margin: 0 0 1rem 0; color: #888;">
|
||||||
|
${currentMinUsers > 0 ? `Try reducing the minimum users filter (currently ${currentMinUsers})` : 'Check back later or create your own room'}
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-secondary" onclick="refreshPublicRooms()">Refresh</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
if (roomsList) roomsList.style.display = 'none';
|
if (roomsList) roomsList.style.display = 'none';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -174,56 +355,112 @@ function displayRooms() {
|
|||||||
if (roomsEmpty) roomsEmpty.style.display = 'none';
|
if (roomsEmpty) roomsEmpty.style.display = 'none';
|
||||||
if (roomsList) {
|
if (roomsList) {
|
||||||
roomsList.style.display = 'block';
|
roomsList.style.display = 'block';
|
||||||
roomsList.innerHTML = publicRoomsData.map(room => createRoomCard(room)).join('');
|
|
||||||
|
try {
|
||||||
|
const roomsHtml = publicRoomsData.map(room => createRoomCard(room)).join('');
|
||||||
|
roomsList.innerHTML = roomsHtml;
|
||||||
|
console.log(`Displayed ${publicRoomsData.length} rooms`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error rendering rooms:', error);
|
||||||
|
showErrorState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create room card HTML
|
// Enhanced room card with better data handling
|
||||||
function createRoomCard(room) {
|
function createRoomCard(room) {
|
||||||
if (!room) return '';
|
if (!room || typeof room !== 'object') {
|
||||||
|
console.warn('Invalid room data:', room);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
const activityClass = getActivityClass(room.minutes_since_activity || 0);
|
const roomId = sanitizeText(room.room_id || 'unknown');
|
||||||
const timeAgo = formatTimeAgo(room.minutes_since_activity || 0);
|
const title = sanitizeText(room.title || room.room_id || 'Unnamed Room');
|
||||||
|
const description = sanitizeText(room.description || 'No description available');
|
||||||
|
const userCount = Math.max(0, parseInt(room.user_count) || 0);
|
||||||
|
const messageCount = Math.max(0, parseInt(room.message_count) || 0);
|
||||||
|
const minutesAgo = Math.max(0, parseInt(room.minutes_since_activity) || 0);
|
||||||
|
|
||||||
|
const activityClass = getActivityClass(minutesAgo);
|
||||||
|
const timeAgo = formatTimeAgo(minutesAgo);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="room-card" onclick="joinPublicRoom('${escapeHtml(room.room_id || '')}')">
|
<div class="room-card"
|
||||||
<div class="room-title">
|
onclick="joinPublicRoom('${escapeHtml(roomId)}')"
|
||||||
${escapeHtml(room.title || room.room_id || 'Unnamed Room')}
|
style="
|
||||||
<span class="activity-indicator ${activityClass}"></span>
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: #2a2a2a;
|
||||||
|
"
|
||||||
|
onmouseover="this.style.background='#333'; this.style.borderColor='#555';"
|
||||||
|
onmouseout="this.style.background='#2a2a2a'; this.style.borderColor='#333';">
|
||||||
|
|
||||||
|
<div class="room-title" style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
">
|
||||||
|
${title}
|
||||||
|
<span class="activity-indicator ${activityClass}" style="
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="room-description">
|
|
||||||
${escapeHtml(room.description || 'No description')}
|
<div class="room-description" style="
|
||||||
</div>
|
color: #b0b0b0;
|
||||||
<div class="room-stats">
|
margin-bottom: 1rem;
|
||||||
<div class="room-stat">
|
line-height: 1.4;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
">${description}</div>
|
||||||
|
|
||||||
|
<div class="room-stats" style="
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #888;
|
||||||
|
">
|
||||||
|
<div class="room-stat" style="display: flex; align-items: center; gap: 0.25rem;">
|
||||||
<span>👥</span>
|
<span>👥</span>
|
||||||
<span>${room.user_count || 0} user${(room.user_count || 0) !== 1 ? 's' : ''}</span>
|
<span>${userCount} user${userCount !== 1 ? 's' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="room-stat">
|
<div class="room-stat" style="display: flex; align-items: center; gap: 0.25rem;">
|
||||||
<span>💬</span>
|
<span>💬</span>
|
||||||
<span>${room.message_count || 0} message${(room.message_count || 0) !== 1 ? 's' : ''}</span>
|
<span>${messageCount} message${messageCount !== 1 ? 's' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="room-stat">
|
<div class="room-stat" style="display: flex; align-items: center; gap: 0.25rem;">
|
||||||
<span>🕐</span>
|
<span>🕐</span>
|
||||||
<span>Active ${timeAgo}</span>
|
<span>Active ${timeAgo}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="room-stat">
|
<div class="room-stat" style="display: flex; align-items: center; gap: 0.25rem;">
|
||||||
<span>🆔</span>
|
<span>🆔</span>
|
||||||
<span>${escapeHtml(room.room_id || 'Unknown')}</span>
|
<span style="font-family: monospace; font-size: 0.8em;">${roomId}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get activity indicator class
|
// Enhanced activity classification
|
||||||
function getActivityClass(minutesAgo) {
|
function getActivityClass(minutesAgo) {
|
||||||
if (minutesAgo <= 5) return 'activity-active';
|
if (minutesAgo <= 5) return 'activity-active';
|
||||||
if (minutesAgo <= 30) return 'activity-recent';
|
if (minutesAgo <= 30) return 'activity-recent';
|
||||||
|
if (minutesAgo <= 120) return 'activity-moderate';
|
||||||
return 'activity-old';
|
return 'activity-old';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format time ago
|
// Enhanced time formatting
|
||||||
function formatTimeAgo(minutes) {
|
function formatTimeAgo(minutes) {
|
||||||
if (minutes < 1) return 'just now';
|
if (minutes < 1) return 'just now';
|
||||||
if (minutes < 60) return `${Math.floor(minutes)}m ago`;
|
if (minutes < 60) return `${Math.floor(minutes)}m ago`;
|
||||||
@@ -232,21 +469,37 @@ function formatTimeAgo(minutes) {
|
|||||||
if (hours < 24) return `${hours}h ago`;
|
if (hours < 24) return `${hours}h ago`;
|
||||||
|
|
||||||
const days = Math.floor(hours / 24);
|
const days = Math.floor(hours / 24);
|
||||||
return `${days}d ago`;
|
if (days < 7) return `${days}d ago`;
|
||||||
|
|
||||||
|
const weeks = Math.floor(days / 7);
|
||||||
|
return `${weeks}w ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape HTML to prevent XSS
|
// Enhanced text sanitization
|
||||||
|
function sanitizeText(text) {
|
||||||
|
if (typeof text !== 'string') return '';
|
||||||
|
return text.trim().substring(0, 200); // Limit length and trim
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced HTML escaping
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = text;
|
div.textContent = String(text);
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join a public room - integrated with your existing system
|
// Enhanced room joining with validation
|
||||||
function joinPublicRoom(roomId) {
|
function joinPublicRoom(roomId) {
|
||||||
if (!roomId) {
|
if (!roomId || typeof roomId !== 'string') {
|
||||||
console.error('No room ID provided');
|
console.error('Invalid room ID provided:', roomId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate room ID format
|
||||||
|
const roomIdPattern = /^[a-zA-Z0-9\-_]{1,32}$/;
|
||||||
|
if (!roomIdPattern.test(roomId)) {
|
||||||
|
console.error('Invalid room ID format:', roomId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,52 +508,65 @@ function joinPublicRoom(roomId) {
|
|||||||
// Close the browser
|
// Close the browser
|
||||||
closePublicRoomsBrowser();
|
closePublicRoomsBrowser();
|
||||||
|
|
||||||
// Fill in the room input with the selected room
|
// Fill in the room input
|
||||||
const roomInput = document.getElementById('roomInput');
|
const roomInput = document.getElementById('roomInput');
|
||||||
if (roomInput) {
|
if (roomInput) {
|
||||||
roomInput.value = roomId;
|
roomInput.value = roomId;
|
||||||
|
roomInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear password field since these are public rooms
|
// Clear password field for public rooms
|
||||||
const roomPasswordInput = document.getElementById('roomPasswordInput');
|
const roomPasswordInput = document.getElementById('roomPasswordInput');
|
||||||
if (roomPasswordInput) {
|
if (roomPasswordInput) {
|
||||||
roomPasswordInput.value = '';
|
roomPasswordInput.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger your existing join room functionality
|
// Trigger join room functionality with delay to ensure UI updates
|
||||||
|
setTimeout(() => {
|
||||||
const joinButton = document.getElementById('joinRoomBtn');
|
const joinButton = document.getElementById('joinRoomBtn');
|
||||||
if (joinButton) {
|
if (joinButton) {
|
||||||
joinButton.click();
|
joinButton.click();
|
||||||
|
} else {
|
||||||
|
console.error('Join button not found');
|
||||||
}
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSocket event handlers for real-time updates
|
// Enhanced WebSocket handlers
|
||||||
function setupPublicRoomsWebSocketHandlers() {
|
function setupPublicRoomsWebSocketHandlers() {
|
||||||
if (typeof socket === 'undefined' || socket === null) {
|
if (!isSocketAvailable()) {
|
||||||
console.log('Socket not available, using HTTP fallback');
|
console.log('Socket not available, WebSocket handlers not attached');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle public rooms data response
|
// Handle public rooms data response
|
||||||
socket.on('public_rooms_data', function(data) {
|
socket.on('public_rooms_data', function(data) {
|
||||||
console.log('Received public rooms data via WebSocket:', data);
|
console.log('Received public rooms data via WebSocket:', data);
|
||||||
publicRoomsData = data && data.rooms ? data.rooms : [];
|
|
||||||
|
if (data && Array.isArray(data.rooms)) {
|
||||||
|
publicRoomsData = data.rooms;
|
||||||
|
} else {
|
||||||
|
console.warn('Invalid public rooms data format');
|
||||||
|
publicRoomsData = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (data && data.stats) {
|
if (data && data.stats) {
|
||||||
updateStatsDisplay(data.stats);
|
updateStatsDisplay(data.stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isRoomsBrowserOpen) {
|
||||||
displayRooms();
|
displayRooms();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle live updates to public rooms
|
// Handle live updates
|
||||||
socket.on('public_rooms_updated', function(data) {
|
socket.on('public_rooms_updated', function(data) {
|
||||||
console.log('Received live public rooms update:', data);
|
console.log('Received live public rooms update:', data);
|
||||||
|
|
||||||
// Only update if the browser is currently open and we're subscribed
|
if (isRoomsBrowserOpen && isSubscribedToRooms) {
|
||||||
const browserElement = document.getElementById('publicRoomsBrowser');
|
if (data && Array.isArray(data.rooms)) {
|
||||||
if (browserElement && browserElement.style.display === 'block' && isSubscribedToRooms) {
|
publicRoomsData = data.rooms;
|
||||||
publicRoomsData = data && data.rooms ? data.rooms : [];
|
}
|
||||||
|
|
||||||
if (data && data.stats) {
|
if (data && data.stats) {
|
||||||
updateStatsDisplay(data.stats);
|
updateStatsDisplay(data.stats);
|
||||||
@@ -310,57 +576,89 @@ function setupPublicRoomsWebSocketHandlers() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle WebSocket errors for public rooms
|
// Handle WebSocket errors
|
||||||
socket.on('public_rooms_error', function(data) {
|
socket.on('public_rooms_error', function(data) {
|
||||||
console.error('Public rooms WebSocket error:', data);
|
console.error('Public rooms WebSocket error:', data);
|
||||||
|
if (isRoomsBrowserOpen) {
|
||||||
showErrorState();
|
showErrorState();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Public rooms WebSocket handlers attached');
|
// Handle connection events
|
||||||
|
socket.on('connect', function() {
|
||||||
|
console.log('Socket connected, resubscribing if browser is open');
|
||||||
|
if (isRoomsBrowserOpen && !isSubscribedToRooms) {
|
||||||
|
subscribeToPublicRooms();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
console.log('Socket disconnected');
|
||||||
|
isSubscribedToRooms = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Enhanced public rooms WebSocket handlers attached');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-setup WebSocket handlers when page loads
|
// Enhanced socket initialization
|
||||||
function waitForSocketAndSetupHandlers(retryCount = 0) {
|
function waitForSocketAndSetupHandlers(retryCount = 0) {
|
||||||
// Check if socket is properly initialized and connected
|
if (isSocketAvailable()) {
|
||||||
if (typeof socket !== 'undefined' && socket !== null && typeof socket.connected !== 'undefined' && socket.connected) {
|
|
||||||
setupPublicRoomsWebSocketHandlers();
|
setupPublicRoomsWebSocketHandlers();
|
||||||
console.log('WebSocket handlers setup successfully');
|
console.log('WebSocket handlers setup successfully');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've retried too many times, give up and use HTTP only
|
|
||||||
if (retryCount > 100) { // 100 * 250ms = 25 seconds max wait
|
if (retryCount > 100) { // 100 * 250ms = 25 seconds max wait
|
||||||
console.warn('Socket failed to initialize after 25 seconds, falling back to HTTP-only mode');
|
console.warn('Socket failed to initialize after 25 seconds, using HTTP-only mode');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log current socket state for debugging
|
|
||||||
if (retryCount % 20 === 0) { // Log every 5 seconds
|
if (retryCount % 20 === 0) { // Log every 5 seconds
|
||||||
console.log(`Waiting for socket... (attempt ${retryCount + 1})`);
|
console.log(`Waiting for socket... (attempt ${retryCount + 1})`);
|
||||||
console.log('Socket type:', typeof socket);
|
|
||||||
console.log('Socket value:', socket);
|
|
||||||
console.log('Socket.io library loaded:', typeof io !== 'undefined');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue waiting
|
|
||||||
setTimeout(() => waitForSocketAndSetupHandlers(retryCount + 1), 250);
|
setTimeout(() => waitForSocketAndSetupHandlers(retryCount + 1), 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event listeners for controls
|
// Enhanced event listeners with better error handling
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Add null checks for DOM elements
|
console.log('Setting up public rooms browser event listeners');
|
||||||
const sortSelect = document.getElementById('roomsSortSelect');
|
|
||||||
const minUsersFilter = document.getElementById('minUsersFilter');
|
|
||||||
|
|
||||||
|
// Browse button (this was missing!)
|
||||||
|
const browseButton = document.getElementById('browsePublicRoomsBtn');
|
||||||
|
if (browseButton) {
|
||||||
|
browseButton.addEventListener('click', showPublicRoomsBrowser);
|
||||||
|
console.log('Browse public rooms button listener attached');
|
||||||
|
} else {
|
||||||
|
console.warn('Browse public rooms button not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
const closeButton = document.getElementById('closePublicRoomsBrowserBtn');
|
||||||
|
if (closeButton) {
|
||||||
|
closeButton.addEventListener('click', closePublicRoomsBrowser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort selector
|
||||||
|
const sortSelect = document.getElementById('roomsSortSelect');
|
||||||
if (sortSelect) {
|
if (sortSelect) {
|
||||||
sortSelect.addEventListener('change', refreshPublicRooms);
|
sortSelect.addEventListener('change', refreshPublicRooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Min users filter
|
||||||
|
const minUsersFilter = document.getElementById('minUsersFilter');
|
||||||
if (minUsersFilter) {
|
if (minUsersFilter) {
|
||||||
minUsersFilter.addEventListener('change', refreshPublicRooms);
|
minUsersFilter.addEventListener('change', refreshPublicRooms);
|
||||||
|
minUsersFilter.addEventListener('input', debounce(refreshPublicRooms, 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup backdrop click handler
|
// Refresh button
|
||||||
|
const refreshButton = document.getElementById('refreshPublicRoomsBtn');
|
||||||
|
if (refreshButton) {
|
||||||
|
refreshButton.addEventListener('click', refreshPublicRooms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backdrop click handler
|
||||||
const browserElement = document.getElementById('publicRoomsBrowser');
|
const browserElement = document.getElementById('publicRoomsBrowser');
|
||||||
if (browserElement) {
|
if (browserElement) {
|
||||||
browserElement.addEventListener('click', function(e) {
|
browserElement.addEventListener('click', function(e) {
|
||||||
@@ -370,22 +668,70 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the WebSocket setup process
|
// Keyboard shortcuts
|
||||||
waitForSocketAndSetupHandlers();
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (!isRoomsBrowserOpen) return;
|
||||||
|
|
||||||
// Auto-refresh every 30 seconds when browser is open
|
if (e.key === 'Escape') {
|
||||||
setInterval(function() {
|
e.preventDefault();
|
||||||
const browserElement = document.getElementById('publicRoomsBrowser');
|
closePublicRoomsBrowser();
|
||||||
if (browserElement && browserElement.style.display === 'block') {
|
} else if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
|
||||||
|
e.preventDefault();
|
||||||
refreshPublicRooms();
|
refreshPublicRooms();
|
||||||
}
|
}
|
||||||
}, 30000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close on escape key
|
// Start WebSocket setup
|
||||||
document.addEventListener('keydown', function(e) {
|
waitForSocketAndSetupHandlers();
|
||||||
const browserElement = document.getElementById('publicRoomsBrowser');
|
|
||||||
if (e.key === 'Escape' && browserElement && browserElement.style.display === 'block') {
|
console.log('Public rooms browser setup complete');
|
||||||
closePublicRoomsBrowser();
|
});
|
||||||
|
|
||||||
|
// Utility: Debounce function
|
||||||
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
func.apply(this, args);
|
||||||
|
};
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup on page unload
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (isSubscribedToRooms) {
|
||||||
|
unsubscribeFromPublicRooms();
|
||||||
|
}
|
||||||
|
if (roomsRefreshInterval) {
|
||||||
|
clearInterval(roomsRefreshInterval);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add CSS for activity indicators if not already present
|
||||||
|
if (!document.getElementById('roomsActivityStyles')) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'roomsActivityStyles';
|
||||||
|
style.textContent = `
|
||||||
|
.activity-active {
|
||||||
|
background-color: #00ff88 !important;
|
||||||
|
box-shadow: 0 0 6px rgba(0, 255, 136, 0.6);
|
||||||
|
}
|
||||||
|
.activity-recent {
|
||||||
|
background-color: #ffaa00 !important;
|
||||||
|
}
|
||||||
|
.activity-moderate {
|
||||||
|
background-color: #888 !important;
|
||||||
|
}
|
||||||
|
.activity-old {
|
||||||
|
background-color: #444 !important;
|
||||||
|
}
|
||||||
|
.room-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user