diff --git a/docker-compose.yml b/docker-compose.yml index 2c846e0..fda8e0d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: - ./admin.html:/app/admin.html:ro - ./server.js:/app/server.js:ro - ./database.js:/app/database.js:ro + - ./service-worker.js:/app/service-worker.js:ro - ./:/app/data - ./mapgameimgs:/app/mapgameimgs - ./mapgamemusic:/app/mapgamemusic diff --git a/index.html b/index.html index 436d1c6..ea63b5c 100644 --- a/index.html +++ b/index.html @@ -11236,7 +11236,8 @@ // Check if user has a character try { const hasCharResponse = await fetch('/api/user/has-character', { - headers: { 'Authorization': `Bearer ${token}` } + headers: { 'Authorization': `Bearer ${token}` }, + cache: 'no-store' }); if (hasCharResponse.ok) { @@ -11253,17 +11254,18 @@ console.error('Failed to check character status:', e); } - // Try to load character from server + // Try to load character from server (always fetch fresh, never use cache) try { const response = await fetch('/api/user/rpg-stats', { - headers: { 'Authorization': `Bearer ${token}` } + headers: { 'Authorization': `Bearer ${token}` }, + cache: 'no-store' // Force fresh fetch, bypass all caches }); if (response.ok) { const serverStats = await response.json(); if (serverStats && serverStats.name) { playerStats = serverStats; - console.log('Loaded RPG stats from server:', playerStats); + console.log('Loaded RPG stats from server (fresh):', playerStats); // Mark that we've loaded from server - safe to save now statsLoadedFromServer = true; @@ -11325,7 +11327,8 @@ try { const response = await fetch('/api/user/rpg-stats', { - headers: { 'Authorization': `Bearer ${token}` } + headers: { 'Authorization': `Bearer ${token}` }, + cache: 'no-store' }); if (response.ok) { @@ -12470,6 +12473,57 @@ } }); + // Also save on pagehide (more reliable on mobile) + window.addEventListener('pagehide', () => { + if (playerStats && statsLoadedFromServer) { + const token = localStorage.getItem('accessToken'); + if (token) { + navigator.sendBeacon('/api/user/rpg-stats-beacon', new Blob([JSON.stringify({ + token: token, + stats: playerStats + })], { type: 'application/json' })); + } + } + }); + + // Refresh stats from server when page becomes visible (switching tabs/apps) + document.addEventListener('visibilitychange', async () => { + if (document.visibilityState === 'visible' && currentUser && statsLoadedFromServer) { + console.log('[SYNC] Page visible - refreshing stats from server...'); + const token = localStorage.getItem('accessToken'); + if (!token) return; + + try { + const response = await fetch('/api/user/rpg-stats', { + headers: { 'Authorization': `Bearer ${token}` }, + cache: 'no-store' // Force fresh fetch + }); + + if (response.ok) { + const serverStats = await response.json(); + if (serverStats && serverStats.name) { + // Check if server has newer data + const serverVersion = serverStats.dataVersion || 0; + const localVersion = playerStats?.dataVersion || 0; + + if (serverVersion > localVersion) { + console.log(`[SYNC] Server has newer data (v${serverVersion} > v${localVersion}), updating...`); + playerStats = serverStats; + localStorage.setItem('hikemap_rpg_stats', JSON.stringify(playerStats)); + updateRpgHud(); + updateHomeBaseMarker(); + updateStatus('Stats synced from server', 'info'); + } else { + console.log(`[SYNC] Local data is current (v${localVersion})`); + } + } + } + } catch (e) { + console.error('[SYNC] Failed to refresh stats:', e); + } + } + }); + // Note: startAutoSave() is now called in initializePlayerStats() after server data loads // This prevents auto-save from running before we have valid server data