diff --git a/extension/audio-options.js b/extension/audio-options.js index a45d4fa..5342377 100644 --- a/extension/audio-options.js +++ b/extension/audio-options.js @@ -126,17 +126,13 @@ function setCustomParam(param, value) { } async function init() { - let audioData = (await chrome.storage.local.get(['audioSettings'])).audioSettings; - const syncData = await chrome.storage.sync.get(['audioSettings', 'locale']); - if (!audioData && syncData.audioSettings) { - audioData = syncData.audioSettings; - await chrome.storage.local.set({ audioSettings: audioData }); - } - const lang = syncData.locale || getSystemLanguage(); + // Local-only: audioSettings/locale are never read from storage.sync. + const { audioSettings, locale } = await chrome.storage.local.get(['audioSettings', 'locale']); + const lang = locale || getSystemLanguage(); await loadLocale(lang); translateDOM(); - currentSettings = mergeAudioSettings(audioData); + currentSettings = mergeAudioSettings(audioSettings); render(); } diff --git a/extension/background.js b/extension/background.js index 3d668d6..14a7887 100644 --- a/extension/background.js +++ b/extension/background.js @@ -43,11 +43,13 @@ async function initUninstallURL() { chrome.runtime.onInstalled.addListener((details) => { if (details.reason === 'install' || details.reason === 'update') { initUninstallURL(); + purgeLegacySyncKeys(); } }); chrome.runtime.onStartup.addListener(() => { initUninstallURL(); + purgeLegacySyncKeys(); }); // --- State Management --- @@ -286,29 +288,14 @@ async function getPeerId() { } async function getSettings() { - // Try local (per-device) first, fall back to sync for migration - let data = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username']); - let migrated = false; - if (!data.username) { - const syncData = await chrome.storage.sync.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username']); - if (syncData.username || syncData.roomId) { - data = syncData; - migrated = true; - } - } + // Local-only by design. Room credentials (roomId/password) and identity + // (username) must NEVER come from storage.sync — syncing them across devices + // both leaks them and resurrects dead rooms on reinstall (a fresh install + // has empty local storage but sync survives in the user's Google account). + const data = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username']); let username = data.username; if (!username) { username = generateUsername(); - } - if (migrated) { - await chrome.storage.local.set({ - serverUrl: data.serverUrl || '', - useCustomServer: data.useCustomServer || false, - roomId: data.roomId || '', - password: data.password || '', - username - }); - } else if (!data.username) { await chrome.storage.local.set({ username }); } return { @@ -320,6 +307,19 @@ async function getSettings() { }; } +// Privacy + correctness: only onboardingComplete and dismissedHints belong in +// storage.sync. Everything else is per-device local storage. This actively +// removes legacy keys that older versions wrote to sync (and that would +// otherwise be redistributed across devices and resurrected on reinstall). +const LEGACY_SYNC_KEYS = [ + 'serverUrl', 'useCustomServer', 'roomId', 'password', 'username', + 'filterNoise', 'autoSyncNextEpisode', 'forceSyncMode', + 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings' +]; +function purgeLegacySyncKeys() { + chrome.storage.sync.remove(LEGACY_SYNC_KEYS).catch(() => {}); +} + function addLog(message, type = 'info') { const log = { timestamp: new Date().toISOString(), @@ -1494,14 +1494,8 @@ function resetAudioProcessingInTab(tabId) { async function applyAudioSettingsToTab(tabId) { if (!tabId) return; - let data = (await chrome.storage.local.get(['audioSettings'])); - if (!data.audioSettings) { - const syncData = await chrome.storage.sync.get(['audioSettings']); - if (syncData.audioSettings) { - data = syncData; - await chrome.storage.local.set({ audioSettings: syncData.audioSettings }); - } - } + // Local-only: audioSettings are never read from storage.sync. + const data = await chrome.storage.local.get(['audioSettings']); chrome.tabs.sendMessage(tabId, { action: 'APPLY_AUDIO_SETTINGS', settings: data.audioSettings diff --git a/extension/content.js b/extension/content.js index e53e488..aa6458f 100644 --- a/extension/content.js +++ b/extension/content.js @@ -89,25 +89,8 @@ let _audioSettings = null; let _audioProcessingAllowed = true; - // Cache the autoSyncNextEpisode setting + // Cache the autoSyncNextEpisode setting (local-only; never read from sync) chrome.storage.local.get(['autoSyncNextEpisode', 'audioSettings'], (data) => { - if (data.autoSyncNextEpisode === undefined || data.audioSettings === undefined) { - chrome.storage.sync.get(['autoSyncNextEpisode', 'audioSettings'], (syncData) => { - const migrate = {}; - if (data.autoSyncNextEpisode === undefined && syncData.autoSyncNextEpisode !== undefined) { - migrate.autoSyncNextEpisode = syncData.autoSyncNextEpisode; - } - if (data.audioSettings === undefined && syncData.audioSettings !== undefined) { - migrate.audioSettings = syncData.audioSettings; - } - if (Object.keys(migrate).length) chrome.storage.local.set(migrate); - _autoSyncEnabled = syncData.autoSyncNextEpisode !== false; - _audioSettings = mergeAudioSettings(syncData.audioSettings); - const v = findVideo(); - if (v && _audioProcessingAllowed) applyAudioSettings(v, _audioSettings); - }); - return; - } _autoSyncEnabled = data.autoSyncNextEpisode !== false; _audioSettings = mergeAudioSettings(data.audioSettings); const video = findVideo(); diff --git a/extension/popup.js b/extension/popup.js index 764ddf1..5f5467f 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -175,19 +175,9 @@ function setRoomRefreshCooldown() { // --- Initialization --- async function init() { + // Local-only by design — settings and room credentials never come from + // storage.sync (only onboardingComplete + dismissedHints live there). const localData = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'activeTab']); - // Migrate preferences from sync → local for existing users - const oldSync = await chrome.storage.sync.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings']); - const toMigrate = {}; - for (const key of ['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings']) { - if (localData[key] === undefined && oldSync[key] !== undefined) { - toMigrate[key] = oldSync[key]; - localData[key] = oldSync[key]; - } - } - if (Object.keys(toMigrate).length) { - await chrome.storage.local.set(toMigrate); - } let activeLang = localData.locale; if (!activeLang) {