diff --git a/src/components/proxies/Proxy.tsx b/src/components/proxies/Proxy.tsx
index 8cc668f..47a3d54 100644
--- a/src/components/proxies/Proxy.tsx
+++ b/src/components/proxies/Proxy.tsx
@@ -47,7 +47,7 @@ type ProxyProps = {
name: string;
now?: boolean;
proxy: any;
- latency: any;
+ latency?: { number?: number };
isSelectable?: boolean;
onClick?: (proxyName: string) => unknown;
};
@@ -130,10 +130,10 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr
const className = useMemo(() => {
return cx(s0.proxy, {
[s0.now]: now,
- [s0.error]: latency && latency.error,
+ // [s0.error]: latency && latency.error,
[s0.selectable]: isSelectable,
});
- }, [isSelectable, now, latency]);
+ }, [isSelectable, now]);
return (
{
const proxies = getProxies(s);
const delay = getDelay(s);
- return {
- proxy: proxies[name],
- latency: delay[name],
- };
+ const proxy = proxies[name] || { name, type: 'Unknown', history: [] };
+ return { proxy, latency: delay[name] };
};
export const Proxy = connect(mapState)(ProxyImpl);
diff --git a/src/components/proxies/ProxyGroup.tsx b/src/components/proxies/ProxyGroup.tsx
index 6d6c59e..857a480 100644
--- a/src/components/proxies/ProxyGroup.tsx
+++ b/src/components/proxies/ProxyGroup.tsx
@@ -2,7 +2,8 @@ import Tooltip from '@reach/tooltip';
import * as React from 'react';
import { useState2 } from '$src/hooks/basic';
-import { State } from '$src/store/types';
+import { DelayMapping, DispatchFn, ProxiesMapping, State } from '$src/store/types';
+import { ClashAPIConfig } from '$src/types';
import { getCollapsibleIsOpen, getHideUnavailableProxies, getProxySortBy } from '../../store/app';
import { getProxies, switchProxy } from '../../store/proxies';
@@ -16,6 +17,20 @@ import { ProxyList, ProxyListSummaryView } from './ProxyList';
const { createElement, useCallback, useMemo } = React;
+type ProxyGroupImplProps = {
+ name: string;
+ all: string[];
+ delay: DelayMapping;
+ hideUnavailableProxies: boolean;
+ proxySortBy: string;
+ proxies: ProxiesMapping;
+ type: string;
+ now: string;
+ isOpen: boolean;
+ apiConfig: ClashAPIConfig;
+ dispatch: DispatchFn;
+};
+
function ProxyGroupImpl({
name,
all: allItems,
@@ -28,7 +43,7 @@ function ProxyGroupImpl({
isOpen,
apiConfig,
dispatch,
-}) {
+}: ProxyGroupImplProps) {
const all = useFilteredAndSorted(allItems, delay, hideUnavailableProxies, proxySortBy, proxies);
const isSelectable = useMemo(() => type === 'Selector', [type]);
diff --git a/src/store/proxies.tsx b/src/store/proxies.tsx
index ad0a84e..5f4c0aa 100644
--- a/src/store/proxies.tsx
+++ b/src/store/proxies.tsx
@@ -1,8 +1,10 @@
import { atom } from 'recoil';
import {
+ DelayMapping,
DispatchFn,
FormattedProxyProvider,
GetStateFn,
+ LatencyHistory,
ProxiesMapping,
ProxyItem,
ProxyProvider,
@@ -50,6 +52,19 @@ export const getProxyProviders = (s: State) => s.proxies.proxyProviders || [];
export const getDangleProxyNames = (s: State) => s.proxies.dangleProxyNames;
export const getShowModalClosePrevConns = (s: State) => s.proxies.showModalClosePrevConns;
+function mapLatency(names: string[], getProxy: (name: string) => { history: LatencyHistory }) {
+ const result: DelayMapping = {};
+ for (const name of names) {
+ const p = getProxy(name) || { history: [] };
+ const history = p.history;
+ const h = history[history.length - 1];
+ if (h && typeof h.delay === 'number') {
+ result[name] = { number: h.delay };
+ }
+ }
+ return result;
+}
+
export function fetchProxies(apiConfig: ClashAPIConfig) {
return async (dispatch: any, getState: any) => {
const [proxiesData, providersData] = await Promise.all([
@@ -57,36 +72,28 @@ export function fetchProxies(apiConfig: ClashAPIConfig) {
proxiesAPI.fetchProviderProxies(apiConfig),
]);
- const { providers: proxyProviders, proxies: providerProxies } = formatProxyProviders(
- providersData.providers
- );
- const proxies = { ...proxiesData.proxies, ...providerProxies };
+ const { proxyProviders, providerProxyRecord } = formatProxyProviders(providersData.providers);
+
+ const proxies = { ...providerProxyRecord, ...proxiesData.proxies };
const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies);
- const delayPrev = getDelay(getState());
- const delayNext = { ...delayPrev };
-
- for (let i = 0; i < proxyNames.length; i++) {
- const name = proxyNames[i];
- const { history } = proxies[name] || { history: [] };
- const h = history[history.length - 1];
- if (h && typeof h.delay === 'number') {
- delayNext[name] = { number: h.delay };
- }
- }
+ const delayNext = {
+ ...getDelay(getState()),
+ ...mapLatency(Object.keys(proxies), (name) => proxies[name]),
+ };
// proxies that are not from a provider
const dangleProxyNames = [];
for (const v of proxyNames) {
- if (!providerProxies[v]) dangleProxyNames.push(v);
+ if (!providerProxyRecord[v]) dangleProxyNames.push(v);
}
dispatch('store/proxies#fetchProxies', (s: State) => {
s.proxies.proxies = proxies;
s.proxies.groupNames = groupNames;
+ s.proxies.dangleProxyNames = dangleProxyNames;
s.proxies.delay = delayNext;
s.proxies.proxyProviders = proxyProviders;
- s.proxies.dangleProxyNames = dangleProxyNames;
});
};
}
@@ -201,11 +208,6 @@ async function switchProxyImpl(
// no wait
closePrevConns(apiConfig, proxies, { groupName, itemName });
}
-
- /* dispatch('showModalClosePrevConns', (s: GlobalState) => { */
- /* s.proxies.showModalClosePrevConns = true; */
- /* s.proxies.switchProxyCtx = { to: { groupName, itemName } }; */
- /* }); */
}
function closeModalClosePrevConns() {
@@ -273,15 +275,7 @@ function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) {
error = res.statusText;
}
const { delay } = await res.json();
-
- const delayPrev = getDelay(getState());
- const delayNext = {
- ...delayPrev,
- [name]: {
- error,
- number: delay,
- },
- };
+ const delayNext = { ...getDelay(getState()), [name]: { error, number: delay } };
dispatch('requestDelayForProxyOnce', (s) => {
s.proxies.delay = delayNext;
@@ -297,12 +291,33 @@ export function requestDelayForProxy(apiConfig: ClashAPIConfig, name: string) {
export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
- const proxyNames = getDangleProxyNames(getState());
+ const proxies = getProxies(getState());
+ const latencyTestUrl = getLatencyTestUrl(getState());
- const works = names
- // remove names that are provided by proxy providers
- .filter((p) => proxyNames.indexOf(p) > -1)
- .map((p) => dispatch(requestDelayForProxy(apiConfig, p)));
+ const proxyDedupMap = new Map
();
+ const providerDedupMap = new Map();
+
+ const works = names.map((name) => {
+ const p = proxies[name];
+ if (!p.__provider) {
+ if (proxyDedupMap.get(name)) {
+ return undefined;
+ } else {
+ proxyDedupMap.set(name, true);
+ return proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl);
+ }
+ } 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);
+ }
+ } else {
+ return undefined;
+ }
+ });
await Promise.all(works);
await dispatch(fetchProxies(apiConfig));
};
@@ -311,7 +326,10 @@ export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[
export function requestDelayAll(apiConfig: ClashAPIConfig) {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const proxyNames = getDangleProxyNames(getState());
- await Promise.all(proxyNames.map((p) => dispatch(requestDelayForProxy(apiConfig, p))));
+ const latencyTestUrl = getLatencyTestUrl(getState());
+ await Promise.all(
+ proxyNames.map((p) => proxiesAPI.requestDelayForProxy(apiConfig, p, latencyTestUrl))
+ );
const proxyProviders = getProxyProviders(getState());
// one by one
for (const p of proxyProviders) {
@@ -353,12 +371,13 @@ type ProvidersRaw = {
};
function formatProxyProviders(providersInput: ProvidersRaw): {
- providers: Array;
- proxies: { [key: string]: ProxyItem };
+ proxyProviders: Array;
+ providerProxyRecord: ProxiesMapping;
} {
const keys = Object.keys(providersInput);
- const providers = [];
- const proxies = {};
+ const proxyProviders = [];
+ const providerProxyRecord: ProxiesMapping = {};
+
for (let i = 0; i < keys.length; i++) {
const provider: ProxyProvider = providersInput[keys[i]];
if (provider.name === 'default' || provider.vehicleType === 'Compatible') {
@@ -368,19 +387,16 @@ function formatProxyProviders(providersInput: ProvidersRaw): {
const names = [];
for (let j = 0; j < proxiesArr.length; j++) {
const proxy = proxiesArr[j];
- proxies[proxy.name] = proxy;
+ providerProxyRecord[proxy.name] = { ...proxy, __provider: provider.name };
names.push(proxy.name);
}
// mutate directly
provider.proxies = names;
- providers.push(provider);
+ proxyProviders.push(provider);
}
- return {
- providers,
- proxies,
- };
+ return { proxyProviders, providerProxyRecord };
}
export const actions = {
diff --git a/src/store/types.ts b/src/store/types.ts
index d12adaa..b0b457e 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -27,39 +27,45 @@ export type ClashGeneralConfig = {
///// store.proxies
-type LatencyHistory = Array<{ time: string; delay: number }>;
-type PrimitiveProxyType = 'Shadowsocks' | 'Snell' | 'Socks5' | 'Http' | 'Vmess';
+type LatencyHistoryItem = { time: string; delay: number };
+export type LatencyHistory = LatencyHistoryItem[];
+
export type ProxyItem = {
name: string;
- type: PrimitiveProxyType;
+ type: string;
history: LatencyHistory;
all?: string[];
now?: string;
+
+ __provider?: string;
};
+
+export type ProxyDelayItem = {
+ number?: number;
+};
+
export type ProxiesMapping = Record;
-export type DelayMapping = Record;
+export type DelayMapping = Record;
export type ProxyProvider = {
name: string;
type: 'Proxy';
updatedAt: string;
vehicleType: 'HTTP' | 'File' | 'Compatible';
- proxies: Array;
+ proxies: ProxyItem[];
};
-export type FormattedProxyProvider = Omit & {
- proxies: string[];
-};
+export type FormattedProxyProvider = Omit & { proxies: string[] };
export type SwitchProxyCtxItem = { groupName: string; itemName: string };
-type SwitchProxyCtx = {
- to: SwitchProxyCtxItem;
-};
+type SwitchProxyCtx = { to: SwitchProxyCtxItem };
+
export type StateProxies = {
- proxies: ProxiesMapping;
- delay: DelayMapping;
groupNames: string[];
proxyProviders?: FormattedProxyProvider[];
+
+ proxies: ProxiesMapping;
+ delay: DelayMapping;
dangleProxyNames?: string[];
showModalClosePrevConns: boolean;