From 4f86f3c0ecdfd30f25c2fee5820f2f060eb2090e Mon Sep 17 00:00:00 2001 From: Ahao <108321411+Ahaohaohao@users.noreply.github.com> Date: Wed, 14 May 2025 20:32:45 +0800 Subject: [PATCH] refactored IP detection and added zashboard redirect URL (#3510) * Fixed the language display size issue and updated the translation synchronously! * Fixed Russian language display issue * refactored IP detection and added zashboard redirect URL --------- Co-authored-by: Ahao <108321411+xuanyuan0408@users.noreply.github.com> --- UPDATELOG.md | 3 +- src/components/setting/mods/web-ui-viewer.tsx | 1 + src/services/api.ts | 129 +++++++++++++----- 3 files changed, 98 insertions(+), 35 deletions(-) diff --git a/UPDATELOG.md b/UPDATELOG.md index dedb6586..bc482f4b 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -44,11 +44,11 @@ - 支持手动卸载服务模式,回退到 Sidecar 模式 - 添加了土耳其语,日本语,德语,西班牙语,繁体中文的支持 - 卸载服务的按钮 + - 添加了Zashboard的一键跳转URL #### 优化了: - 系统代理 Bypass 设置 - Windows 下使用 Startup 文件夹的方式实现开机自启,解决管理员模式下开机自启的各种问题 - - 增加 IP 信息请求重试机制,减少 Network Error 发生的情况 - 切换到规则页面时自动刷新规则数据 - 重构更新失败回退机制,使用后端完成更新失败后回退到使用 Clash 代理再次尝试更新 - 编辑非激活订阅的时候不在触发当前订阅配置重载 @@ -63,6 +63,7 @@ - 优化了其他语言的翻译问题 - Mihomo 内核默认日志等级为 warn - Clash Verge Rev 应用默认日志等级为 warn + - 重构了原来的IP 信息请求重试机制,采用轮询检测,解决了 Network Error 和 超时问题 ## v2.2.3 diff --git a/src/components/setting/mods/web-ui-viewer.tsx b/src/components/setting/mods/web-ui-viewer.tsx index 35589955..569a684f 100644 --- a/src/components/setting/mods/web-ui-viewer.tsx +++ b/src/components/setting/mods/web-ui-viewer.tsx @@ -26,6 +26,7 @@ export const WebUIViewer = forwardRef((props, ref) => { const webUIList = verge?.web_ui_list || [ "https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret", "https://yacd.metacubex.one/?hostname=%host&port=%port&secret=%secret", + "https://board.zash.run.place/#/setup?http=true&hostname=%host&port=%port&secret=%secret", ]; const handleAdd = useLockFn(async (value: string) => { diff --git a/src/services/api.ts b/src/services/api.ts index 9feb34dd..9acd6c1a 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -319,42 +319,103 @@ export const gc = async () => { } }; -// Get current IP and geolocation information -export const getIpInfo = async () => { - // 添加重试机制 - const maxRetries = 3; - const retryDelay = 1500; - const timeout = 5000; +// Get current IP and geolocation information (refactored IP detection) +interface IpInfo { + ip: string; + country_code: string; + country: string; + region: string; + city: string; + organization: string; + asn: number; + asn_organization: string; + longitude: number; + latitude: number; + timezone: string; +} - let lastError; - - for (let attempt = 0; attempt < maxRetries; attempt++) { - try { - // 使用axios直接请求IP.sb的API,不通过clash代理 - const response = await axios.get("https://api.ip.sb/geoip", { timeout }); - return response.data as { - ip: string; - country_code: string; - country: string; - region: string; - city: string; - organization: string; - asn: number; - asn_organization: string; - longitude: number; - latitude: number; - timezone: string; - }; - } catch (error) { - console.log(`获取IP信息失败,尝试 ${attempt + 1}/${maxRetries}`, error); - lastError = error; +// 可用的IP检测服务列表 +const IP_CHECK_SERVICES = [ + "https://api.ip.sb/geoip", + "https://ipapi.co/json", + "http://ip-api.com/json", + "https://ipinfo.io/json", + "https://ifconfig.co/json", +]; + +// 随机打乱服务列表顺序 +function shuffleServices() { + return [...IP_CHECK_SERVICES].sort(() => Math.random() - 0.5); +} + +// 获取当前IP和地理位置信息(优化版本) +export const getIpInfo = async (): Promise => { + // 配置参数 + const maxRetries = 3; + const serviceTimeout = 5000; + const overallTimeout = 15000; + + const overallTimeoutController = new AbortController(); + const overallTimeoutId = setTimeout(() => { + overallTimeoutController.abort(); + }, overallTimeout); + + try { + const shuffledServices = shuffleServices(); + let lastError: Error | null = null; + + for (const serviceUrl of shuffledServices) { + console.log(`尝试IP检测服务: ${serviceUrl}`); - // 如果不是最后一次尝试,则等待后重试 - if (attempt < maxRetries - 1) { - await new Promise(resolve => setTimeout(resolve, retryDelay)); + for (let attempt = 0; attempt < maxRetries; attempt++) { + let timeoutId: ReturnType | null = null; + + try { + const timeoutController = new AbortController(); + timeoutId = setTimeout(() => { + timeoutController.abort(); + }, serviceTimeout); + + const response = await axios.get(serviceUrl, { + signal: timeoutController.signal, + timeout: serviceTimeout, + headers: { "User-Agent": "Mozilla/5.0" }, + }); + + if (timeoutId) clearTimeout(timeoutId); + + if (response.data && response.data.ip) { + console.log(`IP检测成功,使用服务: ${serviceUrl}`); + return response.data; + } else { + throw new Error(`无效的响应格式 from ${serviceUrl}`); + } + } catch (error: any) { + if (timeoutId) clearTimeout(timeoutId); + + lastError = error; + console.log( + `尝试 ${attempt + 1}/${maxRetries} 失败 (${serviceUrl}):`, + error.message + ); + + if (error.name === "AbortError") { + throw error; + } + + if (attempt < maxRetries - 1) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } } } - } - throw lastError; -}; + if (lastError) { + throw new Error(`所有IP检测服务都失败: ${lastError.message}`); + } else { + throw new Error('没有可用的IP检测服务'); + } + } finally { + clearTimeout(overallTimeoutId); + } +}; \ No newline at end of file