From 98be9621a661ad62ec759bb0fa22a5206af26e70 Mon Sep 17 00:00:00 2001 From: wonfen Date: Thu, 3 Apr 2025 14:55:17 +0800 Subject: [PATCH] feat: retry subscription fetch using Clash proxy on failure --- UPDATELOG.md | 1 + src/components/profile/profile-item.tsx | 35 +++++++++--- src/components/profile/profile-viewer.tsx | 69 +++++++++++++++++++---- src/locales/en.json | 8 +++ src/locales/zh.json | 8 +++ src/pages/profiles.tsx | 27 ++++++++- src/services/cmds.ts | 4 +- 7 files changed, 130 insertions(+), 22 deletions(-) diff --git a/UPDATELOG.md b/UPDATELOG.md index 5f8d0f3b..4a469309 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -25,6 +25,7 @@ - 代理组显示节点数量 - 统一运行模式检测,支持管理员模式下开启TUN模式 - 托盘切换代理模式会根据设置自动断开之前连接 + - 如订阅获取失败回退使用Clash内核代理再次尝试 #### 移除了: - 实时保存窗口位置和大小。这个功能可能会导致窗口异常大小和位置,还需观察。 diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index 05936f0e..c824902b 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -179,17 +179,21 @@ export const ProfileItem = (props: Props) => { /// 0 不使用任何代理 /// 1 使用订阅好的代理 /// 2 至少使用一个代理,根据订阅,如果没订阅,默认使用系统代理 - const onUpdate = useLockFn(async (type: 0 | 1 | 2) => { + const onUpdate = useLockFn(async (type: 0 | 1 | 2): Promise => { setAnchorEl(null); setLoadingCache((cache) => ({ ...cache, [itemData.uid]: true })); - const option: Partial = {}; + // 存储原始设置以便回退后恢复 + const originalOptions = { + with_proxy: itemData.option?.with_proxy, + self_proxy: itemData.option?.self_proxy + }; + // 根据类型设置初始更新选项 + const option: Partial = {}; if (type === 0) { option.with_proxy = false; option.self_proxy = false; - } else if (type === 1) { - // nothing } else if (type === 2) { if (itemData.option?.self_proxy) { option.with_proxy = false; @@ -201,14 +205,31 @@ export const ProfileItem = (props: Props) => { } try { + // 尝试正常更新 await updateProfile(itemData.uid, option); Notice.success(t("Update subscription successfully")); mutate("getProfiles"); } catch (err: any) { + // 更新失败,尝试使用自身代理 const errmsg = err?.message || err.toString(); - Notice.error( - errmsg.replace(/error sending request for url (\S+?): /, ""), - ); + Notice.info(t("Update failed, retrying with Clash proxy...")); + + try { + await updateProfile(itemData.uid, { + with_proxy: false, + self_proxy: true + }); + + Notice.success(t("Update with Clash proxy successfully")); + + await updateProfile(itemData.uid, originalOptions); + mutate("getProfiles"); + } catch (retryErr: any) { + const retryErrmsg = retryErr?.message || retryErr.toString(); + Notice.error( + `${t("Update failed even with Clash proxy")}: ${retryErrmsg.replace(/error sending request for url (\S+?): /, "")}`, + ); + } } finally { setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false })); } diff --git a/src/components/profile/profile-viewer.tsx b/src/components/profile/profile-viewer.tsx index 06879d0d..7fbc870a 100644 --- a/src/components/profile/profile-viewer.tsx +++ b/src/components/profile/profile-viewer.tsx @@ -88,11 +88,13 @@ export const ProfileViewer = forwardRef( formIns.handleSubmit(async (form) => { setLoading(true); try { + // 基本验证 if (!form.type) throw new Error("`Type` should not be null"); if (form.type === "remote" && !form.url) { throw new Error("The URL should not be null"); } + // 处理表单数据 if (form.option?.update_interval) { form.option.update_interval = +form.option.update_interval; } else { @@ -101,25 +103,72 @@ export const ProfileViewer = forwardRef( if (form.option?.user_agent === "") { delete form.option.user_agent; } + const name = form.name || `${form.type} file`; const item = { ...form, name }; - - // 创建 - if (openType === "new") { - await createProfile(item, fileDataRef.current); - } - // 编辑 - else { - if (!form.uid) throw new Error("UID not found"); - await patchProfile(form.uid, item); + const isRemote = form.type === "remote"; + + // 保存原始代理设置以便回退成功后恢复 + const originalOptions = { + with_proxy: form.option?.with_proxy, + self_proxy: form.option?.self_proxy + }; + + // 执行创建或更新操作,本地配置不需要回退机制 + if (!isRemote) { + if (openType === "new") { + await createProfile(item, fileDataRef.current); + } else { + if (!form.uid) throw new Error("UID not found"); + await patchProfile(form.uid, item); + } + } else { + // 远程配置使用回退机制 + try { + // 尝试正常操作 + if (openType === "new") { + await createProfile(item, fileDataRef.current); + } else { + if (!form.uid) throw new Error("UID not found"); + await patchProfile(form.uid, item); + } + } catch (err) { + // 首次创建/更新失败,尝试使用自身代理 + Notice.info(t("Profile creation failed, retrying with Clash proxy...")); + + // 使用自身代理的配置 + const retryItem = { + ...item, + option: { + ...item.option, + with_proxy: false, + self_proxy: true + } + }; + + // 使用自身代理再次尝试 + if (openType === "new") { + await createProfile(retryItem, fileDataRef.current); + } else { + if (!form.uid) throw new Error("UID not found"); + await patchProfile(form.uid, retryItem); + + // 编辑模式下恢复原始代理设置 + await patchProfile(form.uid, { option: originalOptions }); + } + + Notice.success(t("Profile creation succeeded with Clash proxy")); + } } + + // 成功后的操作 setOpen(false); - setLoading(false); setTimeout(() => formIns.reset(), 500); fileDataRef.current = null; props.onChange(); } catch (err: any) { Notice.error(err.message || err.toString()); + } finally { setLoading(false); } }) diff --git a/src/locales/en.json b/src/locales/en.json index 56133040..19aaa492 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -560,6 +560,14 @@ "Last Check Update": "Last Check Update", "Click to import subscription": "Click to import subscription", "Update subscription successfully": "Update subscription successfully", + "Update failed, retrying with Clash proxy...": "Update failed, retrying with Clash proxy...", + "Update with Clash proxy successfully": "Update with Clash proxy successfully", + "Update failed even with Clash proxy": "Update failed even with Clash proxy", + "Profile creation failed, retrying with Clash proxy...": "Profile creation failed, retrying with Clash proxy...", + "Profile creation succeeded with Clash proxy": "Profile creation succeeded with Clash proxy", + "Import failed, retrying with Clash proxy...": "Import failed, retrying with Clash proxy...", + "Profile Imported with Clash proxy": "Profile Imported with Clash proxy", + "Import failed even with Clash proxy": "Import failed even with Clash proxy", "Current Node": "Current Node", "No active proxy node": "No active proxy node", "Network Settings": "Network Settings", diff --git a/src/locales/zh.json b/src/locales/zh.json index 73f759b4..7b3a4f0e 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -560,6 +560,14 @@ "Last Check Update": "最后检查更新", "Click to import subscription": "点击导入订阅", "Update subscription successfully": "订阅更新成功", + "Update failed, retrying with Clash proxy...": "订阅更新失败,尝试使用 Clash 代理更新", + "Update with Clash proxy successfully": "使用 Clash 代理更新成功", + "Update failed even with Clash proxy": "使用 Clash 代理更新也失败", + "Profile creation failed, retrying with Clash proxy...": "订阅创建失败,尝试使用 Clash 代理创建", + "Profile creation succeeded with Clash proxy": "使用 Clash 代理创建订阅成功", + "Import failed, retrying with Clash proxy...": "订阅导入失败,尝试使用 Clash 代理导入", + "Profile Imported with Clash proxy": "使用 Clash 代理导入订阅成功", + "Import failed even with Clash proxy": "使用 Clash 代理导入订阅也失败", "Current Node": "当前节点", "No active proxy node": "暂无激活的代理节点", "Network Settings": "网络设置", diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index 1c4654b2..9b4f3d4e 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -142,15 +142,36 @@ const ProfilePage = () => { setLoading(true); try { + // 尝试正常导入 await importProfile(url); Notice.success(t("Profile Imported Successfully")); setUrl(""); - setLoading(false); mutateProfiles(); await onEnhance(false); } catch (err: any) { - Notice.error(err.message || err.toString()); - setLoading(false); + // 首次导入失败,尝试使用自身代理 + const errmsg = err.message || err.toString(); + Notice.info(t("Import failed, retrying with Clash proxy...")); + + try { + // 使用自身代理尝试导入 + await importProfile(url, { + with_proxy: false, + self_proxy: true + }); + + // 回退导入成功 + Notice.success(t("Profile Imported with Clash proxy")); + setUrl(""); + mutateProfiles(); + await onEnhance(false); + } catch (retryErr: any) { + // 回退导入也失败 + const retryErrmsg = retryErr?.message || retryErr.toString(); + Notice.error( + `${t("Import failed even with Clash proxy")}: ${retryErrmsg}`, + ); + } } finally { setDisabled(false); setLoading(false); diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 81be7b99..454315aa 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -36,10 +36,10 @@ export async function saveProfileFile(index: string, fileData: string) { return invoke("save_profile_file", { index, fileData }); } -export async function importProfile(url: string) { +export async function importProfile(url: string, option?: IProfileOption) { return invoke("import_profile", { url, - option: { with_proxy: true }, + option: option || { with_proxy: true }, }); }