From 32fde140caae66200c3af8811c9705d341937ee2 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sat, 15 Oct 2022 14:07:21 +0800 Subject: [PATCH 01/14] refactor: add new settings storage --- src/page/App.svelte | 9 +-------- src/page/store.js | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/page/App.svelte b/src/page/App.svelte index 462b664d..9e58dfe8 100644 --- a/src/page/App.svelte +++ b/src/page/App.svelte @@ -41,14 +41,7 @@ onMount(async () => { log.add("Requesting initialization data", "info", false); - const initData = await browser.runtime.sendNativeMessage({name: "PAGE_INIT_DATA"}); - if (initData.error) return console.error(initData.error); - for (const [key, value] of Object.entries(initData)) { - if (value === "true" || value === "false") { - initData[key] = JSON.parse(value); - } - } - settings.set(initData); + if (!settings.init()) return console.error("Initialization failure"); state.add("items-loading"); state.remove("init"); diff --git a/src/page/store.js b/src/page/store.js index f366e2c2..c23462aa 100644 --- a/src/page/store.js +++ b/src/page/store.js @@ -76,7 +76,7 @@ export const state = stateStore(); function settingsStore() { const {subscribe, update, set} = writable({}); - const updateSingleSetting = (key, value) => { + const updateSingleSetting_old = (key, value) => { update(settings => { settings[key] = value; // blacklist not stored in normal setting object in manifest, so handle differently @@ -109,7 +109,42 @@ function settingsStore() { return settings; }); }; - return {subscribe, set, updateSingleSetting}; + const storageKey = "USettings"; + const updateSingleSetting = (key, value) => { + update(settings => { + settings[key] = value; + browser.storage.local.set({[storageKey]: settings}); // Durable Storage + return settings; + }); + // Temporarily keep the old storage method until it is confirmed that all dependencies are removed + updateSingleSetting_old(key, value); + }; + const init_import_from_old = async () => { + log.add("Import settings data from old storage", "info", false); + const initData = await browser.runtime.sendNativeMessage({name: "PAGE_INIT_DATA"}); + if (initData.error) { + console.error(initData.error); + return false; + } + for (const [key, value] of Object.entries(initData)) { + if (value === "true" || value === "false") { + initData[key] = JSON.parse(value); + } + } + set(initData); + browser.storage.local.set({[storageKey]: initData}); + return true; + }; + const init = async () => { + const result = await browser.storage.local.get(storageKey); + if (!result[storageKey]) { + // This will remain for several versions until the majority of users use the new version + return init_import_from_old(); + } + set(result[storageKey]); + return true; + }; + return {subscribe, set, init, updateSingleSetting}; } export const settings = settingsStore(); From 1f53065e207307fd5e523e3e52c3a71f6beed2a1 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sun, 16 Oct 2022 12:41:07 +0800 Subject: [PATCH 02/14] refactor: add new settings define file --- src/shared/settings.js | 250 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 src/shared/settings.js diff --git a/src/shared/settings.js b/src/shared/settings.js new file mode 100644 index 00000000..a0dd2d12 --- /dev/null +++ b/src/shared/settings.js @@ -0,0 +1,250 @@ +const setting_template = { + name: "setting_name", + type: "boolean", + default: true, + writable: true, + platforms: ["macos", "ipados", "ios"], + langLabel: { + en: "engilsh label", + zh_hans: "简体中文标签", + zh_hant: "繁體中文標籤" + }, + langTitle: { + en: "engilsh title", + zh_hans: "简体中文描述", + zh_hant: "簡體中文描述" + }, + group: "General", + legacy: "setting_legacy_name", + nodeType: "Toggle", + nodeClass: {red: false} +}; + +const settings_define = [ + { + name: "global_active", + type: "boolean", + default: true, + platforms: ["macos"], + langLabel: { + en: "Enable Injection", + zh_hans: "启用注入" + }, + langTitle: { + en: "toggle on/off script injection for the pages you visit", + zh_hans: "简体中文描述" + }, + group: "General", + legacy: "active", + nodeType: "Toggle", + nodeClass: {red: false} + }, + { + name: "toolbar_badge_count", + type: "boolean", + default: true, + platforms: ["macos", "ipados"], + langLabel: { + en: "Show Toolbar Count", + zh_hans: "工具栏图标显示计数徽章" + }, + langTitle: { + en: "displays a badge on the toolbar icon with a number that represents how many enabled scripts match the url for the page you are on", + zh_hans: "简体中文描述" + }, + group: "General", + legacy: "showCount", + nodeType: "Toggle" + }, + { + name: "global_scripts_update_check", + type: "boolean", + default: true, + platforms: ["macos", "ipados", "ios"], + langLabel: { + en: "Global scripts update check", + zh_hans: "全局脚本更新检查" + }, + langTitle: { + en: "Whether to enable global periodic script update check", + zh_hans: "是否开启全局定期脚本更新检查" + }, + group: "General", + nodeType: "Toggle" + }, + { + name: "scripts_update_check_interval", + type: "boolean", + default: true, + platforms: ["macos", "ipados", "ios"], + langLabel: { + en: "Scripts update check interval", + zh_hans: "脚本更新检查间隔" + }, + langTitle: { + en: "The interval for script update check in background", + zh_hans: "后台脚本更新检查的间隔时间" + }, + group: "General", + nodeType: "Toggle" + }, + { + name: "scripts_auto_update", + type: "boolean", + default: false, + platforms: ["macos", "ipados", "ios"], + langLabel: { + en: "Scripts silent auto update", + zh_hans: "脚本后台静默自动更新" + }, + langTitle: { + en: "Script silently auto-updates in the background, which is dangerous and may introduce unconfirmed malicious code", + zh_hans: "脚本在后台静默自动更新,这是危险的,可能引入未经确认的恶意代码" + }, + group: "General", + nodeType: "Toggle_double_confirm", + nodeClass: {warn: true} + }, + { + name: "location_path", + type: "boolean", + default: true, + writable: false, + platforms: ["macos", "ipados", "ios"], + langLabel: { + en: "Save Location", + zh_hans: "脚本存储路径" + }, + langTitle: { + en: "where your file are currently located and being saved to (clic to open)", + zh_hans: "简体中文描述" + }, + group: "General", + legacy: "saveLocation", + nodeType: "IconButton" + }, + { + name: "global_exclude_match", + type: "object", + default: [], + platforms: ["macos", "ipados", "ios"], + langLabel: { + en: "Global exclude match patterns", + zh_hans: "全局排除匹配模式列表" + }, + langTitle: { + en: "this input accepts a comma separated list of @match patterns, a page url that matches against a pattern in this list will be ignored for script injection", + zh_hans: "简体中文描述" + }, + group: "General", + legacy: "blacklist", + nodeType: "textarea", + nodeClass: {red: "blacklistError"} + }, + { + name: "editor_close_brackets", + type: "boolean", + default: true, + platforms: ["macos"], + langLabel: { + en: "Auto Close Brackets", + zh_hans: "自动关闭括号" + }, + langTitle: { + en: "toggles on/off auto closing of brackets in the editor, this affects the following characters: () [] {} \"\" ''", + zh_hans: "简体中文描述" + }, + group: "Editor", + legacy: "autoCloseBrackets", + nodeType: "Toggle" + }, + { + name: "editor_hint", + type: "boolean", + default: true, + platforms: ["macos"], + langLabel: { + en: "Auto Hint", + zh_hans: "自动提示(Hint)" + }, + langTitle: { + en: "automatically shows completion hints while editing", + zh_hans: "简体中文描述" + }, + group: "Editor", + legacy: "autoHint", + nodeType: "Toggle" + }, + { + name: "editor_list_descriptions", + type: "boolean", + default: false, + platforms: ["macos"], + langLabel: { + en: "Show List Descriptions", + zh_hans: "显示列表项目描述" + }, + langTitle: { + en: "show or hides the item descriptions in the sidebar", + zh_hans: "简体中文描述" + }, + group: "Editor", + legacy: "descriptions", + nodeType: "Toggle" + }, + { + name: "editor_javascript_lint", + type: "boolean", + default: false, + platforms: ["macos"], + langLabel: { + en: "Javascript Linter", + zh_hans: "Javascript Linter" + }, + langTitle: { + en: "toggles basic Javascript linting within the editor", + zh_hans: "简体中文描述" + }, + group: "Editor", + legacy: "lint", + nodeType: "Toggle" + }, + { + name: "editor_whitespace_show", + type: "boolean", + default: true, + platforms: ["macos"], + langLabel: { + en: "Show whitespace characters", + zh_hans: "显示空白字符" + }, + langTitle: { + en: "toggles the display of invisible characters in the editor", + zh_hans: "简体中文描述" + }, + group: "Editor", + legacy: "showInvisibles", + nodeType: "Toggle" + }, + { + name: "editor_tab_size", + type: "number", + default: 4, + platforms: ["macos"], + langLabel: { + en: "Tab Size", + zh_hans: "制表符大小" + }, + langTitle: { + en: "the number of spaces a tab is equal to while editing", + zh_hans: "简体中文描述" + }, + group: "Editor", + legacy: "tabSize", + nodeType: "select" + } +]; + +export const setting_default = setting_template; + +export const settings = settings_define; \ No newline at end of file From a5f0e2dd89ce8e7f66add6a191913c565ee11496 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sun, 16 Oct 2022 18:26:37 +0800 Subject: [PATCH 03/14] refactor: settings export function --- src/shared/settings.js | 44 ++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/shared/settings.js b/src/shared/settings.js index a0dd2d12..ca5f1b17 100644 --- a/src/shared/settings.js +++ b/src/shared/settings.js @@ -1,26 +1,39 @@ -const setting_template = { - name: "setting_name", +export const settingTemplate = { + name: "setting_template", type: "boolean", default: true, writable: true, platforms: ["macos", "ipados", "ios"], langLabel: { - en: "engilsh label", + en: "Engilsh label", zh_hans: "简体中文标签", zh_hant: "繁體中文標籤" }, langTitle: { - en: "engilsh title", - zh_hans: "简体中文描述", - zh_hant: "簡體中文描述" + en: "Engilsh title", + zh_hans: "简体中文标题", + zh_hant: "繁體中文標題" }, - group: "General", + group: "Template", legacy: "setting_legacy_name", nodeType: "Toggle", nodeClass: {red: false} }; -const settings_define = [ +const settingDefault = { + name: "setting_default", + type: undefined, + default: undefined, + writable: true, + platforms: ["macos", "ipados", "ios"], + langLabel: {}, + langTitle: {}, + group: "", + nodeType: "", + nodeClass: {} +}; + +const settingsDefine = [ { name: "global_active", type: "boolean", @@ -159,7 +172,7 @@ const settings_define = [ nodeType: "Toggle" }, { - name: "editor_hint", + name: "editor_auto_hint", type: "boolean", default: true, platforms: ["macos"], @@ -210,7 +223,7 @@ const settings_define = [ nodeType: "Toggle" }, { - name: "editor_whitespace_show", + name: "editor_show_whitespace", type: "boolean", default: true, platforms: ["macos"], @@ -245,6 +258,13 @@ const settings_define = [ } ]; -export const setting_default = setting_template; +const storagePrefix = "US_"; -export const settings = settings_define; \ No newline at end of file +export const settings = settingsDefine.reduce((arr, val) => { + val.key = storagePrefix + val.name.toUpperCase(); + // arr[val.key] = Object.assign({}, settingDefault, val); + arr[val.key] = new Proxy(val, { + get: (obj, p) => p in obj ? obj[p] : settingDefault[p] + }); + return arr; +}, {}); From 40e8c136f3f3df500d5aa06c98fb8b19b0cc7d21 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:00:52 +0800 Subject: [PATCH 04/14] refactor: add import legacy settings function --- src/shared/settings.js | 81 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/shared/settings.js b/src/shared/settings.js index ca5f1b17..f213b4d0 100644 --- a/src/shared/settings.js +++ b/src/shared/settings.js @@ -34,6 +34,13 @@ const settingDefault = { }; const settingsDefine = [ + { + name: "legacy_imported", + type: "boolean", + default: false, + platforms: ["macos"], + group: "Internal" + }, { name: "global_active", type: "boolean", @@ -120,7 +127,7 @@ const settingsDefine = [ }, { name: "location_path", - type: "boolean", + type: "string", default: true, writable: false, platforms: ["macos", "ipados", "ios"], @@ -260,7 +267,7 @@ const settingsDefine = [ const storagePrefix = "US_"; -export const settings = settingsDefine.reduce((arr, val) => { +export const settingsConfig = settingsDefine.reduce((arr, val) => { val.key = storagePrefix + val.name.toUpperCase(); // arr[val.key] = Object.assign({}, settingDefault, val); arr[val.key] = new Proxy(val, { @@ -268,3 +275,73 @@ export const settings = settingsDefine.reduce((arr, val) => { }); return arr; }, {}); + +const settingsKeys = Object.keys(settingsConfig); + +export async function get(key) { + if (typeof key !== "string") return console.error("key should be a string:", key); + key = storagePrefix + key.toUpperCase(); + if (!settingsKeys.includes(key)) { + return console.error("unexpected settings key:", key); + } + const result = await browser.storage.local.get(key); + return result[key] ?? settingsConfig[key].default; +} + +export async function getAll() { + const result = await browser.storage.local.get(settingsKeys); + const settingsDefault = {}; + for (const key in settingsConfig) { + settingsDefault[key] = settingsConfig[key].default; + } + return Object.assign({}, settingsDefault, result); +} + +export function update(key, value) { + key = storagePrefix + key.toUpperCase(); + if (!settingsKeys.includes(key)) { + return console.error("Unexpected key:", key); + } + const type = settingsConfig[key].type; + if (typeof(value) !== type) { + return console.error(`Unexpected value type ${typeof(value)} should ${type}`); + } + browser.storage.local.set({[key]: value}); + return true; +} + +// Note: It seems unnecessary to use this method, comment it out for now +// export function updateAll(settings) { +// for (const key in settings) { +// if (!settingsKeys.includes(key)) { +// return console.error("unexpected settings key:", key); +// } +// const type = settingsConfig[key].type; +// if (typeof(settings.key) !== type) { +// return console.error(`Unexpected value type ${typeof(value)} should ${type}`); +// } +// } +// browser.storage.local.set(settings); +// } + +export async function import_legacy_data() { + console.info("Import settings data from legacy manifest file"); + if (await get("legacy_imported")) return console.info("Legacy settings has already imported"); + const result = await browser.runtime.sendNativeMessage({name: "PAGE_INIT_DATA"}); + if (result.error) return console.error(result.error); + for (const key in settingsConfig) { + const legacy = settingsConfig[key].legacy; + if (legacy in result) { + let value = result[legacy]; + console.log(`Importing legacy setting: ${legacy} - ${value}`); + switch (settingsConfig[key].type) { + case "boolean": value = JSON.parse(value); break; + case "number": value = Number(value); break; + } + if (!update(settingsConfig[key].name, value)) return console.error("Import abort"); + } + } + update("legacy_imported", true); + console.log("Importing legacy settings complete"); + // Send a message to the Swift layer to safely clean up legacy data +} \ No newline at end of file From 017b9a1456d46d473c7ca038b52eaba200f53c6c Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Mon, 17 Oct 2022 00:55:02 +0800 Subject: [PATCH 05/14] refactor: update functions use settings storage --- .../Resources/content.js | 161 +++++++++--------- src/page/App.svelte | 2 +- src/page/store.js | 34 +--- src/popup/App.svelte | 16 +- src/shared/settings.js | 20 ++- 5 files changed, 108 insertions(+), 125 deletions(-) diff --git a/extension/Userscripts Extension/Resources/content.js b/extension/Userscripts Extension/Resources/content.js index 82156c87..7747b92c 100644 --- a/extension/Userscripts Extension/Resources/content.js +++ b/extension/Userscripts Extension/Resources/content.js @@ -323,88 +323,93 @@ function cspFallback(e) { } } -browser.runtime.sendMessage({name: "REQ_USERSCRIPTS"}, response => { - // cancel injection if errors detected - if (!response || response.error) { - console.error(response?.error || "REQ_USERSCRIPTS returned undefined"); - return; - } - // save response locally in case CSP events occur - data = response; - // loop through each userscript and prepare for processing - for (let i = 0; i < data.files.js.length; i++) { - const userscript = data.files.js[i]; - userscript.preCode = ""; - // pass references to the api methods as needed - const gmMethods = []; - const filename = userscript.scriptObject.filename; - const grants = userscript.scriptObject.grant; - const injectInto = userscript.scriptObject["inject-into"]; - // create GM.info object - const scriptData = { - "script": userscript.scriptObject, - "scriptHandler": data.scriptHandler, - "scriptHandlerVersion": data.scriptHandlerVersion, - "scriptMetaStr": userscript.scriptMetaStr - }; - // all userscripts get access to GM.info - gmMethods.push("info: GM_info"); - // if @grant explicitly set to none, empty grants array - if (grants.includes("none")) grants.length = 0; - // @grant values exist for page scoped userscript - if (grants.length && injectInto === "page") { - // remove grants - grants.length = 0; - // log warning - console.warn(`${filename} @grant values removed due to @inject-into value: ${injectInto} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`); +// should import settings.js and use `settingsStorage.get("global_active")` +browser.storage.local.get("US_GLOBAL_ACTIVE").then(CONFIG => { + if (CONFIG?.US_GLOBAL_ACTIVE === false) return console.info("Userscripts off"); + browser.runtime.sendMessage({name: "REQ_USERSCRIPTS"}, response => { + // cancel injection if errors detected + if (!response || response.error) { + console.error(response?.error || "REQ_USERSCRIPTS returned undefined"); + return; } - // @grant exist for auto scoped userscript - if (grants.length && injectInto === "auto") { - // change scope - userscript.scriptObject["inject-into"] = "content"; - // log warning - console.warn(`${filename} @inject-into value set to 'content' due to @grant values: ${grants} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`); - } - // loop through each userscript @grant value, add methods as needed - for (let j = 0; j < grants.length; j++) { - const grant = grants[j]; - const method = grant.split(".")[1] || grant.split(".")[0]; - // ensure API method exists in apis object - if (!Object.keys(apis).includes(method)) continue; - // create the method string to be pushed to methods array - let methodStr = `${method}: apis.${method}`; - // add require variables to specific methods - switch (method) { - case "getValue": - case "setValue": - case "deleteValue": - case "listValues": - methodStr += `.bind({"US_filename": "${filename}"})`; - break; - case "info": - case "GM_info": - continue; - case "xmlHttpRequest": - gmMethods.push("xmlHttpRequest: apis.xhr"); - continue; - case "GM_xmlhttpRequest": - userscript.preCode += "const GM_xmlhttpRequest = apis.xhr;"; - continue; + // save response locally in case CSP events occur + data = response; + // loop through each userscript and prepare for processing + for (let i = 0; i < data.files.js.length; i++) { + const userscript = data.files.js[i]; + userscript.preCode = ""; + // pass references to the api methods as needed + const gmMethods = []; + const filename = userscript.scriptObject.filename; + const grants = userscript.scriptObject.grant; + const injectInto = userscript.scriptObject["inject-into"]; + // create GM.info object + const scriptData = { + "script": userscript.scriptObject, + "scriptHandler": data.scriptHandler, + "scriptHandlerVersion": data.scriptHandlerVersion, + "scriptMetaStr": userscript.scriptMetaStr + }; + // all userscripts get access to GM.info + gmMethods.push("info: GM_info"); + // if @grant explicitly set to none, empty grants array + if (grants.includes("none")) grants.length = 0; + // @grant values exist for page scoped userscript + if (grants.length && injectInto === "page") { + // remove grants + grants.length = 0; + // log warning + console.warn(`${filename} @grant values removed due to @inject-into value: ${injectInto} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`); + } + // @grant exist for auto scoped userscript + if (grants.length && injectInto === "auto") { + // change scope + userscript.scriptObject["inject-into"] = "content"; + // log warning + console.warn(`${filename} @inject-into value set to 'content' due to @grant values: ${grants} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`); } - gmMethods.push(methodStr); + // loop through each userscript @grant value, add methods as needed + for (let j = 0; j < grants.length; j++) { + const grant = grants[j]; + const method = grant.split(".")[1] || grant.split(".")[0]; + // ensure API method exists in apis object + if (!Object.keys(apis).includes(method)) continue; + // create the method string to be pushed to methods array + let methodStr = `${method}: apis.${method}`; + // add require variables to specific methods + switch (method) { + case "getValue": + case "setValue": + case "deleteValue": + case "listValues": + methodStr += `.bind({"US_filename": "${filename}"})`; + break; + case "info": + case "GM_info": + continue; + case "xmlHttpRequest": + gmMethods.push("xmlHttpRequest: apis.xhr"); + continue; + case "GM_xmlhttpRequest": + userscript.preCode += "const GM_xmlhttpRequest = apis.xhr;"; + continue; + } + gmMethods.push(methodStr); + } + // add GM.info + userscript.preCode += `const GM_info = ${JSON.stringify(scriptData)};`; + // add other included GM API methods + userscript.preCode += `const GM = {${gmMethods.join(",")}};`; + // process file for injection + processJS(userscript); } - // add GM.info - userscript.preCode += `const GM_info = ${JSON.stringify(scriptData)};`; - // add other included GM API methods - userscript.preCode += `const GM = {${gmMethods.join(",")}};`; - // process file for injection - processJS(userscript); - } - for (let i = 0; i < data.files.css.length; i++) { - const userstyle = data.files.css[i]; - injectCSS(userstyle.name, userstyle.code); - } + for (let i = 0; i < data.files.css.length; i++) { + const userstyle = data.files.css[i]; + injectCSS(userstyle.name, userstyle.code); + } + }); }); + // listens for messages from background, popup, etc... browser.runtime.onMessage.addListener((request, sender, sendResponse) => { const name = request.name; diff --git a/src/page/App.svelte b/src/page/App.svelte index 9e58dfe8..9ca68949 100644 --- a/src/page/App.svelte +++ b/src/page/App.svelte @@ -41,7 +41,7 @@ onMount(async () => { log.add("Requesting initialization data", "info", false); - if (!settings.init()) return console.error("Initialization failure"); + await settings.init(); state.add("items-loading"); state.remove("init"); diff --git a/src/page/store.js b/src/page/store.js index c23462aa..123123f3 100644 --- a/src/page/store.js +++ b/src/page/store.js @@ -1,5 +1,6 @@ import {writable} from "svelte/store"; import {uniqueId} from "./utils.js"; +import * as settingsStorage from "../shared/settings.js"; function notificationStore() { const {subscribe, update} = writable([]); @@ -109,40 +110,15 @@ function settingsStore() { return settings; }); }; - const storageKey = "USettings"; const updateSingleSetting = (key, value) => { - update(settings => { - settings[key] = value; - browser.storage.local.set({[storageKey]: settings}); // Durable Storage - return settings; - }); + update(settings => (settings[key] = value, settings)); + settingsStorage.set(key, value); // Durable Storage // Temporarily keep the old storage method until it is confirmed that all dependencies are removed updateSingleSetting_old(key, value); }; - const init_import_from_old = async () => { - log.add("Import settings data from old storage", "info", false); - const initData = await browser.runtime.sendNativeMessage({name: "PAGE_INIT_DATA"}); - if (initData.error) { - console.error(initData.error); - return false; - } - for (const [key, value] of Object.entries(initData)) { - if (value === "true" || value === "false") { - initData[key] = JSON.parse(value); - } - } - set(initData); - browser.storage.local.set({[storageKey]: initData}); - return true; - }; const init = async () => { - const result = await browser.storage.local.get(storageKey); - if (!result[storageKey]) { - // This will remain for several versions until the majority of users use the new version - return init_import_from_old(); - } - set(result[storageKey]); - return true; + settingsStorage.import_legacy_data(); + set(await settingsStorage.getAll()); }; return {subscribe, set, init, updateSingleSetting}; } diff --git a/src/popup/App.svelte b/src/popup/App.svelte index ef8a72e2..7db399f7 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -12,6 +12,7 @@ import iconUpdate from "../shared/img/icon-update.svg"; import iconClear from "../shared/img/icon-clear.svg"; import iconRefresh from "../shared/img/icon-refresh.svg"; + import * as settingsStorage from "../shared/settings.js"; let error = undefined; let active = true; @@ -50,14 +51,9 @@ $: if (platform) document.body.classList.add(platform); - function toggleExtension(e) { - e.preventDefault(); // prevent check state from changing on click - disabled = true; - browser.runtime.sendNativeMessage({name: "POPUP_TOGGLE_EXTENSION"}, response => { - disabled = false; - if (response.error) return error = response.error; - active = !active; - }); + async function toggleExtension(e) { + await settingsStorage.set("global_active", !active); + active = await settingsStorage.get("global_active"); } function updateAll() { @@ -259,9 +255,9 @@ loading = false; disabled = false; return; - } else { - active = init.initData.active === "true" ? true : false; } + + active = await settingsStorage.get("global_active"); // refresh session rules browser.runtime.sendMessage({name: "REFRESH_SESSION_RULES"}); diff --git a/src/shared/settings.js b/src/shared/settings.js index f213b4d0..a53a92d2 100644 --- a/src/shared/settings.js +++ b/src/shared/settings.js @@ -297,7 +297,7 @@ export async function getAll() { return Object.assign({}, settingsDefault, result); } -export function update(key, value) { +export async function set(key, value) { key = storagePrefix + key.toUpperCase(); if (!settingsKeys.includes(key)) { return console.error("Unexpected key:", key); @@ -306,12 +306,17 @@ export function update(key, value) { if (typeof(value) !== type) { return console.error(`Unexpected value type ${typeof(value)} should ${type}`); } - browser.storage.local.set({[key]: value}); - return true; + try { + await browser.storage.local.set({[key]: value}); + return true; + } catch (error) { + console.error(error); + return false; + } } // Note: It seems unnecessary to use this method, comment it out for now -// export function updateAll(settings) { +// export function setAll(settings) { // for (const key in settings) { // if (!settingsKeys.includes(key)) { // return console.error("unexpected settings key:", key); @@ -325,10 +330,10 @@ export function update(key, value) { // } export async function import_legacy_data() { - console.info("Import settings data from legacy manifest file"); if (await get("legacy_imported")) return console.info("Legacy settings has already imported"); const result = await browser.runtime.sendNativeMessage({name: "PAGE_INIT_DATA"}); if (result.error) return console.error(result.error); + console.info("Import settings data from legacy manifest file"); for (const key in settingsConfig) { const legacy = settingsConfig[key].legacy; if (legacy in result) { @@ -338,10 +343,11 @@ export async function import_legacy_data() { case "boolean": value = JSON.parse(value); break; case "number": value = Number(value); break; } - if (!update(settingsConfig[key].name, value)) return console.error("Import abort"); + if (!set(settingsConfig[key].name, value)) return console.error("Import abort"); } } - update("legacy_imported", true); + set("legacy_imported", true); console.log("Importing legacy settings complete"); // Send a message to the Swift layer to safely clean up legacy data + browser.runtime.sendNativeMessage({name: "PAGE_LEGACY_IMPORTED"}); } \ No newline at end of file From e77c94aa60307098a1658aa4b5f3f418ce17887c Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Tue, 18 Oct 2022 02:26:26 +0800 Subject: [PATCH 06/14] refactor: add sync settings storage feature --- src/page/store.js | 4 +- src/popup/App.svelte | 2 +- src/shared/settings.js | 294 +++++++++++++++++++++++++++++++---------- 3 files changed, 224 insertions(+), 76 deletions(-) diff --git a/src/page/store.js b/src/page/store.js index 123123f3..1c30d2db 100644 --- a/src/page/store.js +++ b/src/page/store.js @@ -112,13 +112,13 @@ function settingsStore() { }; const updateSingleSetting = (key, value) => { update(settings => (settings[key] = value, settings)); - settingsStorage.set(key, value); // Durable Storage + settingsStorage.set({[key]: value}); // Durable Storage // Temporarily keep the old storage method until it is confirmed that all dependencies are removed updateSingleSetting_old(key, value); }; const init = async () => { settingsStorage.import_legacy_data(); - set(await settingsStorage.getAll()); + set(await settingsStorage.get()); }; return {subscribe, set, init, updateSingleSetting}; } diff --git a/src/popup/App.svelte b/src/popup/App.svelte index 7db399f7..be50d240 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -52,7 +52,7 @@ $: if (platform) document.body.classList.add(platform); async function toggleExtension(e) { - await settingsStorage.set("global_active", !active); + await settingsStorage.set({"global_active": !active}); active = await settingsStorage.get("global_active"); } diff --git a/src/shared/settings.js b/src/shared/settings.js index a53a92d2..143d9b77 100644 --- a/src/shared/settings.js +++ b/src/shared/settings.js @@ -1,29 +1,18 @@ -export const settingTemplate = { - name: "setting_template", - type: "boolean", - default: true, - writable: true, - platforms: ["macos", "ipados", "ios"], - langLabel: { - en: "Engilsh label", - zh_hans: "简体中文标签", - zh_hant: "繁體中文標籤" - }, - langTitle: { - en: "Engilsh title", - zh_hans: "简体中文标题", - zh_hant: "繁體中文標題" - }, - group: "Template", - legacy: "setting_legacy_name", - nodeType: "Toggle", - nodeClass: {red: false} -}; +function deepFreeze(object) { + for (const p in object) { + if (typeof object[p] == "object") { + deepFreeze(object[p]); + } + } + return Object.freeze(object); +} -const settingDefault = { +const settingDefault = deepFreeze({ name: "setting_default", type: undefined, + local: false, default: undefined, + confirm: false, writable: true, platforms: ["macos", "ipados", "ios"], langLabel: {}, @@ -31,19 +20,38 @@ const settingDefault = { group: "", nodeType: "", nodeClass: {} -}; +}); const settingsDefine = [ { name: "legacy_imported", type: "boolean", + local: true, default: false, platforms: ["macos"], group: "Internal" }, + { + name: "settings_sync", + type: "boolean", + local: true, + default: false, + platforms: ["macos", "ipados", "ios"], + langLabel: { + en: "Sync settings", + zh_hans: "同步设置" + }, + langTitle: { + en: "Sync settings across devices", + zh_hans: "跨设备同步设置" + }, + group: "General", + nodeType: "Toggle" + }, { name: "global_active", type: "boolean", + local: true, default: true, platforms: ["macos"], langLabel: { @@ -112,6 +120,7 @@ const settingsDefine = [ name: "scripts_auto_update", type: "boolean", default: false, + confirm: true, platforms: ["macos", "ipados", "ios"], langLabel: { en: "Scripts silent auto update", @@ -122,12 +131,13 @@ const settingsDefine = [ zh_hans: "脚本在后台静默自动更新,这是危险的,可能引入未经确认的恶意代码" }, group: "General", - nodeType: "Toggle_double_confirm", + nodeType: "Toggle", nodeClass: {warn: true} }, { name: "location_path", type: "string", + local: true, default: true, writable: false, platforms: ["macos", "ipados", "ios"], @@ -266,88 +276,226 @@ const settingsDefine = [ ]; const storagePrefix = "US_"; +const storageKey = key => storagePrefix + key.toUpperCase(); +const storageRef = async area => { // storage reference + browser.storage.sync.area = "sync"; + browser.storage.local.area = "local"; + if (area === "sync") return browser.storage.sync; + if (area === "local") return browser.storage.local; + const key = storageKey("settings_sync"); + const result = await browser.storage.local.get(key); + if (result?.[key] === true) { + return browser.storage.sync; + } else { + return browser.storage.local; + } +}; export const settingsConfig = settingsDefine.reduce((arr, val) => { - val.key = storagePrefix + val.name.toUpperCase(); - // arr[val.key] = Object.assign({}, settingDefault, val); - arr[val.key] = new Proxy(val, { - get: (obj, p) => p in obj ? obj[p] : settingDefault[p] - }); + val.key = storageKey(val.name); + arr[val.key] = Object.assign({}, settingDefault, val); return arr; }, {}); -const settingsKeys = Object.keys(settingsConfig); +deepFreeze(settingsConfig); +deepFreeze(settingsDefine); -export async function get(key) { - if (typeof key !== "string") return console.error("key should be a string:", key); - key = storagePrefix + key.toUpperCase(); - if (!settingsKeys.includes(key)) { - return console.error("unexpected settings key:", key); - } - const result = await browser.storage.local.get(key); - return result[key] ?? settingsConfig[key].default; -} +export const settingsKeys = deepFreeze(Object.keys(settingsConfig)); -export async function getAll() { - const result = await browser.storage.local.get(settingsKeys); - const settingsDefault = {}; - for (const key in settingsConfig) { - settingsDefault[key] = settingsConfig[key].default; +export async function get(keys, area) { + if (![undefined, "local", "sync"].includes(area)) { + return console.error("unexpected storage area:", area); + } + if (typeof keys == "string") { // single setting + const key = storageKey(keys); + if (!settingsKeys.includes(key)) { + return console.error("unexpected settings key:", key); + } + settingsConfig[key].local === true && (area = "local"); + const storage = await storageRef(area); + const result = await storage.get(key); + return result[key] ?? settingsConfig[key].default; + } + const complexGet = async areaKeys => { + const storage = await storageRef(area); + const results = {local: {}, sync: {}}; + if (storage.area === "sync") { + if (areaKeys.sync.length) { + results.sync = await storage.get(areaKeys.sync); + } + if (areaKeys.local.length) { + const storage = await storageRef("local"); + results.local = await storage.get(areaKeys.local); + } + } else { + results.local = await storage.get(areaKeys.all); + } + return results; + }; + if (Array.isArray(keys)) { // muilt settings + if (!keys.length) { + return console.error("Settings keys empty:", keys); + } + const areaKeys = {local: [], sync: [], all: []}; + const settingsDefault = {}; + for (const k of keys) { + const key = storageKey(k); + if (!settingsKeys.includes(key)) { + return console.error("unexpected settings key:", key); + } + settingsDefault[key] = settingsConfig[key].default; + settingsConfig[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + areaKeys.all.push(key); + } + const {local, sync} = complexGet(areaKeys); + return Object.assign(settingsDefault, local, sync); } - return Object.assign({}, settingsDefault, result); + if (typeof keys == "undefined" || keys === null) { // all settings + const areaKeys = {local: [], sync: [], all: []}; + const settingsDefault = {}; + for (const key in settingsConfig) { + settingsDefault[key] = settingsConfig[key].default; + settingsConfig[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + areaKeys.all.push(key); + } + const {local, sync} = complexGet(areaKeys); + return Object.assign(settingsDefault, local, sync); + } + return console.error("Unexpected arg type:", keys); } -export async function set(key, value) { - key = storagePrefix + key.toUpperCase(); - if (!settingsKeys.includes(key)) { - return console.error("Unexpected key:", key); +export async function set(keys, area) { + if (![undefined, "local", "sync"].includes(area)) { + return console.error("unexpected storage area:", area); } - const type = settingsConfig[key].type; - if (typeof(value) !== type) { - return console.error(`Unexpected value type ${typeof(value)} should ${type}`); + if (typeof keys != "object") { + return console.error("Unexpected arg type:", keys); } + if (!Object.keys(keys).length) { + return console.error("Settings object empty:", keys); + } + const areaKeys = {local: {}, sync: {}, all: {}}; + for (const k in keys) { + const key = storageKey(k); + if (!settingsKeys.includes(key)) { + return console.error("Unexpected settings keys:", key); + } + const type = settingsConfig[key].type; + if (typeof keys[k] != type) { + return console.error(`Unexpected value type ${typeof(value)} should ${type}`); + } + settingsConfig[key].local === true + ? areaKeys.local[key] = keys[k] + : areaKeys.sync[key] = keys[k]; + areaKeys.all[key] = keys[k]; + } + const storage = await storageRef(area); try { - await browser.storage.local.set({[key]: value}); + if (storage.area === "sync") { + if (Object.keys(areaKeys.sync).length) { + await storage.set(areaKeys.sync); + } + if (Object.keys(areaKeys.local).length) { + const storage = await storageRef("local"); + await storage.set(areaKeys.local); + } + } else { + await storage.set(areaKeys.all); + } return true; } catch (error) { - console.error(error); - return false; + return console.error(error); } } -// Note: It seems unnecessary to use this method, comment it out for now -// export function setAll(settings) { -// for (const key in settings) { -// if (!settingsKeys.includes(key)) { -// return console.error("unexpected settings key:", key); -// } -// const type = settingsConfig[key].type; -// if (typeof(settings.key) !== type) { -// return console.error(`Unexpected value type ${typeof(value)} should ${type}`); -// } -// } -// browser.storage.local.set(settings); -// } +export async function reset(keys, area) { // reset to default + if (![undefined, "local", "sync"].includes(area)) { + return console.error("unexpected storage area:", area); + } + if (typeof keys == "string") { // single setting + const key = storageKey(keys); + if (!settingsKeys.includes(key)) { + return console.error("unexpected settings key:", key); + } + settingsConfig[key].local === true && (area = "local"); + const storage = await storageRef(area); + return storage.remove(key); + } + const complexRemove = async areaKeys => { + const storage = await storageRef(area); + try { + if (storage.area === "sync") { + if (areaKeys.sync.length) { + await storage.remove(areaKeys.sync); + } + if (areaKeys.local.length) { + const storage = await storageRef("local"); + await storage.remove(areaKeys.local); + } + } else { + await storage.remove(areaKeys.all); + } + return true; + } catch (error) { + return console.error(error); + } + }; + if (Array.isArray(keys)) { // muilt settings + if (!keys.length) { + return console.error("Settings keys empty:", keys); + } + const areaKeys = {local: [], sync: [], all: []}; + for (const k of keys) { + const key = storageKey(k); + if (!settingsKeys.includes(key)) { + return console.error("unexpected settings key:", key); + } + settingsConfig[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + areaKeys.all.push(key); + } + return complexRemove(areaKeys); + } + if (typeof keys == "undefined" || keys === null) { // all settings + const areaKeys = {local: [], sync: [], all: []}; + for (const key in settingsConfig) { + settingsConfig[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + areaKeys.all.push(key); + } + return complexRemove(areaKeys); + } + return console.error("Unexpected arg type:", keys); +} export async function import_legacy_data() { - if (await get("legacy_imported")) return console.info("Legacy settings has already imported"); + const imported = await get("legacy_imported"); + if (imported) return console.info("Legacy settings has already imported"); const result = await browser.runtime.sendNativeMessage({name: "PAGE_INIT_DATA"}); if (result.error) return console.error(result.error); console.info("Import settings data from legacy manifest file"); + const settings = {}; for (const key in settingsConfig) { const legacy = settingsConfig[key].legacy; if (legacy in result) { let value = result[legacy]; - console.log(`Importing legacy setting: ${legacy} - ${value}`); switch (settingsConfig[key].type) { case "boolean": value = JSON.parse(value); break; case "number": value = Number(value); break; } - if (!set(settingsConfig[key].name, value)) return console.error("Import abort"); + console.log(`Importing legacy setting: ${legacy}`, value); + settings[settingsConfig[key].name] = value; } } - set("legacy_imported", true); - console.log("Importing legacy settings complete"); + if (!set(settings)) return console.error("Import legacy settings abort"); + set({"legacy_imported": true}); + console.info("Import legacy settings complete"); // Send a message to the Swift layer to safely clean up legacy data browser.runtime.sendNativeMessage({name: "PAGE_LEGACY_IMPORTED"}); } \ No newline at end of file From eb2522fd199cdc39e7afe243adeb590cfee25a64 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Thu, 20 Oct 2022 02:34:04 +0800 Subject: [PATCH 07/14] refactor: add settings storage legacy functions --- src/page/store.js | 30 ++++------ src/shared/settings.js | 122 +++++++++++++++++++++++++++++++++-------- 2 files changed, 109 insertions(+), 43 deletions(-) diff --git a/src/page/store.js b/src/page/store.js index 1c30d2db..dfc3fd45 100644 --- a/src/page/store.js +++ b/src/page/store.js @@ -89,36 +89,28 @@ function settingsStore() { log.add("Failed to save blacklist to disk", "error", true); } }); - return settings; } - // settings are saved as strings on the swift side - // convert all booleans to strings before dispatching - const settingsClone = {...settings}; - for (const [key, value] of Object.entries(settingsClone)) { - if (typeof value === "boolean") settingsClone[key] = value.toString(); - } - // remove settings in clone that aren't save in user defaults - delete settingsClone.blacklist; - delete settingsClone.version; - // update settings on swift side - const message = {name: "PAGE_UPDATE_SETTINGS", settings: settingsClone}; - browser.runtime.sendNativeMessage(message, response => { - if (response.error) { - log.add(response.error, "error", true); - } - }); return settings; }); }; const updateSingleSetting = (key, value) => { update(settings => (settings[key] = value, settings)); - settingsStorage.set({[key]: value}); // Durable Storage + // For compatibility with legacy setting names only + // Once the new name is used, use settingsStorage.set() + settingsStorage.legacy_set({[key]: value}); // Durable Storage // Temporarily keep the old storage method until it is confirmed that all dependencies are removed updateSingleSetting_old(key, value); }; const init = async () => { settingsStorage.import_legacy_data(); - set(await settingsStorage.get()); + const settings = await settingsStorage.legacy_get(); + // console.log("store.js settingsStore init", settings); + set(settings); + // sync popup, backgound, etc... settings changes + settingsStorage.onChanged((settings, area) => { + console.log(`storage.${area}.onChanged`, settings); + update(obj => Object.assign(obj, settings)); + }); }; return {subscribe, set, init, updateSingleSetting}; } diff --git a/src/shared/settings.js b/src/shared/settings.js index 143d9b77..8d89b003 100644 --- a/src/shared/settings.js +++ b/src/shared/settings.js @@ -11,6 +11,7 @@ const settingDefault = deepFreeze({ name: "setting_default", type: undefined, local: false, + values: [], default: undefined, confirm: false, writable: true, @@ -18,6 +19,7 @@ const settingDefault = deepFreeze({ langLabel: {}, langTitle: {}, group: "", + legacy: "", nodeType: "", nodeClass: {} }); @@ -25,12 +27,20 @@ const settingDefault = deepFreeze({ const settingsDefine = [ { name: "legacy_imported", - type: "boolean", + type: "number", local: true, - default: false, + default: 0, platforms: ["macos"], group: "Internal" }, + { + name: "language_code", + type: "string", + default: "en", + platforms: ["macos", "ipados", "ios"], + group: "Internal", + legacy: "languageCode" + }, { name: "settings_sync", type: "boolean", @@ -102,8 +112,8 @@ const settingsDefine = [ }, { name: "scripts_update_check_interval", - type: "boolean", - default: true, + type: "number", + default: 86400000, platforms: ["macos", "ipados", "ios"], langLabel: { en: "Scripts update check interval", @@ -205,10 +215,28 @@ const settingsDefine = [ legacy: "autoHint", nodeType: "Toggle" }, + { + name: "editor_list_sort", + type: "string", + values: ["nameAsc", "nameDesc", "lastModifiedAsc", "lastModifiedDesc"], + default: "lastModifiedDesc", + platforms: ["macos"], + langLabel: { + en: "Sort order", + zh_hans: "排序顺序" + }, + langTitle: { + en: "Display order of items in sidebar", + zh_hans: "侧栏中项目的显示顺序" + }, + group: "Editor", + legacy: "sortOrder", + nodeType: "Dropdown" + }, { name: "editor_list_descriptions", type: "boolean", - default: false, + default: true, platforms: ["macos"], langLabel: { en: "Show List Descriptions", @@ -291,10 +319,10 @@ const storageRef = async area => { // storage reference } }; -export const settingsConfig = settingsDefine.reduce((arr, val) => { - val.key = storageKey(val.name); - arr[val.key] = Object.assign({}, settingDefault, val); - return arr; +export const settingsConfig = settingsDefine.reduce((p, c) => { + c.key = storageKey(c.name); + p[c.key] = Object.assign({}, settingDefault, c); + return p; }, {}); deepFreeze(settingsConfig); @@ -316,28 +344,32 @@ export async function get(keys, area) { const result = await storage.get(key); return result[key] ?? settingsConfig[key].default; } - const complexGet = async areaKeys => { + const complexGet = async (settingsDefault, areaKeys) => { const storage = await storageRef(area); - const results = {local: {}, sync: {}}; + let local = {}, sync = {}; if (storage.area === "sync") { if (areaKeys.sync.length) { - results.sync = await storage.get(areaKeys.sync); + sync = await storage.get(areaKeys.sync); } if (areaKeys.local.length) { const storage = await storageRef("local"); - results.local = await storage.get(areaKeys.local); + local = await storage.get(areaKeys.local); } } else { - results.local = await storage.get(areaKeys.all); + local = await storage.get(areaKeys.all); } - return results; + const result = Object.assign(settingsDefault, local, sync); + // reduce settings object property name + return Object.entries(result).reduce((p, c) => ( + p[settingsConfig[c[0]].name] = c[1], p + ), {}); }; if (Array.isArray(keys)) { // muilt settings if (!keys.length) { return console.error("Settings keys empty:", keys); } - const areaKeys = {local: [], sync: [], all: []}; const settingsDefault = {}; + const areaKeys = {local: [], sync: [], all: []}; for (const k of keys) { const key = storageKey(k); if (!settingsKeys.includes(key)) { @@ -349,12 +381,11 @@ export async function get(keys, area) { : areaKeys.sync.push(key); areaKeys.all.push(key); } - const {local, sync} = complexGet(areaKeys); - return Object.assign(settingsDefault, local, sync); + return await complexGet(settingsDefault, areaKeys); } if (typeof keys == "undefined" || keys === null) { // all settings - const areaKeys = {local: [], sync: [], all: []}; const settingsDefault = {}; + const areaKeys = {local: [], sync: [], all: []}; for (const key in settingsConfig) { settingsDefault[key] = settingsConfig[key].default; settingsConfig[key].local === true @@ -362,8 +393,7 @@ export async function get(keys, area) { : areaKeys.sync.push(key); areaKeys.all.push(key); } - const {local, sync} = complexGet(areaKeys); - return Object.assign(settingsDefault, local, sync); + return await complexGet(settingsDefault, areaKeys); } return console.error("Unexpected arg type:", keys); } @@ -459,7 +489,7 @@ export async function reset(keys, area) { // reset to default : areaKeys.sync.push(key); areaKeys.all.push(key); } - return complexRemove(areaKeys); + return await complexRemove(areaKeys); } if (typeof keys == "undefined" || keys === null) { // all settings const areaKeys = {local: [], sync: [], all: []}; @@ -469,11 +499,55 @@ export async function reset(keys, area) { // reset to default : areaKeys.sync.push(key); areaKeys.all.push(key); } - return complexRemove(areaKeys); + return await complexRemove(areaKeys); } return console.error("Unexpected arg type:", keys); } +export function onChanged(callback) { + const listener = (changes, area) => { + // console.log(`storage.${area}.onChanged`, changes); + const settings = {}; + for (const key in changes) { + if (!(key in settingsConfig)) continue; + settings[settingsConfig[key].name] = changes[key].newValue; + } + callback(settings, area); + }; + browser.storage.sync.onChanged.addListener(changes => { + listener(changes, "sync"); + }); + browser.storage.local.onChanged.addListener(changes => { + listener(changes, "local"); + }); +} + +export async function legacy_get(keys) { + const result = await get(keys); + // console.log("legacy_get", keys, result); + return Object.entries(result).reduce((p, c) => ( + p[settingsConfig[storageKey(c[0])].legacy || c[0]] = c[1], p + ), {}); +} + +export async function legacy_set(keys) { + if (typeof keys != "object") { + return console.error("Unexpected arg type:", keys); + } + if (!Object.keys(keys).length) { + return console.error("Settings object empty:", keys); + } + const settings = {}; + for (const obj of settingsDefine) { + if (!obj.legacy) continue; + if (obj.legacy in keys) { + settings[obj.name] = keys[obj.legacy]; + } + } + // console.log("legacy_set", keys, settings); + return await set(settings); +} + export async function import_legacy_data() { const imported = await get("legacy_imported"); if (imported) return console.info("Legacy settings has already imported"); @@ -494,7 +568,7 @@ export async function import_legacy_data() { } } if (!set(settings)) return console.error("Import legacy settings abort"); - set({"legacy_imported": true}); + set({"legacy_imported": Date.now()}); console.info("Import legacy settings complete"); // Send a message to the Swift layer to safely clean up legacy data browser.runtime.sendNativeMessage({name: "PAGE_LEGACY_IMPORTED"}); From 230163d46d8ac4f42c6ee370b13d2d7178215d6f Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sat, 22 Oct 2022 17:04:27 +0800 Subject: [PATCH 08/14] fix: restore bind initial value display --- src/page/Components/Settings.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/page/Components/Settings.svelte b/src/page/Components/Settings.svelte index b6b4d499..13f9ccfd 100644 --- a/src/page/Components/Settings.svelte +++ b/src/page/Components/Settings.svelte @@ -120,11 +120,11 @@