Should check provider health if a group contains provider proxies

This commit is contained in:
Haishan 2022-06-12 18:22:17 +08:00
parent 69423bdfff
commit 38571da24a
4 changed files with 104 additions and 69 deletions

View file

@ -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 (
<div
@ -161,10 +161,8 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr
const mapState = (s: State, { name }) => {
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);

View file

@ -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]);

View file

@ -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<string, boolean>();
const providerDedupMap = new Map<string, boolean>();
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<FormattedProxyProvider>;
proxies: { [key: string]: ProxyItem };
proxyProviders: Array<FormattedProxyProvider>;
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 = {

View file

@ -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<string, ProxyItem>;
export type DelayMapping = Record<string, { number?: number }>;
export type DelayMapping = Record<string, ProxyDelayItem>;
export type ProxyProvider = {
name: string;
type: 'Proxy';
updatedAt: string;
vehicleType: 'HTTP' | 'File' | 'Compatible';
proxies: Array<ProxyItem>;
proxies: ProxyItem[];
};
export type FormattedProxyProvider = Omit<ProxyProvider, 'proxies'> & {
proxies: string[];
};
export type FormattedProxyProvider = Omit<ProxyProvider, 'proxies'> & { 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;