From fab643dfbe6b3607b787a88359b204f52ec48698 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Fri, 13 Feb 2026 23:00:47 +0900 Subject: [PATCH 1/8] fix #1235 --- src/app/service/service_worker/script.ts | 10 +- src/locales/ach-UG/translation.json | 6 +- src/locales/de-DE/translation.json | 2 + src/locales/en-US/translation.json | 6 +- src/locales/ja-JP/translation.json | 2 + src/locales/ru-RU/translation.json | 2 + src/locales/vi-VN/translation.json | 2 + src/locales/zh-CN/translation.json | 2 + src/locales/zh-TW/translation.json | 4 +- src/pages/install/App.tsx | 148 ++++++++++++++++++----- 10 files changed, 147 insertions(+), 37 deletions(-) diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 227be28d1..2d610bcfd 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -266,7 +266,15 @@ export class ScriptService { action: { type: "redirect" as chrome.declarativeNetRequest.RuleActionType, redirect: { - regexSubstitution: `${installPageURL}?url=\\1`, + /** + * 核心设计: + * 使用 `<,\1,>` 作为特征锚点注入到重定向 URL 中。 + * 1. 引导格式化:利用 \1 提取正则捕获组内容。 + * 2. 编码探测:通过包裹特殊的定界符(尖括号和逗号),在目标页面解析时, + * 可以通过检测这些字符是否被转义(如变为 %3C, %2C)来精准判定 + * 浏览器底层触发的是哪种 URL 编码策略(Raw / encodeURI / encodeURIComponent)。 + */ + regexSubstitution: `${installPageURL}?url=<,\\1,>`, }, }, condition: condition, diff --git a/src/locales/ach-UG/translation.json b/src/locales/ach-UG/translation.json index cf254e349..7b984f867 100644 --- a/src/locales/ach-UG/translation.json +++ b/src/locales/ach-UG/translation.json @@ -325,8 +325,10 @@ "header_other_update": "crwdns12784:0crwdne12784:0" }, "downloading_status_text": "Downloading. Received {{bytes}}.", - "install_page_loading": "Installation page loading", - "invalid_page": "Invalid page", + "install_page_please_wait": "Please wait", + "install_page_loading": "Loading Installation Page", + "install_page_load_failed": "Failed to Load Installation Page", + "invalid_page": "Invalid Page", "background_script_tag": "crwdns8460:0crwdne8460:0", "scheduled_script_tag": "crwdns8462:0crwdne8462:0", "background_script": "crwdns8464:0crwdne8464:0", diff --git a/src/locales/de-DE/translation.json b/src/locales/de-DE/translation.json index 6ea643df8..dc389e7ea 100644 --- a/src/locales/de-DE/translation.json +++ b/src/locales/de-DE/translation.json @@ -334,7 +334,9 @@ "header_other_update": "Andere verfügbare Updates" }, "downloading_status_text": "Wird heruntergeladen. {{bytes}} empfangen.", + "install_page_please_wait": "Bitte warten Sie", "install_page_loading": "Installationsseite wird geladen", + "install_page_load_failed": "Installationsseite konnte nicht geladen werden", "invalid_page": "Ungültige Seite", "background_script_tag": "Dies ist ein Hintergrundskript", "scheduled_script_tag": "Dies ist ein geplantes Skript", diff --git a/src/locales/en-US/translation.json b/src/locales/en-US/translation.json index 1091314dd..006a3af2d 100644 --- a/src/locales/en-US/translation.json +++ b/src/locales/en-US/translation.json @@ -334,8 +334,10 @@ "header_other_update": "Other available updates" }, "downloading_status_text": "Downloading. Received {{bytes}}.", - "install_page_loading": "Installation page loading", - "invalid_page": "Invalid page", + "install_page_please_wait": "Please wait", + "install_page_loading": "Loading Installation Page", + "install_page_load_failed": "Failed to Load Installation Page", + "invalid_page": "Invalid Page", "background_script_tag": "This is a Background Script", "scheduled_script_tag": "This is a Scheduled Script", "background_script": "Background Script", diff --git a/src/locales/ja-JP/translation.json b/src/locales/ja-JP/translation.json index 346ac1524..68a3db516 100644 --- a/src/locales/ja-JP/translation.json +++ b/src/locales/ja-JP/translation.json @@ -334,7 +334,9 @@ "header_other_update": "その他の利用可能な更新" }, "downloading_status_text": "ダウンロード中。{{bytes}} を受信しました。", + "install_page_please_wait": "しばらくお待ちください", "install_page_loading": "インストールページを読み込み中", + "install_page_load_failed": "インストールページの読み込みに失敗しました", "invalid_page": "無効なページ", "background_script_tag": "これはバックグラウンドスクリプトです", "scheduled_script_tag": "これはスケジュールスクリプトです", diff --git a/src/locales/ru-RU/translation.json b/src/locales/ru-RU/translation.json index cc80fe49e..f3da00dc2 100644 --- a/src/locales/ru-RU/translation.json +++ b/src/locales/ru-RU/translation.json @@ -334,7 +334,9 @@ "header_other_update": "Другие доступные обновления" }, "downloading_status_text": "Загрузка. Получено {{bytes}}.", + "install_page_please_wait": "Пожалуйста, подождите", "install_page_loading": "Загрузка страницы установки", + "install_page_load_failed": "Не удалось загрузить страницу установки", "invalid_page": "Недействительная страница", "background_script_tag": "Это фоновый скрипт", "scheduled_script_tag": "Это запланированный скрипт", diff --git a/src/locales/vi-VN/translation.json b/src/locales/vi-VN/translation.json index f16febc99..ed482f3ed 100644 --- a/src/locales/vi-VN/translation.json +++ b/src/locales/vi-VN/translation.json @@ -334,7 +334,9 @@ "header_other_update": "Các bản cập nhật khác" }, "downloading_status_text": "Đang tải xuống. Đã nhận {{bytes}}.", + "install_page_please_wait": "Vui lòng chờ", "install_page_loading": "Đang tải trang cài đặt", + "install_page_load_failed": "Tải trang cài đặt thất bại", "invalid_page": "Trang không hợp lệ", "background_script_tag": "Đây là một script nền", "scheduled_script_tag": "Đây là một script hẹn giờ", diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index 508c9ea3d..80be7c663 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -334,7 +334,9 @@ "header_other_update": "其他可用更新" }, "downloading_status_text": "正在下载。已接收 {{bytes}}。", + "install_page_please_wait": "请稍等", "install_page_loading": "安装页面加载中", + "install_page_load_failed": "安装页面加载失败", "invalid_page": "无效页面", "background_script_tag": "这是一个后台脚本", "scheduled_script_tag": "这是一个定时脚本", diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index abb5c289d..c384e2ab4 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -334,7 +334,9 @@ "header_other_update": "其他可用更新" }, "downloading_status_text": "正在下載。已接收 {{bytes}}。", - "install_page_loading": "安裝頁載入中", + "install_page_please_wait": "請稍等", + "install_page_loading": "安裝頁面載入中", + "install_page_load_failed": "安裝頁面載入失敗", "invalid_page": "無效頁面", "background_script_tag": "這是一個背景腳本", "scheduled_script_tag": "這是一個排程腳本", diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index d27481a9c..8ce047257 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -648,49 +648,125 @@ function App() { }, [memoWatchFile]); // 检查是否有 uuid 或 file - const hasUUIDorFile = useMemo(() => { - return !!(searchParams.get("uuid") || searchParams.get("file")); - }, [searchParams]); + const hasValidSourceParam = !!(searchParams.get("uuid") || searchParams.get("file")); const urlHref = useMemo(() => { - try { - if (!hasUUIDorFile) { - const url = searchParams.get("url"); - if (url) { + if (!hasValidSourceParam) { + /** + * 逻辑说明: + * 在 chrome.declarativeNetRequest 规则中,我们使用 `<,\1,>` 作为占位符引导 API 进行参数填充。 + * 由于不同浏览器版本或配置对 URL 参数的自动编码(Auto-encoding)策略不一致, + * 我们通过检测该占位符的“被编码状态”来逆推浏览器采用了哪种编码方式。 + */ + let m; + let url; + try { + // 场景 1:URL 完全未编码。直接匹配原始特征符号 "<", ">" 和 "," + if ((m = /\burl=(<,.+,>)(&|$)/.exec(location.search)?.[1])) { + url = m; // 未被编码,取原始值。 + } + // 场景 2:URL 经过了部分编码(类似 encodeURI)。逗号 "," 未被编码,但尖括号被转义为 %3C, %3E + else if ((m = /\burl=(%3C,.+,%3E)(&|$)/.exec(location.search)?.[1])) { + url = decodeURI(m); + } + // 场景 3:URL 经过了完全编码(类似 encodeURIComponent)。逗号也被转义为 %2C + else if ((m = /\burl=(%3C%2C.+%2C%3E)(&|$)/.exec(location.search)?.[1])) { + url = decodeURIComponent(m); + } + } catch { + // ignored + } + // 如果正则匹配/标准解码失败,回退到标准的 searchParams 获取方式 (浏览器会自行理解和解码不规范的编码) + if (!url) url = searchParams.get("url") || ""; // fallback + // 移除人工注入的特征锚点 <, ,>,提取真实的 URL 内容 + url = url.replace(/^<,(.+),>$/, "$1"); // 去掉 <, ,> + if (url) { + try { const urlObject = new URL(url); + // 验证解析后的 URL 是否具备核心要素,确保安全性与合法性 if (urlObject.protocol && urlObject.hostname && urlObject.pathname) { - return urlObject.href; + return url; } + } catch { + // ignored } } - } catch { - // ignored } return ""; - }, [hasUUIDorFile, searchParams]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasValidSourceParam, searchParams.get("url")]); const [fetchingState, setFetchingState] = useState({ loadingStatus: "", errorStatus: "", }); - const loadURLAsync = async (urlHref: string) => { + const getCandidateUrls = (targetUrlHref: string) => { + const inputU = new URL(targetUrlHref); + const extraCandidateUrls = new Set(); + extraCandidateUrls.add(inputU.href); + + const isGreasyForkOrSleazyFork = /[.-](greasyfork|sleazyfork)\.org$/.test(inputU.hostname); + + if (isGreasyForkOrSleazyFork) { + // example: + // CASE 1 + // raw 'https://update.greasyfork.org/scripts/550295/100%解锁CSDN文库vip文章阅读限制.user.js' + // encoded 'https://update.greasyfork.org/scripts/550295/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.user.js' + // correct 'https://update.greasyfork.org/scripts/550295/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.user.js' + // CASE 2 + // raw 'https://update.greasyfork.org/scripts/519037/Nexus No Wait ++.user.js' + // encoded 'https://update.greasyfork.org/scripts/519037/Nexus%20No%20Wait%20++.user.js' + // correct 'https://update.greasyfork.org/scripts/519037/Nexus%20No%20Wait%20%2B%2B.user.js' + try { + const url = targetUrlHref.replace(/([^/]+\.js)/, encodeURIComponent); + extraCandidateUrls.add(new URL(url).href); + } catch (e) { + // can skip if it cannot be converted using decodeURI + console.warn(e); // just a warning for debug purpose. + } + } + + return [...extraCandidateUrls]; + }; + + const loadURLAsync = async (candidateUrls: string[]) => { + // 1. 定义获取单个脚本的内部逻辑,负责处理进度条与单次错误 + const fetchValidScript = async () => { + let firstError: unknown; + for (const url of candidateUrls) { + try { + const result = await fetchScriptBody(url, { + onProgress: (info: { receivedLength: number }) => { + setFetchingState((prev) => ({ + ...prev, + loadingStatusText: t("downloading_status_text", { bytes: formatBytes(info.receivedLength) }), + })); + }, + }); + if (result.code && result.metadata) { + return { result, url }; // 找到有效的立即返回 + } + } catch (e) { + if (!firstError) firstError = e; + } + } + // 如果循环结束都没成功,抛出第一个捕获到的错误或预设错误 + throw firstError || new Error(t("install_page_load_failed")); + }; + try { - const { code, metadata } = await fetchScriptBody(urlHref, { - onProgress: (info: { receivedLength: number }) => { - setFetchingState((prev) => ({ - ...prev, - loadingStatus: t("downloading_status_text", { bytes: `${formatBytes(info.receivedLength)}` }), - })); - }, - }); - const update = false; + // 2. 执行获取 + const { result, url } = await fetchValidScript(); + const { code, metadata } = result; + + // 3. 处理数据与缓存 const uuid = uuidv4(); - const url = urlHref; - const upsertBy = "user"; + const scriptData = [false, createScriptInfo(uuid, code, url, "user", metadata)]; + + await cacheInstance.set(`${CACHE_KEY_SCRIPT_INFO}${uuid}`, scriptData); - const si = [update, createScriptInfo(uuid, code, url, upsertBy, metadata)]; - await cacheInstance.set(`${CACHE_KEY_SCRIPT_INFO}${uuid}`, si); + // 4. 更新导向 setSearchParams( (prev) => { prev.delete("url"); @@ -700,21 +776,31 @@ function App() { { replace: true } ); } catch (err: any) { - const errMessage = `${err.message || err}`; + // 5. 统一错误处理 setFetchingState((prev) => ({ ...prev, - loadingStatus: "", - errorStatus: errMessage, + loadingStatusText: "", + errorStatusText: String(err?.message || err), })); } }; + const handleUrlChangeAndFetch = (targetUrlHref: string) => { + setFetchingState((prev) => ({ + ...prev, + loadingStatusText: t("install_page_please_wait"), + })); + const candidateUrls = getCandidateUrls(targetUrlHref); + loadURLAsync(candidateUrls); + }; + + // 有 url 的话下载内容 useEffect(() => { - if (!urlHref) return; - loadURLAsync(urlHref); + if (urlHref) handleUrlChangeAndFetch(urlHref); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [urlHref]); - if (!hasUUIDorFile) { + if (!hasValidSourceParam) { return urlHref ? (
From 89580fa18f877bd3e1056527e2c9faabb3bee323 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:59:22 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=E9=87=8D=E6=96=B0=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/script.ts | 10 +- src/pages/install/App.tsx | 122 +++++++---------------- 2 files changed, 39 insertions(+), 93 deletions(-) diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 2d610bcfd..d922d4921 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -266,15 +266,7 @@ export class ScriptService { action: { type: "redirect" as chrome.declarativeNetRequest.RuleActionType, redirect: { - /** - * 核心设计: - * 使用 `<,\1,>` 作为特征锚点注入到重定向 URL 中。 - * 1. 引导格式化:利用 \1 提取正则捕获组内容。 - * 2. 编码探测:通过包裹特殊的定界符(尖括号和逗号),在目标页面解析时, - * 可以通过检测这些字符是否被转义(如变为 %3C, %2C)来精准判定 - * 浏览器底层触发的是哪种 URL 编码策略(Raw / encodeURI / encodeURIComponent)。 - */ - regexSubstitution: `${installPageURL}?url=<,\\1,>`, + regexSubstitution: `${installPageURL}?url=___,\\1,___`, }, }, condition: condition, diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index 8ce047257..816030dba 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -224,13 +224,13 @@ function App() { try { const uuid = searchParams.get("uuid"); const fid = searchParams.get("file"); - let info: ScriptInfo | undefined; - let isKnownUpdate: boolean = false; - // 如果没有 uuid 和 file,跳过初始化逻辑 - if (!uuid && !fid) { + // 如果有 url 或 没有 uuid 和 file,跳过初始化逻辑 + if (searchParams.get("url") || (!uuid && !fid)) { return; } + let info: ScriptInfo | undefined; + let isKnownUpdate: boolean = false; if (window.history.length > 1) { setDoBackwards(true); @@ -648,44 +648,28 @@ function App() { }, [memoWatchFile]); // 检查是否有 uuid 或 file - const hasValidSourceParam = !!(searchParams.get("uuid") || searchParams.get("file")); + const hasValidSourceParam = !searchParams.get("url") && !!(searchParams.get("uuid") || searchParams.get("file")); const urlHref = useMemo(() => { if (!hasValidSourceParam) { - /** - * 逻辑说明: - * 在 chrome.declarativeNetRequest 规则中,我们使用 `<,\1,>` 作为占位符引导 API 进行参数填充。 - * 由于不同浏览器版本或配置对 URL 参数的自动编码(Auto-encoding)策略不一致, - * 我们通过检测该占位符的“被编码状态”来逆推浏览器采用了哪种编码方式。 - */ let m; - let url; - try { - // 场景 1:URL 完全未编码。直接匹配原始特征符号 "<", ">" 和 "," - if ((m = /\burl=(<,.+,>)(&|$)/.exec(location.search)?.[1])) { - url = m; // 未被编码,取原始值。 - } - // 场景 2:URL 经过了部分编码(类似 encodeURI)。逗号 "," 未被编码,但尖括号被转义为 %3C, %3E - else if ((m = /\burl=(%3C,.+,%3E)(&|$)/.exec(location.search)?.[1])) { - url = decodeURI(m); - } - // 场景 3:URL 经过了完全编码(类似 encodeURIComponent)。逗号也被转义为 %2C - else if ((m = /\burl=(%3C%2C.+%2C%3E)(&|$)/.exec(location.search)?.[1])) { - url = decodeURIComponent(m); - } - } catch { - // ignored + let rawUrl; + if ((m = /\burl=___,(.+),___(.*)$/.exec(location.search))) { + // without component encoding (Chrome's current spec) + // "/src/install.html?url=___,https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js,___?a=12&b=34" + rawUrl = `${m[1]}${m[2]}`; + } else if ((m = /\burl=___%2C(.+)%2C___(.*)$/.exec(location.search))) { + // double encoding (browser auto encode) + // "/src/install.html?url=___%2Chttps%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34%2C___" + rawUrl = decodeURIComponent(`${m[1]}${m[2]}`); } - // 如果正则匹配/标准解码失败,回退到标准的 searchParams 获取方式 (浏览器会自行理解和解码不规范的编码) - if (!url) url = searchParams.get("url") || ""; // fallback - // 移除人工注入的特征锚点 <, ,>,提取真实的 URL 内容 - url = url.replace(/^<,(.+),>$/, "$1"); // 去掉 <, ,> - if (url) { + + if (rawUrl) { try { - const urlObject = new URL(url); + const urlObject = new URL(rawUrl); // 验证解析后的 URL 是否具备核心要素,确保安全性与合法性 if (urlObject.protocol && urlObject.hostname && urlObject.pathname) { - return url; + return rawUrl; } } catch { // ignored @@ -701,35 +685,6 @@ function App() { errorStatus: "", }); - const getCandidateUrls = (targetUrlHref: string) => { - const inputU = new URL(targetUrlHref); - const extraCandidateUrls = new Set(); - extraCandidateUrls.add(inputU.href); - - const isGreasyForkOrSleazyFork = /[.-](greasyfork|sleazyfork)\.org$/.test(inputU.hostname); - - if (isGreasyForkOrSleazyFork) { - // example: - // CASE 1 - // raw 'https://update.greasyfork.org/scripts/550295/100%解锁CSDN文库vip文章阅读限制.user.js' - // encoded 'https://update.greasyfork.org/scripts/550295/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.user.js' - // correct 'https://update.greasyfork.org/scripts/550295/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.user.js' - // CASE 2 - // raw 'https://update.greasyfork.org/scripts/519037/Nexus No Wait ++.user.js' - // encoded 'https://update.greasyfork.org/scripts/519037/Nexus%20No%20Wait%20++.user.js' - // correct 'https://update.greasyfork.org/scripts/519037/Nexus%20No%20Wait%20%2B%2B.user.js' - try { - const url = targetUrlHref.replace(/([^/]+\.js)/, encodeURIComponent); - extraCandidateUrls.add(new URL(url).href); - } catch (e) { - // can skip if it cannot be converted using decodeURI - console.warn(e); // just a warning for debug purpose. - } - } - - return [...extraCandidateUrls]; - }; - const loadURLAsync = async (candidateUrls: string[]) => { // 1. 定义获取单个脚本的内部逻辑,负责处理进度条与单次错误 const fetchValidScript = async () => { @@ -740,12 +695,12 @@ function App() { onProgress: (info: { receivedLength: number }) => { setFetchingState((prev) => ({ ...prev, - loadingStatusText: t("downloading_status_text", { bytes: formatBytes(info.receivedLength) }), + loadingStatus: t("downloading_status_text", { bytes: formatBytes(info.receivedLength) }), })); }, }); if (result.code && result.metadata) { - return { result, url }; // 找到有效的立即返回 + return { result, url } as const; // 找到有效的立即返回 } } catch (e) { if (!firstError) firstError = e; @@ -767,20 +722,13 @@ function App() { await cacheInstance.set(`${CACHE_KEY_SCRIPT_INFO}${uuid}`, scriptData); // 4. 更新导向 - setSearchParams( - (prev) => { - prev.delete("url"); - prev.set("uuid", uuid); - return prev; - }, - { replace: true } - ); + setSearchParams(new URLSearchParams(`?uuid=${uuid}`), { replace: true }); } catch (err: any) { // 5. 统一错误处理 setFetchingState((prev) => ({ ...prev, - loadingStatusText: "", - errorStatusText: String(err?.message || err), + loadingStatus: "", + errorStatus: `${err?.message || err}`, })); } }; @@ -788,10 +736,9 @@ function App() { const handleUrlChangeAndFetch = (targetUrlHref: string) => { setFetchingState((prev) => ({ ...prev, - loadingStatusText: t("install_page_please_wait"), + loadingStatus: t("install_page_please_wait"), })); - const candidateUrls = getCandidateUrls(targetUrlHref); - loadURLAsync(candidateUrls); + loadURLAsync([targetUrlHref]); }; // 有 url 的话下载内容 @@ -804,14 +751,21 @@ function App() { return urlHref ? (
- {t("install_page_loading")} {fetchingState.loadingStatus && ( -
- {fetchingState.loadingStatus} -
-
+ <> + {t("install_page_loading")} +
+ {fetchingState.loadingStatus} +
+
+ + )} + {fetchingState.errorStatus && ( + <> + {t("install_page_load_failed")} +
{fetchingState.errorStatus}
+ )} - {fetchingState.errorStatus &&
{fetchingState.errorStatus}
}
) : ( From 09e328663739ba7c20c67ebfd27ea1bbe5ae3dac Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:09:10 +0900 Subject: [PATCH 3/8] try catch --- src/pages/install/App.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index 816030dba..0acd81bc9 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -654,26 +654,26 @@ function App() { if (!hasValidSourceParam) { let m; let rawUrl; - if ((m = /\burl=___,(.+),___(.*)$/.exec(location.search))) { - // without component encoding (Chrome's current spec) - // "/src/install.html?url=___,https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js,___?a=12&b=34" - rawUrl = `${m[1]}${m[2]}`; - } else if ((m = /\burl=___%2C(.+)%2C___(.*)$/.exec(location.search))) { - // double encoding (browser auto encode) - // "/src/install.html?url=___%2Chttps%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34%2C___" - rawUrl = decodeURIComponent(`${m[1]}${m[2]}`); - } - if (rawUrl) { - try { + try { + if ((m = /\burl=___,(.+),___(.*)$/.exec(location.search))) { + // without component encoding (Chrome's current spec) + // "/src/install.html?url=___,https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js,___?a=12&b=34" + rawUrl = `${m[1]}${m[2]}`; + } else if ((m = /\burl=___%2C(.+)%2C___(.*)$/.exec(location.search))) { + // double encoding (browser auto encode) + // "/src/install.html?url=___%2Chttps%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34%2C___" + rawUrl = decodeURIComponent(`${m[1]}${m[2]}`); + } + if (rawUrl) { const urlObject = new URL(rawUrl); // 验证解析后的 URL 是否具备核心要素,确保安全性与合法性 if (urlObject.protocol && urlObject.hostname && urlObject.pathname) { return rawUrl; } - } catch { - // ignored } + } catch { + // ignored } } return ""; From d1a2a1a411e447fbcc7033f12ca367d9e9deee4c Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:28:09 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E4=BC=98=E5=8C=96=20UI/UX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/install/App.tsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index 0acd81bc9..d20e32611 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -648,7 +648,8 @@ function App() { }, [memoWatchFile]); // 检查是否有 uuid 或 file - const hasValidSourceParam = !searchParams.get("url") && !!(searchParams.get("uuid") || searchParams.get("file")); + const searchParamUrl = searchParams.get("url"); + const hasValidSourceParam = !searchParamUrl && !!(searchParams.get("uuid") || searchParams.get("file")); const urlHref = useMemo(() => { if (!hasValidSourceParam) { @@ -657,13 +658,16 @@ function App() { try { if ((m = /\burl=___,(.+),___(.*)$/.exec(location.search))) { - // without component encoding (Chrome's current spec) + // without component encoding (Chrome's current MV3 spec) // "/src/install.html?url=___,https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js,___?a=12&b=34" rawUrl = `${m[1]}${m[2]}`; } else if ((m = /\burl=___%2C(.+)%2C___(.*)$/.exec(location.search))) { - // double encoding (browser auto encode) + // double encoding (auto encoded by browser's MV3 spec) // "/src/install.html?url=___%2Chttps%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34%2C___" rawUrl = decodeURIComponent(`${m[1]}${m[2]}`); + } else { + // 用户直接在网址栏输入 + rawUrl = searchParamUrl; } if (rawUrl) { const urlObject = new URL(rawUrl); @@ -678,7 +682,18 @@ function App() { } return ""; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hasValidSourceParam, searchParams.get("url")]); + }, [hasValidSourceParam, searchParamUrl]); + + useEffect(() => { + if (urlHref) { + setSearchParams( + (prev) => { + return prev.get("url") !== urlHref ? new URLSearchParams(`?url=${urlHref}`) : prev; + }, + { replace: true } + ); + } + }, [setSearchParams, urlHref]); const [fetchingState, setFetchingState] = useState({ loadingStatus: "", From 50ded375777bc6516169b04a01834c41965924bf Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:44:03 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=E4=B8=8D=E7=94=A8=E6=A8=99=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/script.ts | 2 +- src/pages/install/App.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index d922d4921..227be28d1 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -266,7 +266,7 @@ export class ScriptService { action: { type: "redirect" as chrome.declarativeNetRequest.RuleActionType, redirect: { - regexSubstitution: `${installPageURL}?url=___,\\1,___`, + regexSubstitution: `${installPageURL}?url=\\1`, }, }, condition: condition, diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index d20e32611..70caf4ee3 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -657,14 +657,14 @@ function App() { let rawUrl; try { - if ((m = /\burl=___,(.+),___(.*)$/.exec(location.search))) { + if ((m = /\burl=([\w-]*[:\\/].+)$/.exec(location.search))) { // without component encoding (Chrome's current MV3 spec) // "/src/install.html?url=___,https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js,___?a=12&b=34" - rawUrl = `${m[1]}${m[2]}`; - } else if ((m = /\burl=___%2C(.+)%2C___(.*)$/.exec(location.search))) { + rawUrl = m[1]; + } else if ((m = /\burl=([\w-]*%.+)$/.exec(location.search))) { // double encoding (auto encoded by browser's MV3 spec) // "/src/install.html?url=___%2Chttps%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34%2C___" - rawUrl = decodeURIComponent(`${m[1]}${m[2]}`); + rawUrl = decodeURIComponent(m[1]); } else { // 用户直接在网址栏输入 rawUrl = searchParamUrl; From 568b9f0a5a1ee2819e560387178d6187fb6c0992 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:46:13 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E4=B8=80=E5=80=8B=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/install/App.tsx | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index 70caf4ee3..4ac3414e9 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -700,29 +700,21 @@ function App() { errorStatus: "", }); - const loadURLAsync = async (candidateUrls: string[]) => { + const loadURLAsync = async (url: string) => { // 1. 定义获取单个脚本的内部逻辑,负责处理进度条与单次错误 const fetchValidScript = async () => { - let firstError: unknown; - for (const url of candidateUrls) { - try { - const result = await fetchScriptBody(url, { - onProgress: (info: { receivedLength: number }) => { - setFetchingState((prev) => ({ - ...prev, - loadingStatus: t("downloading_status_text", { bytes: formatBytes(info.receivedLength) }), - })); - }, - }); - if (result.code && result.metadata) { - return { result, url } as const; // 找到有效的立即返回 - } - } catch (e) { - if (!firstError) firstError = e; - } + const result = await fetchScriptBody(url, { + onProgress: (info: { receivedLength: number }) => { + setFetchingState((prev) => ({ + ...prev, + loadingStatus: t("downloading_status_text", { bytes: formatBytes(info.receivedLength) }), + })); + }, + }); + if (result.code && result.metadata) { + return { result, url } as const; // 找到有效的立即返回 } - // 如果循环结束都没成功,抛出第一个捕获到的错误或预设错误 - throw firstError || new Error(t("install_page_load_failed")); + throw new Error(t("install_page_load_failed")); }; try { @@ -753,7 +745,7 @@ function App() { ...prev, loadingStatus: t("install_page_please_wait"), })); - loadURLAsync([targetUrlHref]); + loadURLAsync(targetUrlHref); }; // 有 url 的话下载内容 From 0681f328e728aa3a3284a26a3782e4e90ff6bb37 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:51:47 +0900 Subject: [PATCH 7/8] comment fix --- src/pages/install/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index 4ac3414e9..0cec883b8 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -659,11 +659,11 @@ function App() { try { if ((m = /\burl=([\w-]*[:\\/].+)$/.exec(location.search))) { // without component encoding (Chrome's current MV3 spec) - // "/src/install.html?url=___,https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js,___?a=12&b=34" + // "/src/install.html?url=https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js?a=12&b=34" rawUrl = m[1]; } else if ((m = /\burl=([\w-]*%.+)$/.exec(location.search))) { // double encoding (auto encoded by browser's MV3 spec) - // "/src/install.html?url=___%2Chttps%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34%2C___" + // "/src/install.html?url=https%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34" rawUrl = decodeURIComponent(m[1]); } else { // 用户直接在网址栏输入 From adb13ff81fdf29057258cedd3bc60bf7181fa31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sun, 15 Feb 2026 13:48:50 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9url=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/install/App.tsx | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index 0cec883b8..e98fae9c3 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -653,22 +653,10 @@ function App() { const urlHref = useMemo(() => { if (!hasValidSourceParam) { - let m; let rawUrl; - try { - if ((m = /\burl=([\w-]*[:\\/].+)$/.exec(location.search))) { - // without component encoding (Chrome's current MV3 spec) - // "/src/install.html?url=https://update.greasyfork.org/scripts/1234/ABC%20DEF%20GHK%20%2B%2B.user.js?a=12&b=34" - rawUrl = m[1]; - } else if ((m = /\burl=([\w-]*%.+)$/.exec(location.search))) { - // double encoding (auto encoded by browser's MV3 spec) - // "/src/install.html?url=https%3A%2F%2Fupdate.greasyfork.org%2Fscripts%2F1234%2FABC%2520DEF%2520GHK%2520%252B%252B.user.js%3Fa%3D12%26b%3D34" - rawUrl = decodeURIComponent(m[1]); - } else { - // 用户直接在网址栏输入 - rawUrl = searchParamUrl; - } + // 取url=之后的所有内容 + rawUrl = location.search.match(/\?url=([^&]+)/)?.[1] || ""; if (rawUrl) { const urlObject = new URL(rawUrl); // 验证解析后的 URL 是否具备核心要素,确保安全性与合法性 @@ -681,19 +669,7 @@ function App() { } } return ""; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hasValidSourceParam, searchParamUrl]); - - useEffect(() => { - if (urlHref) { - setSearchParams( - (prev) => { - return prev.get("url") !== urlHref ? new URLSearchParams(`?url=${urlHref}`) : prev; - }, - { replace: true } - ); - } - }, [setSearchParams, urlHref]); + }, [hasValidSourceParam]); const [fetchingState, setFetchingState] = useState({ loadingStatus: "",