diff --git a/src/components/proxies/Proxy.tsx b/src/components/proxies/Proxy.tsx index 47a3d54..4b63921 100644 --- a/src/components/proxies/Proxy.tsx +++ b/src/components/proxies/Proxy.tsx @@ -2,7 +2,7 @@ import { TooltipPopup, useTooltip } from '@reach/tooltip'; import cx from 'clsx'; import * as React from 'react'; -import { State } from '$src/store/types'; +import { ProxyDelayItem, State } from '$src/store/types'; import { getDelay, getProxies, NonProxyTypes } from '../../store/proxies'; import { connect } from '../StateProvider'; @@ -22,7 +22,9 @@ const colorMap = { na: '#909399', }; -function getLabelColor({ number }: { number?: number } = {}) { +function getLabelColor(latency: ProxyDelayItem) { + if (!latency || latency.kind !== 'Result') return colorMap.na; + const number = latency.number; if (number === 0) { return colorMap.na; } else if (number < 200) { @@ -35,7 +37,7 @@ function getLabelColor({ number }: { number?: number } = {}) { return colorMap.na; } -function getProxyDotStyle(latency: { number?: number }, proxyType: string) { +function getProxyDotStyle(latency: ProxyDelayItem, proxyType: string) { if (NonProxyTypes.indexOf(proxyType) > -1) { return { border: '1px dotted #777' }; } @@ -47,7 +49,7 @@ type ProxyProps = { name: string; now?: boolean; proxy: any; - latency?: { number?: number }; + latency?: ProxyDelayItem; isSelectable?: boolean; onClick?: (proxyName: string) => unknown; }; @@ -56,7 +58,7 @@ function ProxySmallImpl({ now, name, proxy, latency, isSelectable, onClick }: Pr const style = useMemo(() => getProxyDotStyle(latency, proxy.type), [latency, proxy]); const title = useMemo(() => { let ret = name; - if (latency && typeof latency.number === 'number') { + if (latency && latency.kind === 'Result' && typeof latency.number === 'number') { ret += ' ' + latency.number + ' ms'; } return ret; @@ -152,7 +154,7 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr {formatProxyType(proxy.type)} - + ); diff --git a/src/components/proxies/ProxyLatency.tsx b/src/components/proxies/ProxyLatency.tsx index 29036d5..7ba2604 100644 --- a/src/components/proxies/ProxyLatency.tsx +++ b/src/components/proxies/ProxyLatency.tsx @@ -1,16 +1,32 @@ import * as React from 'react'; +import { ProxyDelayItem } from '$src/store/types'; + import s0 from './ProxyLatency.module.scss'; type ProxyLatencyProps = { - number?: number; + latency: ProxyDelayItem | undefined; color: string; }; -export function ProxyLatency({ number, color }: ProxyLatencyProps) { +export function ProxyLatency({ latency, color }: ProxyLatencyProps) { + let text = ' '; + if (latency) { + switch (latency.kind) { + case 'Error': + case 'Testing': + text = '- ms'; + break; + case 'Result': + text = (latency.number !== 0 ? latency.number : '-') + ' ms'; + break; + default: + break; + } + } return ( - {typeof number === 'number' && number !== 0 ? number + ' ms' : ' '} + {text} ); } diff --git a/src/misc/i18n.ts b/src/misc/i18n.ts index 13cedd4..e8515c4 100644 --- a/src/misc/i18n.ts +++ b/src/misc/i18n.ts @@ -5,11 +5,7 @@ import { initReactI18next } from 'react-i18next'; const LngBackend = { type: 'backend' as const, - read: ( - lng: string, - _namespace: string, - callback: ReadCallback, - ) => { + read: (lng: string, _namespace: string, callback: ReadCallback) => { let p: PromiseLike<{ data: any }>; switch (lng) { case 'zh': @@ -22,12 +18,15 @@ const LngBackend = { break; } if (p) { - p.then(d => callback(null, d.data), err => callback(err, null)); + p.then( + (d) => callback(null, d.data), + (err) => callback(err, null) + ); } else { - callback(new Error(`unable to load translation file for language ${lng}`), null) + callback(new Error(`unable to load translation file for language ${lng}`), null); } - } -} + }, +}; i18next .use(initReactI18next) diff --git a/src/store/proxies.tsx b/src/store/proxies.tsx index 5f4c0aa..0022816 100644 --- a/src/store/proxies.tsx +++ b/src/store/proxies.tsx @@ -59,7 +59,7 @@ function mapLatency(names: string[], getProxy: (name: string) => { history: Late const history = p.history; const h = history[history.length - 1]; if (h && typeof h.delay === 'number') { - result[name] = { number: h.delay }; + result[name] = { kind: 'Result', number: h.delay }; } } return result; @@ -268,18 +268,35 @@ export function switchProxy(apiConfig: ClashAPIConfig, groupName: string, itemNa function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) { return async (dispatch: DispatchFn, getState: GetStateFn) => { - const latencyTestUrl = getLatencyTestUrl(getState()); - const res = await proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl); - let error = ''; - if (res.ok === false) { - error = res.statusText; - } - const { delay } = await res.json(); - const delayNext = { ...getDelay(getState()), [name]: { error, number: delay } }; - - dispatch('requestDelayForProxyOnce', (s) => { - s.proxies.delay = delayNext; + dispatch('set latency state to testing in progress', (s) => { + s.proxies.delay = { ...getDelay(getState()), [name]: { kind: 'Testing' } }; }); + + const latencyTestUrl = getLatencyTestUrl(getState()); + + try { + const res = await proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl); + if (res.ok) { + const { delay } = await res.json(); + dispatch('set latency result', (s) => { + s.proxies.delay = { ...getDelay(getState()), [name]: { kind: 'Result', number: delay } }; + }); + } else { + dispatch('set latency testing error', (s) => { + s.proxies.delay = { + ...getDelay(getState()), + [name]: { kind: 'Error', message: res.statusText }, + }; + }); + } + } catch (err) { + dispatch('set latency testing networkish error', (s) => { + s.proxies.delay = { + ...getDelay(getState()), + [name]: { kind: 'Error', message: err.message || err.type }, + }; + }); + } }; } @@ -292,30 +309,31 @@ export function requestDelayForProxy(apiConfig: ClashAPIConfig, name: string) { export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) { return async (dispatch: DispatchFn, getState: GetStateFn) => { const proxies = getProxies(getState()); - const latencyTestUrl = getLatencyTestUrl(getState()); const proxyDedupMap = new Map(); const providerDedupMap = new Map(); - const works = names.map((name) => { + const works: Array> = []; + + names.forEach((name) => { const p = proxies[name]; if (!p.__provider) { - if (proxyDedupMap.get(name)) { - return undefined; - } else { + if (!proxyDedupMap.get(name)) { proxyDedupMap.set(name, true); - return proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl); + dispatch(requestDelayForProxyOnce(apiConfig, name)); } } else if (p.__provider) { - // this one is from a proxy provider - if (providerDedupMap.get(p.__provider)) { - return undefined; - } else { - providerDedupMap.set(p.__provider, true); - return healthcheckProviderByNameInternal(apiConfig, p.__provider); + if (!proxyDedupMap.get(name)) { + proxyDedupMap.set(name, true); + dispatch('set latency state to testing in progress', (s) => { + s.proxies.delay = { ...getDelay(getState()), [name]: { kind: 'Testing' } }; + }); + } + // this one is from a proxy provider + if (!providerDedupMap.get(p.__provider)) { + providerDedupMap.set(p.__provider, true); + works.push(healthcheckProviderByNameInternal(apiConfig, p.__provider)); } - } else { - return undefined; } }); await Promise.all(works); diff --git a/src/store/types.ts b/src/store/types.ts index b0b457e..867e437 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -40,9 +40,11 @@ export type ProxyItem = { __provider?: string; }; -export type ProxyDelayItem = { - number?: number; -}; +export type ProxyDelayItem = + | { kind: 'Result'; number: number } + | { kind: 'Testing' } + | { kind: 'Error'; message: string } + | { kind: 'None' }; export type ProxiesMapping = Record; export type DelayMapping = Record;