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; name: string;
now?: boolean; now?: boolean;
proxy: any; proxy: any;
latency: any; latency?: { number?: number };
isSelectable?: boolean; isSelectable?: boolean;
onClick?: (proxyName: string) => unknown; onClick?: (proxyName: string) => unknown;
}; };
@ -130,10 +130,10 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr
const className = useMemo(() => { const className = useMemo(() => {
return cx(s0.proxy, { return cx(s0.proxy, {
[s0.now]: now, [s0.now]: now,
[s0.error]: latency && latency.error, // [s0.error]: latency && latency.error,
[s0.selectable]: isSelectable, [s0.selectable]: isSelectable,
}); });
}, [isSelectable, now, latency]); }, [isSelectable, now]);
return ( return (
<div <div
@ -161,10 +161,8 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr
const mapState = (s: State, { name }) => { const mapState = (s: State, { name }) => {
const proxies = getProxies(s); const proxies = getProxies(s);
const delay = getDelay(s); const delay = getDelay(s);
return { const proxy = proxies[name] || { name, type: 'Unknown', history: [] };
proxy: proxies[name], return { proxy, latency: delay[name] };
latency: delay[name],
};
}; };
export const Proxy = connect(mapState)(ProxyImpl); export const Proxy = connect(mapState)(ProxyImpl);

View file

@ -2,7 +2,8 @@ import Tooltip from '@reach/tooltip';
import * as React from 'react'; import * as React from 'react';
import { useState2 } from '$src/hooks/basic'; 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 { getCollapsibleIsOpen, getHideUnavailableProxies, getProxySortBy } from '../../store/app';
import { getProxies, switchProxy } from '../../store/proxies'; import { getProxies, switchProxy } from '../../store/proxies';
@ -16,6 +17,20 @@ import { ProxyList, ProxyListSummaryView } from './ProxyList';
const { createElement, useCallback, useMemo } = React; 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({ function ProxyGroupImpl({
name, name,
all: allItems, all: allItems,
@ -28,7 +43,7 @@ function ProxyGroupImpl({
isOpen, isOpen,
apiConfig, apiConfig,
dispatch, dispatch,
}) { }: ProxyGroupImplProps) {
const all = useFilteredAndSorted(allItems, delay, hideUnavailableProxies, proxySortBy, proxies); const all = useFilteredAndSorted(allItems, delay, hideUnavailableProxies, proxySortBy, proxies);
const isSelectable = useMemo(() => type === 'Selector', [type]); const isSelectable = useMemo(() => type === 'Selector', [type]);

View file

@ -1,8 +1,10 @@
import { atom } from 'recoil'; import { atom } from 'recoil';
import { import {
DelayMapping,
DispatchFn, DispatchFn,
FormattedProxyProvider, FormattedProxyProvider,
GetStateFn, GetStateFn,
LatencyHistory,
ProxiesMapping, ProxiesMapping,
ProxyItem, ProxyItem,
ProxyProvider, ProxyProvider,
@ -50,6 +52,19 @@ export const getProxyProviders = (s: State) => s.proxies.proxyProviders || [];
export const getDangleProxyNames = (s: State) => s.proxies.dangleProxyNames; export const getDangleProxyNames = (s: State) => s.proxies.dangleProxyNames;
export const getShowModalClosePrevConns = (s: State) => s.proxies.showModalClosePrevConns; 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) { export function fetchProxies(apiConfig: ClashAPIConfig) {
return async (dispatch: any, getState: any) => { return async (dispatch: any, getState: any) => {
const [proxiesData, providersData] = await Promise.all([ const [proxiesData, providersData] = await Promise.all([
@ -57,36 +72,28 @@ export function fetchProxies(apiConfig: ClashAPIConfig) {
proxiesAPI.fetchProviderProxies(apiConfig), proxiesAPI.fetchProviderProxies(apiConfig),
]); ]);
const { providers: proxyProviders, proxies: providerProxies } = formatProxyProviders( const { proxyProviders, providerProxyRecord } = formatProxyProviders(providersData.providers);
providersData.providers
); const proxies = { ...providerProxyRecord, ...proxiesData.proxies };
const proxies = { ...proxiesData.proxies, ...providerProxies };
const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies); const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies);
const delayPrev = getDelay(getState()); const delayNext = {
const delayNext = { ...delayPrev }; ...getDelay(getState()),
...mapLatency(Object.keys(proxies), (name) => proxies[name]),
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 };
}
}
// proxies that are not from a provider // proxies that are not from a provider
const dangleProxyNames = []; const dangleProxyNames = [];
for (const v of proxyNames) { for (const v of proxyNames) {
if (!providerProxies[v]) dangleProxyNames.push(v); if (!providerProxyRecord[v]) dangleProxyNames.push(v);
} }
dispatch('store/proxies#fetchProxies', (s: State) => { dispatch('store/proxies#fetchProxies', (s: State) => {
s.proxies.proxies = proxies; s.proxies.proxies = proxies;
s.proxies.groupNames = groupNames; s.proxies.groupNames = groupNames;
s.proxies.dangleProxyNames = dangleProxyNames;
s.proxies.delay = delayNext; s.proxies.delay = delayNext;
s.proxies.proxyProviders = proxyProviders; s.proxies.proxyProviders = proxyProviders;
s.proxies.dangleProxyNames = dangleProxyNames;
}); });
}; };
} }
@ -201,11 +208,6 @@ async function switchProxyImpl(
// no wait // no wait
closePrevConns(apiConfig, proxies, { groupName, itemName }); closePrevConns(apiConfig, proxies, { groupName, itemName });
} }
/* dispatch('showModalClosePrevConns', (s: GlobalState) => { */
/* s.proxies.showModalClosePrevConns = true; */
/* s.proxies.switchProxyCtx = { to: { groupName, itemName } }; */
/* }); */
} }
function closeModalClosePrevConns() { function closeModalClosePrevConns() {
@ -273,15 +275,7 @@ function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) {
error = res.statusText; error = res.statusText;
} }
const { delay } = await res.json(); const { delay } = await res.json();
const delayNext = { ...getDelay(getState()), [name]: { error, number: delay } };
const delayPrev = getDelay(getState());
const delayNext = {
...delayPrev,
[name]: {
error,
number: delay,
},
};
dispatch('requestDelayForProxyOnce', (s) => { dispatch('requestDelayForProxyOnce', (s) => {
s.proxies.delay = delayNext; s.proxies.delay = delayNext;
@ -297,12 +291,33 @@ export function requestDelayForProxy(apiConfig: ClashAPIConfig, name: string) {
export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) { export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) {
return async (dispatch: DispatchFn, getState: GetStateFn) => { return async (dispatch: DispatchFn, getState: GetStateFn) => {
const proxyNames = getDangleProxyNames(getState()); const proxies = getProxies(getState());
const latencyTestUrl = getLatencyTestUrl(getState());
const works = names const proxyDedupMap = new Map<string, boolean>();
// remove names that are provided by proxy providers const providerDedupMap = new Map<string, boolean>();
.filter((p) => proxyNames.indexOf(p) > -1)
.map((p) => dispatch(requestDelayForProxy(apiConfig, p))); 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 Promise.all(works);
await dispatch(fetchProxies(apiConfig)); await dispatch(fetchProxies(apiConfig));
}; };
@ -311,7 +326,10 @@ export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[
export function requestDelayAll(apiConfig: ClashAPIConfig) { export function requestDelayAll(apiConfig: ClashAPIConfig) {
return async (dispatch: DispatchFn, getState: GetStateFn) => { return async (dispatch: DispatchFn, getState: GetStateFn) => {
const proxyNames = getDangleProxyNames(getState()); 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()); const proxyProviders = getProxyProviders(getState());
// one by one // one by one
for (const p of proxyProviders) { for (const p of proxyProviders) {
@ -353,12 +371,13 @@ type ProvidersRaw = {
}; };
function formatProxyProviders(providersInput: ProvidersRaw): { function formatProxyProviders(providersInput: ProvidersRaw): {
providers: Array<FormattedProxyProvider>; proxyProviders: Array<FormattedProxyProvider>;
proxies: { [key: string]: ProxyItem }; providerProxyRecord: ProxiesMapping;
} { } {
const keys = Object.keys(providersInput); const keys = Object.keys(providersInput);
const providers = []; const proxyProviders = [];
const proxies = {}; const providerProxyRecord: ProxiesMapping = {};
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
const provider: ProxyProvider = providersInput[keys[i]]; const provider: ProxyProvider = providersInput[keys[i]];
if (provider.name === 'default' || provider.vehicleType === 'Compatible') { if (provider.name === 'default' || provider.vehicleType === 'Compatible') {
@ -368,19 +387,16 @@ function formatProxyProviders(providersInput: ProvidersRaw): {
const names = []; const names = [];
for (let j = 0; j < proxiesArr.length; j++) { for (let j = 0; j < proxiesArr.length; j++) {
const proxy = proxiesArr[j]; const proxy = proxiesArr[j];
proxies[proxy.name] = proxy; providerProxyRecord[proxy.name] = { ...proxy, __provider: provider.name };
names.push(proxy.name); names.push(proxy.name);
} }
// mutate directly // mutate directly
provider.proxies = names; provider.proxies = names;
providers.push(provider); proxyProviders.push(provider);
} }
return { return { proxyProviders, providerProxyRecord };
providers,
proxies,
};
} }
export const actions = { export const actions = {

View file

@ -27,39 +27,45 @@ export type ClashGeneralConfig = {
///// store.proxies ///// store.proxies
type LatencyHistory = Array<{ time: string; delay: number }>; type LatencyHistoryItem = { time: string; delay: number };
type PrimitiveProxyType = 'Shadowsocks' | 'Snell' | 'Socks5' | 'Http' | 'Vmess'; export type LatencyHistory = LatencyHistoryItem[];
export type ProxyItem = { export type ProxyItem = {
name: string; name: string;
type: PrimitiveProxyType; type: string;
history: LatencyHistory; history: LatencyHistory;
all?: string[]; all?: string[];
now?: string; now?: string;
__provider?: string;
}; };
export type ProxyDelayItem = {
number?: number;
};
export type ProxiesMapping = Record<string, ProxyItem>; export type ProxiesMapping = Record<string, ProxyItem>;
export type DelayMapping = Record<string, { number?: number }>; export type DelayMapping = Record<string, ProxyDelayItem>;
export type ProxyProvider = { export type ProxyProvider = {
name: string; name: string;
type: 'Proxy'; type: 'Proxy';
updatedAt: string; updatedAt: string;
vehicleType: 'HTTP' | 'File' | 'Compatible'; vehicleType: 'HTTP' | 'File' | 'Compatible';
proxies: Array<ProxyItem>; proxies: ProxyItem[];
}; };
export type FormattedProxyProvider = Omit<ProxyProvider, 'proxies'> & { export type FormattedProxyProvider = Omit<ProxyProvider, 'proxies'> & { proxies: string[] };
proxies: string[];
};
export type SwitchProxyCtxItem = { groupName: string; itemName: string }; export type SwitchProxyCtxItem = { groupName: string; itemName: string };
type SwitchProxyCtx = { type SwitchProxyCtx = { to: SwitchProxyCtxItem };
to: SwitchProxyCtxItem;
};
export type StateProxies = { export type StateProxies = {
proxies: ProxiesMapping;
delay: DelayMapping;
groupNames: string[]; groupNames: string[];
proxyProviders?: FormattedProxyProvider[]; proxyProviders?: FormattedProxyProvider[];
proxies: ProxiesMapping;
delay: DelayMapping;
dangleProxyNames?: string[]; dangleProxyNames?: string[];
showModalClosePrevConns: boolean; showModalClosePrevConns: boolean;