From 78deebe2e35fcbbc964c479069658b924767d578 Mon Sep 17 00:00:00 2001 From: SYM01 <33443792+SYM01@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:40:17 +0800 Subject: [PATCH 1/2] fix: resolve multiple bugs and code quality issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix critical `in` operator bug in refreshProxy() — `proxyType in ["system", "direct"]` always returned false; replaced with `["system", "direct"].includes(proxyType)` - Fix service worker crash: wrap `new URL(details.url)` in try/catch in background.ts - Fix document.body null access in preference.ts using optional chaining - Replace deepClone() JSON.parse/JSON.stringify with structuredClone() for correctness - Fix window.open() called with import.meta.url as target name; use '_blank' instead - Replace .map() with .forEach() for side-effect-only iterations (profile.ts, auth.ts) - Remove leftover debug console.log calls from ThemeSwitcher, AutoSwitchInput, background, PopupPage - Fix i18n typo: "Advance Config" → "Advanced Config" Co-Authored-By: Claude Sonnet 4.6 --- public/_locales/en/messages.json | 2 +- src/background.ts | 9 +++++++-- src/components/configs/AutoSwitchInput.vue | 1 - src/components/controls/ThemeSwitcher.vue | 1 - src/pages/PopupPage.vue | 4 +--- src/services/preference.ts | 4 ++-- src/services/profile.ts | 2 +- src/services/proxy/auth.ts | 2 +- src/services/proxy/index.ts | 2 +- src/services/utils.ts | 2 +- 10 files changed, 15 insertions(+), 14 deletions(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 66ceb1c..b7faed9 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -100,7 +100,7 @@ "message": "Bypass List" }, "config_section_advance": { - "message": "Advance Config" + "message": "Advanced Config" }, "config_reference_bypass_list": { "message": "Learn more about bypass list" diff --git a/src/background.ts b/src/background.ts index 84d6941..1720b9e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -96,11 +96,16 @@ class StatsProvider { // this.stats.addFailedRequest(details); // TODO: update indicator const proxySetting = await getCurrentProxySetting(); - console.log("onResponseStarted", details); if (details.tabId > 0 && proxySetting.activeProfile) { + let parsedUrl: URL; + try { + parsedUrl = new URL(details.url); + } catch { + return; + } const ret = await findProfile( proxySetting.activeProfile, - new URL(details.url) + parsedUrl ); StatsProvider.stats.setCurrentProfile(details.tabId, ret); diff --git a/src/components/configs/AutoSwitchInput.vue b/src/components/configs/AutoSwitchInput.vue index 4f1be10..214412c 100644 --- a/src/components/configs/AutoSwitchInput.vue +++ b/src/components/configs/AutoSwitchInput.vue @@ -102,7 +102,6 @@ const getConditionInputRule = (type: AutoSwitchType): FieldRule => { case "url": return { validator: async (value: string, cb: (message?: string) => void) => { - console.log("test"); let u; try { u = new URL(value || ""); diff --git a/src/components/controls/ThemeSwitcher.vue b/src/components/controls/ThemeSwitcher.vue index 5debbea..d96dbb9 100644 --- a/src/components/controls/ThemeSwitcher.vue +++ b/src/components/controls/ThemeSwitcher.vue @@ -28,7 +28,6 @@ const onDarkModeChanged = (newMode: DarkMode) => { }; const toggleDarkMode = async () => { - console.log(await currentDarkMode()); switch (await currentDarkMode()) { case DarkMode.Dark: onDarkModeChanged(DarkMode.Light); diff --git a/src/pages/PopupPage.vue b/src/pages/PopupPage.vue index 25d8a1b..d5ab199 100644 --- a/src/pages/PopupPage.vue +++ b/src/pages/PopupPage.vue @@ -40,14 +40,12 @@ onMounted(async () => { const jumpTo = (to: RouteLocationRaw) => { const path = router.resolve(to).fullPath; - window.open(`/index.html#${path}`, import.meta.url); - // window.open(router.resolve(to).href, import.meta.url) + window.open(`/index.html#${path}`, "_blank"); }; // actions const setProxyByProfile = async (val: ProxyProfile) => { try { - console.log(toRaw(val)); await setProxy(toRaw(val)); activeProfile.value = toRaw(val); } catch (e: any) { diff --git a/src/services/preference.ts b/src/services/preference.ts index db5928a..5dea90b 100644 --- a/src/services/preference.ts +++ b/src/services/preference.ts @@ -53,10 +53,10 @@ export async function changeDarkMode(newMode: DarkMode) { switch (newMode) { case DarkMode.Dark: - document && document.body.setAttribute("arco-theme", "dark"); + document?.body?.setAttribute("arco-theme", "dark"); break; case DarkMode.Light: - document && document.body.removeAttribute("arco-theme"); + document?.body?.removeAttribute("arco-theme"); break; } } diff --git a/src/services/profile.ts b/src/services/profile.ts index f057b4b..d3de6c5 100644 --- a/src/services/profile.ts +++ b/src/services/profile.ts @@ -107,7 +107,7 @@ async function overwriteProfiles(profiles: ProfilesStorage) { // Deep clone to remove any Proxy objects before saving const clonedProfiles = deepClone(profiles); await Host.set(keyProfileStorage, clonedProfiles); - onProfileUpdateListeners.map((cb) => cb(profiles)); + onProfileUpdateListeners.forEach((cb) => cb(profiles)); } /** diff --git a/src/services/proxy/auth.ts b/src/services/proxy/auth.ts index fcb7bc0..cfa29a3 100644 --- a/src/services/proxy/auth.ts +++ b/src/services/proxy/auth.ts @@ -50,7 +50,7 @@ export class ProfileAuthProvider { ]; // check if there's any matching host and port - auths.map((item) => { + auths.forEach((item) => { if (!item) return; if ( diff --git a/src/services/proxy/index.ts b/src/services/proxy/index.ts index 9da5c75..f194c7b 100644 --- a/src/services/proxy/index.ts +++ b/src/services/proxy/index.ts @@ -75,7 +75,7 @@ export async function refreshProxy() { const newProfile = await getProfile(current.activeProfile.profileID); // if it's preset profiles, then do nothing - if (!newProfile || current.activeProfile.proxyType in ["system", "direct"]) { + if (!newProfile || ["system", "direct"].includes(current.activeProfile.proxyType)) { return; } diff --git a/src/services/utils.ts b/src/services/utils.ts index fbe4b8c..3965e30 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -4,5 +4,5 @@ * which cannot clone Proxy objects. */ export function deepClone(obj: T): T { - return JSON.parse(JSON.stringify(obj)); + return structuredClone(obj); } From 3ea01735395e9e5020ae7dcde67905660a855d50 Mon Sep 17 00:00:00 2001 From: SYM01 <33443792+SYM01@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:48:41 +0800 Subject: [PATCH 2/2] revert: deepClone back to JSON round-trip from structuredClone() structuredClone() throws a DataCloneError on JavaScript Proxy objects, including Vue reactive() and ref() wrappers. The JSON.parse/JSON.stringify approach correctly serializes through the Proxy traps, producing a plain object safe for chrome.storage. Extend the comment to document why structuredClone() is intentionally avoided here, to prevent this mistake in future. Co-Authored-By: Claude Sonnet 4.6 --- src/services/utils.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 3965e30..d060598 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -2,7 +2,12 @@ * Deep clone an object to remove all Proxy objects (e.g., from Vue reactivity). * This is necessary because chrome.storage and browser.storage use structured clone * which cannot clone Proxy objects. + * + * Note: structuredClone() is NOT used here intentionally — it throws a DataCloneError + * on JavaScript Proxy objects (including Vue reactive/ref wrappers). JSON round-trip + * serializes through the Proxy traps and produces a plain object, which is exactly + * what chrome.storage requires. */ export function deepClone(obj: T): T { - return structuredClone(obj); + return JSON.parse(JSON.stringify(obj)); }