diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 74dcee86..b985d481 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -685,7 +685,7 @@ class SkipNoticeComponent extends React.Component this.configUpdate(); - Config.configListeners.push(this.configUpdate.bind(this)); + Config.configSyncListeners.push(this.configUpdate.bind(this)); } this.checkToShowFullVideoWarning(); @@ -80,7 +80,7 @@ class SponsorTimeEditComponent extends React.Component unknown>; + configSyncListeners: Array<(changes: StorageChangesObject) => unknown>; defaults: SBConfig; - localConfig: SBConfig; + cachedSyncConfig: SBConfig; + cachedLocalStorage: SBStorage; config: SBConfig; + local: SBStorage; forceUpdate(prop: string): void; } @@ -108,7 +113,7 @@ const Config: SBObject = { /** * Callback function when an option is updated */ - configListeners: [], + configSyncListeners: [], defaults: { userID: null, isVip: false, @@ -270,27 +275,35 @@ const Config: SBObject = { } } }, - localConfig: null, + cachedSyncConfig: null, + cachedLocalStorage: null, config: null, + local: null, forceUpdate }; // Function setup -function configProxy(): SBConfig { - chrome.storage.onChanged.addListener((changes: {[key: string]: chrome.storage.StorageChange}) => { - for (const key in changes) { - Config.localConfig[key] = changes[key].newValue; - } - - for (const callback of Config.configListeners) { - callback(changes); +function configProxy(): { sync: SBConfig, local: SBStorage } { + chrome.storage.onChanged.addListener((changes: {[key: string]: chrome.storage.StorageChange}, areaName) => { + if (areaName === "sync") { + for (const key in changes) { + Config.cachedSyncConfig[key] = changes[key].newValue; + } + + for (const callback of Config.configSyncListeners) { + callback(changes); + } + } else if (areaName === "local") { + for (const key in changes) { + Config.cachedLocalStorage[key] = changes[key].newValue; + } } }); - const handler: ProxyHandler = { + const syncHandler: ProxyHandler = { set(obj: SBConfig, prop: K, value: SBConfig[K]) { - Config.localConfig[prop] = value; + Config.cachedSyncConfig[prop] = value; chrome.storage.sync.set({ [prop]: value @@ -300,7 +313,7 @@ function configProxy(): SBConfig { }, get(obj: SBConfig, prop: K): SBConfig[K] { - const data = Config.localConfig[prop]; + const data = Config.cachedSyncConfig[prop]; return obj[prop] || data; }, @@ -313,25 +326,53 @@ function configProxy(): SBConfig { }; - return new Proxy({handler} as unknown as SBConfig, handler); + const localHandler: ProxyHandler = { + set(obj: SBStorage, prop: K, value: SBStorage[K]) { + Config.cachedLocalStorage[prop] = value; + + chrome.storage.local.set({ + [prop]: value + }); + + return true; + }, + + get(obj: SBStorage, prop: K): SBStorage[K] { + const data = Config.cachedLocalStorage[prop]; + + return obj[prop] || data; + }, + + deleteProperty(obj: SBConfig, prop: keyof SBStorage) { + chrome.storage.local.remove( prop); + + return true; + } + + }; + + return { + sync: new Proxy({handler: syncHandler} as unknown as SBConfig, syncHandler), + local: new Proxy({handler: localHandler} as unknown as SBConfig, localHandler) + }; } function forceUpdate(prop: string): void { chrome.storage.sync.set({ - [prop]: Config.localConfig[prop] + [prop]: Config.cachedSyncConfig[prop] }); } function fetchConfig(): Promise { return new Promise((resolve) => { chrome.storage.sync.get(null, function(items) { - Config.localConfig = items; // Data is ready + Config.cachedSyncConfig = items; // Data is ready resolve(); }); }); } -function migrateOldFormats(config: SBConfig) { +function migrateOldSyncFormats(config: SBConfig) { if (config["segmentTimes"]) { for (const item of config["segmentTimes"]) { config.unsubmittedSegments[item[0]] = item[1]; @@ -428,20 +469,21 @@ async function setupConfig() { await fetchConfig(); addDefaults(); const config = configProxy(); - migrateOldFormats(config); + migrateOldSyncFormats(config.sync); - Config.config = config; + Config.config = config.sync; + Config.local = config.local; } // Add defaults function addDefaults() { for (const key in Config.defaults) { - if(!Object.prototype.hasOwnProperty.call(Config.localConfig, key)) { - Config.localConfig[key] = Config.defaults[key]; + if(!Object.prototype.hasOwnProperty.call(Config.cachedSyncConfig, key)) { + Config.cachedSyncConfig[key] = Config.defaults[key]; } else if (key === "barTypes") { for (const key2 in Config.defaults[key]) { - if(!Object.prototype.hasOwnProperty.call(Config.localConfig[key], key2)) { - Config.localConfig[key][key2] = Config.defaults[key][key2]; + if(!Object.prototype.hasOwnProperty.call(Config.cachedSyncConfig[key], key2)) { + Config.cachedSyncConfig[key][key2] = Config.defaults[key][key2]; } } } diff --git a/src/content.ts b/src/content.ts index 8cbb412e..17e56dc5 100644 --- a/src/content.ts +++ b/src/content.ts @@ -227,8 +227,8 @@ function contentConfigUpdateListener(changes: StorageChangesObject) { } } -if (!Config.configListeners.includes(contentConfigUpdateListener)) { - Config.configListeners.push(contentConfigUpdateListener); +if (!Config.configSyncListeners.includes(contentConfigUpdateListener)) { + Config.configSyncListeners.push(contentConfigUpdateListener); } function resetValues() { diff --git a/src/options.ts b/src/options.ts index d3dcdcad..f759e066 100644 --- a/src/options.ts +++ b/src/options.ts @@ -44,8 +44,8 @@ async function init() { createStickyHeader(); } - if (!Config.configListeners.includes(optionsConfigUpdateListener)) { - Config.configListeners.push(optionsConfigUpdateListener); + if (!Config.configSyncListeners.includes(optionsConfigUpdateListener)) { + Config.configSyncListeners.push(optionsConfigUpdateListener); } await utils.wait(() => Config.config !== null); @@ -499,7 +499,7 @@ function activatePrivateTextChange(element: HTMLElement) { // See if anything extra must be done switch (option) { case "*": { - result = JSON.stringify(Config.localConfig); + result = JSON.stringify(Config.cachedSyncConfig); break; } } @@ -579,7 +579,7 @@ async function setTextOption(option: string, element: HTMLElement, value: string function downloadConfig() { const file = document.createElement("a"); - const jsonData = JSON.parse(JSON.stringify(Config.localConfig)); + const jsonData = JSON.parse(JSON.stringify(Config.cachedSyncConfig)); file.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData))); file.setAttribute("download", "SponsorBlockConfig.json"); document.body.append(file); @@ -633,7 +633,7 @@ function copyDebugOutputToClipboard() { language: navigator.language, extensionVersion: chrome.runtime.getManifest().version }, - config: JSON.parse(JSON.stringify(Config.localConfig)) // Deep clone config object + config: JSON.parse(JSON.stringify(Config.cachedSyncConfig)) // Deep clone config object }; // Sanitise sensitive user config values diff --git a/src/popup.ts b/src/popup.ts index 5dc3bfb5..3ee10c0b 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -360,8 +360,8 @@ async function runThePopup(messageListener?: MessageListener): Promise { // Perform a second update after the config changes take effect as a workaround for a race condition const removeListener = (listener: typeof lateUpdate) => { - const index = Config.configListeners.indexOf(listener); - if (index !== -1) Config.configListeners.splice(index, 1); + const index = Config.configSyncListeners.indexOf(listener); + if (index !== -1) Config.configSyncListeners.splice(index, 1); }; const lateUpdate = () => { @@ -369,7 +369,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { removeListener(lateUpdate); }; - Config.configListeners.push(lateUpdate); + Config.configSyncListeners.push(lateUpdate); // Remove the listener after 200ms in case the changes were propagated by the time we got the response setTimeout(() => removeListener(lateUpdate), 200);