Browse Source

Fix cross-device sync issues

Key fixes:
1. Add pagehide handler for reliable mobile saves
2. Add visibilitychange handler - refresh stats when tab becomes visible
3. Add cache: 'no-store' to all stats API calls to bypass browser cache
4. Mount service-worker.js in docker-compose (was using stale cached version)

This ensures:
- Mobile saves reliably when switching apps or closing
- Desktop fetches fresh data when tab becomes active
- Browser cache never serves stale API responses

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
master
HikeMap User 1 month ago
parent
commit
1290ad4d8d
  1. 1
      docker-compose.yml
  2. 64
      index.html

1
docker-compose.yml

@ -8,6 +8,7 @@ services:
- ./admin.html:/app/admin.html:ro - ./admin.html:/app/admin.html:ro
- ./server.js:/app/server.js:ro - ./server.js:/app/server.js:ro
- ./database.js:/app/database.js:ro - ./database.js:/app/database.js:ro
- ./service-worker.js:/app/service-worker.js:ro
- ./:/app/data - ./:/app/data
- ./mapgameimgs:/app/mapgameimgs - ./mapgameimgs:/app/mapgameimgs
- ./mapgamemusic:/app/mapgamemusic - ./mapgamemusic:/app/mapgamemusic

64
index.html

@ -11236,7 +11236,8 @@
// Check if user has a character // Check if user has a character
try { try {
const hasCharResponse = await fetch('/api/user/has-character', { const hasCharResponse = await fetch('/api/user/has-character', {
headers: { 'Authorization': `Bearer ${token}` }
headers: { 'Authorization': `Bearer ${token}` },
cache: 'no-store'
}); });
if (hasCharResponse.ok) { if (hasCharResponse.ok) {
@ -11253,17 +11254,18 @@
console.error('Failed to check character status:', e); 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 { try {
const response = await fetch('/api/user/rpg-stats', { 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) { if (response.ok) {
const serverStats = await response.json(); const serverStats = await response.json();
if (serverStats && serverStats.name) { if (serverStats && serverStats.name) {
playerStats = serverStats; 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 // Mark that we've loaded from server - safe to save now
statsLoadedFromServer = true; statsLoadedFromServer = true;
@ -11325,7 +11327,8 @@
try { try {
const response = await fetch('/api/user/rpg-stats', { const response = await fetch('/api/user/rpg-stats', {
headers: { 'Authorization': `Bearer ${token}` }
headers: { 'Authorization': `Bearer ${token}` },
cache: 'no-store'
}); });
if (response.ok) { 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 // Note: startAutoSave() is now called in initializePlayerStats() after server data loads
// This prevents auto-save from running before we have valid server data // This prevents auto-save from running before we have valid server data

Loading…
Cancel
Save