// overlay.js - WebSocket client for real-time achievement updates class RetroPulseOverlay { constructor() { this.ws = null; this.username = null; this.reconnectInterval = null; this.reconnectDelay = 3000; // 3 seconds this.lastUpdateTime = null; // DOM elements this.connectionStatus = document.getElementById('connection-status'); this.currentGame = document.getElementById('current-game'); this.gameIcon = document.getElementById('game-icon'); this.gameTitle = document.getElementById('game-title'); this.consoleName = document.getElementById('console-name'); this.achievementsList = document.getElementById('achievements-list'); this.errorDisplay = document.getElementById('error-display'); this.errorMessage = document.getElementById('error-message'); this.init(); } init() { // Extract username from URL path const pathParts = window.location.pathname.split('/'); this.username = pathParts[pathParts.length - 1]; if (!this.username || this.username === 'overlay') { this.showError('No username specified in URL'); return; } console.log(`🎮 Initializing RetroPulse overlay for user: ${this.username}`); this.connect(); } connect() { try { // Determine WebSocket URL (ws:// for http, wss:// for https) const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws?user=${encodeURIComponent(this.username)}`; console.log(`🔌 Connecting to WebSocket: ${wsUrl}`); this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { console.log('✅ WebSocket connected'); this.updateConnectionStatus('connected'); // Clear reconnect interval if it exists if (this.reconnectInterval) { clearInterval(this.reconnectInterval); this.reconnectInterval = null; } }; this.ws.onmessage = (event) => { try { const message = JSON.parse(event.data); this.handleMessage(message); } catch (error) { console.error('Error parsing WebSocket message:', error); } }; this.ws.onclose = (event) => { console.log('❌ WebSocket disconnected:', event.code, event.reason); this.updateConnectionStatus('disconnected'); // Don't reconnect if it was a clean close if (event.code !== 1000) { this.scheduleReconnect(); } }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.updateConnectionStatus('disconnected'); }; } catch (error) { console.error('Error creating WebSocket connection:', error); this.showError(`Connection failed: ${error.message}`); } } handleMessage(message) { console.log('📨 Received message:', message); if (message.error) { this.showError(message.error); return; } if (message.type === 'initial' || message.type === 'update') { this.updateOverlay(message.data); } } updateOverlay(data) { if (!data) return; console.log('🔄 Updating overlay with data:', data); // Update last update time this.lastUpdateTime = new Date(); // Hide error display this.errorDisplay.classList.add('hidden'); // Update achievements if (data.recentAchievements && data.recentAchievements.length > 0) { this.updateAchievements(data.recentAchievements); // Update game info from the latest achievement const latestAchievement = data.recentAchievements[0]; if (latestAchievement.gameTitle) { this.updateGameInfo({ title: latestAchievement.gameTitle, icon: latestAchievement.gameIcon }); } } else { // No achievements yet this.achievementsList.innerHTML = '