diff --git a/index.html b/index.html index 8d7e8b0..9401e39 100644 --- a/index.html +++ b/index.html @@ -870,6 +870,7 @@ const [lastSyncedFavorites, setLastSyncedFavorites] = useState(() => JSON.parse(localStorage.getItem('th_favorites_synced')) || []); const [quality, setQuality] = useState(() => localStorage.getItem('th_quality') || '320k'); const [syncToken, setSyncToken] = useState(() => localStorage.getItem('th_sync_token') || ''); + const [lastSuccessToken, setLastSuccessToken] = useState(() => localStorage.getItem('th_last_success_token') || ''); // UI State const [showFullPlayer, setShowFullPlayer] = useState(false); @@ -902,22 +903,25 @@ useEffect(() => { localStorage.setItem('th_favorites_synced', JSON.stringify(lastSyncedFavorites)); }, [lastSyncedFavorites]); useEffect(() => { localStorage.setItem('th_quality', quality); }, [quality]); useEffect(() => { localStorage.setItem('th_sync_token', syncToken); }, [syncToken]); + useEffect(() => { localStorage.setItem('th_last_success_token', lastSuccessToken); }, [lastSuccessToken]); // Auto Sync Logic useEffect(() => { - // Only auto-sync if we have a token and there are actual changes compared to last sync - if (syncToken && JSON.stringify(favorites) !== JSON.stringify(lastSyncedFavorites)) { + // Only auto-sync if we have a token, it matches the last successfully synced token, + // and there are actual changes compared to last sync. + // This prevents auto-syncing current favorites to a new token while typing/switching. + if (syncToken && syncToken === lastSuccessToken && JSON.stringify(favorites) !== JSON.stringify(lastSyncedFavorites)) { // Use a small timeout to avoid rapid-fire syncs if user clicks quickly const timer = setTimeout(() => { autoSyncFavorites(); }, 1000); return () => clearTimeout(timer); } - }, [favorites, syncToken, lastSyncedFavorites]); + }, [favorites, syncToken, lastSuccessToken, lastSyncedFavorites]); // 1. Auto Sync: Incremental update based on diff const autoSyncFavorites = async () => { - if (!syncToken) return; + if (!syncToken || syncToken !== lastSuccessToken) return; try { // Get latest remote state const remoteFavorites = await syncService.get('favorites', syncToken) || []; @@ -957,36 +961,59 @@ } }; - // 2. Manual Sync: Merge (Union) - // Used when user clicks "Sync" button or initial load + // 2. Manual Sync const manualSyncFavorites = async () => { if (!syncToken) return; - // Pull Remote - const cloudFavorites = await syncService.get('favorites', syncToken); - - if (cloudFavorites && Array.isArray(cloudFavorites)) { - // Merge Strategy: Union (Local + Remote) - // Since this is a manual sync (often initial), we assume user wants to keep everything - const merged = [...favorites]; - cloudFavorites.forEach(cloudSong => { - if (!merged.find(s => s.id === cloudSong.id)) { - merged.push(cloudSong); - } - }); - - // Update Local - setFavorites(merged); - setLastSyncedFavorites(merged); - - // Push Merged back to server - await syncService.set('favorites', merged, syncToken); - } else { - // If remote is empty/null, push local - if (favorites.length > 0) { - await syncService.set('favorites', favorites, syncToken); - setLastSyncedFavorites(favorites); + // Determine Mode: Switch (Overwrite Local) or Sync (Merge) + const isSwitchingToken = syncToken !== lastSuccessToken; + + if (isSwitchingToken) { + // Mode: Switch Account/Token -> Remote Overwrites Local + try { + const cloudFavorites = await syncService.get('favorites', syncToken); + + // If cloud has data, use it. If null/empty, we assume new empty account. + const newFavorites = Array.isArray(cloudFavorites) ? cloudFavorites : []; + + setFavorites(newFavorites); + setLastSyncedFavorites(newFavorites); + setLastSuccessToken(syncToken); + + } catch (e) { + console.error("Token switch sync failed", e); + throw e; // Let UI show error } + } else { + // Mode: Regular Sync -> Merge / Union + + // Pull Remote + const cloudFavorites = await syncService.get('favorites', syncToken); + + if (cloudFavorites && Array.isArray(cloudFavorites)) { + // Merge: Union + const merged = [...favorites]; + cloudFavorites.forEach(cloudSong => { + if (!merged.find(s => s.id === cloudSong.id)) { + merged.push(cloudSong); + } + }); + + // Update Local + setFavorites(merged); + setLastSyncedFavorites(merged); + + // Push Merged back to server + await syncService.set('favorites', merged, syncToken); + } else { + // Remote is empty, push local to it + if (favorites.length > 0) { + await syncService.set('favorites', favorites, syncToken); + setLastSyncedFavorites(favorites); + } + } + // Ensure token is marked as success + setLastSuccessToken(syncToken); } };