Should check provider health if a group contains provider proxies
This commit is contained in:
parent
69423bdfff
commit
38571da24a
4 changed files with 104 additions and 69 deletions
|
@ -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);
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue