diff --git a/Changelog.md b/Changelog.md index 24e787ea7..2d704f5e3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -30,5 +30,6 @@ - 连通性测试替换为更快的 https://1.1.1.1 - 连接、规则、日志等页面的过滤搜索组件新增了清空输入框按钮 - 链式代理增加明显的入口出口与数据流向标识 +- 优化 IP 信息卡 diff --git a/src/components/home/ip-info-card.tsx b/src/components/home/ip-info-card.tsx index 249a9f792..bc2c2ac63 100644 --- a/src/components/home/ip-info-card.tsx +++ b/src/components/home/ip-info-card.tsx @@ -21,11 +21,13 @@ import { useTranslation } from "react-i18next"; import useSWRImmutable from "swr/immutable"; import { getIpInfo } from "@/services/api"; +import { SWR_EXTERNAL_API } from "@/services/config"; import { EnhancedCard } from "./enhanced-card"; // 定义刷新时间(秒) const IP_REFRESH_SECONDS = 300; +const COUNTDOWN_TICK_INTERVAL = 5_000; const IP_INFO_CACHE_KEY = "cv_ip_info_cache"; const InfoItem = memo(({ label, value }: { label: string; value?: string }) => ( @@ -180,7 +182,7 @@ export const IpInfoCard = () => { console.debug( "IP info card has entered the viewport, starting the countdown interval.", ); - timer = window.setInterval(onCountdownTick, 1000); + timer = window.setInterval(onCountdownTick, COUNTDOWN_TICK_INTERVAL); } else { console.debug( "IP info card has not yet entered the viewport, no counting down.", @@ -207,7 +209,7 @@ export const IpInfoCard = () => { console.debug("Document visible, resume the interval"); // Resume the timer only when previous one is cleared if (timer == null) { - timer = window.setInterval(onCountdownTick, 1000); + timer = window.setInterval(onCountdownTick, COUNTDOWN_TICK_INTERVAL); } } else { console.debug( @@ -420,7 +422,5 @@ export const IpInfoCard = () => { }; function useIPInfo() { - return useSWRImmutable(IP_INFO_CACHE_KEY, getIpInfo, { - shouldRetryOnError: true, - }); + return useSWRImmutable(IP_INFO_CACHE_KEY, getIpInfo, SWR_EXTERNAL_API); } diff --git a/src/services/api.ts b/src/services/api.ts index b68f4fa39..48c57900e 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -137,62 +137,6 @@ const IP_CHECK_SERVICES: ServiceConfig[] = [ }, ]; -// 随机性服务列表洗牌函数 -function shuffleServices() { - // 过滤无效服务并确保每个元素符合ServiceConfig接口 - const validServices = IP_CHECK_SERVICES.filter( - (service): service is ServiceConfig => - service !== null && - service !== undefined && - typeof service.url === "string" && - typeof service.mapping === "function", // 添加对mapping属性的检查 - ); - - if (validServices.length === 0) { - console.error("No valid services found in IP_CHECK_SERVICES"); - return []; - } - - // 使用单一Fisher-Yates洗牌算法,增强随机性 - const shuffled = [...validServices]; - const length = shuffled.length; - - // 使用多个种子进行多次洗牌 - const seeds = [Math.random(), Date.now() / 1000, performance.now() / 1000]; - - for (const seed of seeds) { - const prng = createPrng(seed); - - // Fisher-Yates洗牌算法 - for (let i = length - 1; i > 0; i--) { - const j = Math.floor(prng() * (i + 1)); - - // 使用临时变量进行交换,避免解构赋值可能的问题 - const temp = shuffled[i]; - shuffled[i] = shuffled[j]; - shuffled[j] = temp; - } - } - - return shuffled; -} - -// 创建一个简单的随机数生成器 -function createPrng(seed: number): () => number { - // 使用xorshift32算法 - let state = seed >>> 0; - - // 如果种子为0,设置一个默认值 - if (state === 0) state = 123456789; - - return function () { - state ^= state << 13; - state ^= state >>> 17; - state ^= state << 5; - return (state >>> 0) / 4294967296; - }; -} - // 获取当前IP和地理位置信息 export const getIpInfo = async (): Promise< IpInfo & { lastFetchTs: number } @@ -201,7 +145,9 @@ export const getIpInfo = async (): Promise< const maxRetries = 2; const serviceTimeout = 5000; - const shuffledServices = shuffleServices(); + const shuffledServices = IP_CHECK_SERVICES.toSorted( + () => Math.random() - 0.5, + ); let lastError: unknown | null = null; const userAgent = await getUserAgentPromise(); console.debug("User-Agent for IP detection:", userAgent); @@ -236,7 +182,12 @@ export const getIpInfo = async (): Promise< ); } - const data = await response.json(); + let data: any; + try { + data = await response.json(); + } catch { + return bail(new Error(`无法解析 JSON 响应 from ${service.url}`)); + } if (data && data.ip) { debugLog(`IP检测成功,使用服务: ${service.url}`); @@ -245,7 +196,7 @@ export const getIpInfo = async (): Promise< lastFetchTs: Date.now(), }); } else { - throw new Error(`无效的响应格式 from ${service.url}`); + return bail(new Error(`无效的响应格式 from ${service.url}`)); } }, { diff --git a/src/services/config.ts b/src/services/config.ts index 52eba3bbe..02f73f350 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -26,3 +26,10 @@ export const SWR_MIHOMO = { errorRetryInterval: 500, errorRetryCount: 15, }; + +export const SWR_EXTERNAL_API = { + ...SWR_NOT_SMART, + shouldRetryOnError: true, + errorRetryCount: 1, + errorRetryInterval: 30_000, +} as const;