fix: latency not correct when set custom url in config (#1189)

To thoroughly fix this issue, all latency-related logic needs to be strongly bound to testurl
This commit is contained in:
njzy 2024-11-27 10:53:34 +08:00 committed by GitHub
parent f610333717
commit af3081371d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 190 additions and 78 deletions

View File

@ -1,17 +1,23 @@
import { JSX, ParentComponent } from 'solid-js' import { JSX, ParentComponent } from 'solid-js'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { getLatencyClassName } from '~/helpers' import { getLatencyClassName } from '~/helpers'
import { useProxies } from '~/signals' import { urlForLatencyTest, useProxies } from '~/signals'
interface Props extends JSX.HTMLAttributes<HTMLSpanElement> { interface Props extends JSX.HTMLAttributes<HTMLSpanElement> {
proxyName: string proxyName: string
testUrl: string | null
} }
export const Latency: ParentComponent<Props> = (props) => { export const Latency: ParentComponent<Props> = (props) => {
const [local, others] = splitProps(props, ['class']) const [local, others] = splitProps(props, ['class'])
const { getLatencyByName } = useProxies() const { getLatencyByName } = useProxies()
const [textClassName, setTextClassName] = createSignal('') const [textClassName, setTextClassName] = createSignal('')
const latency = createMemo(() => getLatencyByName(others.proxyName || '')) const latency = createMemo(() =>
getLatencyByName(
others.proxyName || '',
others.testUrl || urlForLatencyTest(),
),
)
createEffect(() => { createEffect(() => {
setTextClassName(getLatencyClassName(latency())) setTextClassName(getLatencyClassName(latency()))
@ -19,7 +25,11 @@ export const Latency: ParentComponent<Props> = (props) => {
return ( return (
<span <span
class={twMerge('badge w-11 whitespace-nowrap', textClassName(), local.class)} class={twMerge(
'badge w-11 whitespace-nowrap',
textClassName(),
local.class,
)}
{...others} {...others}
> >
{latency() || '---'} {latency() || '---'}

View File

@ -8,10 +8,12 @@ import {
formatProxyType, formatProxyType,
getLatencyClassName, getLatencyClassName,
} from '~/helpers' } from '~/helpers'
import { rootElement, useProxies } from '~/signals' import { rootElement, urlForLatencyTest, useProxies } from '~/signals'
export const ProxyNodeCard = (props: { export const ProxyNodeCard = (props: {
proxyName: string proxyName: string
testUrl: string | null
timeout: number | null
isSelected?: boolean isSelected?: boolean
onClick?: () => void onClick?: () => void
}) => { }) => {
@ -36,6 +38,9 @@ export const ProxyNodeCard = (props: {
[proxyName, specialTypes()].filter(Boolean).join(' - '), [proxyName, specialTypes()].filter(Boolean).join(' - '),
) )
const latencyTestHistory =
proxyNode().latencyTestHistory[props.testUrl || urlForLatencyTest()] || []
return ( return (
<Tooltip <Tooltip
placement="top" placement="top"
@ -67,13 +72,19 @@ export const ProxyNodeCard = (props: {
<Latency <Latency
proxyName={props.proxyName} proxyName={props.proxyName}
testUrl={props.testUrl || null}
class={twMerge( class={twMerge(
proxyLatencyTestingMap()[proxyName] && 'animate-pulse', proxyLatencyTestingMap()[proxyName] && 'animate-pulse',
)} )}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
void proxyLatencyTest(proxyName, proxyNode().provider) void proxyLatencyTest(
proxyName,
proxyNode().provider,
props.testUrl,
props.timeout,
)
}} }}
/> />
</div> </div>
@ -87,12 +98,10 @@ export const ProxyNodeCard = (props: {
<div class="flex flex-col items-center gap-2 rounded-box bg-neutral bg-gradient-to-br from-primary to-secondary p-2.5 text-primary-content shadow-lg"> <div class="flex flex-col items-center gap-2 rounded-box bg-neutral bg-gradient-to-br from-primary to-secondary p-2.5 text-primary-content shadow-lg">
<h2 class="text-lg font-bold">{proxyName}</h2> <h2 class="text-lg font-bold">{proxyName}</h2>
<div class="w-full text-xs uppercase"> <div class="w-full text-xs uppercase">{specialTypes()}</div>
{specialTypes()}
</div>
<ul class="timeline timeline-vertical timeline-compact timeline-snap-icon"> <ul class="timeline timeline-vertical timeline-compact timeline-snap-icon">
<For each={proxyNode().latencyTestHistory}> <For each={latencyTestHistory}>
{(latencyTestResult, index) => ( {(latencyTestResult, index) => (
<li> <li>
<Show when={index() > 0}> <Show when={index() > 0}>
@ -120,11 +129,7 @@ export const ProxyNodeCard = (props: {
<IconCircleCheckFilled class="size-4" /> <IconCircleCheckFilled class="size-4" />
</div> </div>
<Show <Show when={index() !== latencyTestHistory.length - 1}>
when={
index() !== proxyNode().latencyTestHistory.length - 1
}
>
<hr /> <hr />
</Show> </Show>
</li> </li>

View File

@ -4,6 +4,7 @@ import { proxiesPreviewType } from '~/signals'
export const ProxyNodePreview = (props: { export const ProxyNodePreview = (props: {
proxyNameList: string[] proxyNameList: string[]
testUrl: string | null
now?: string now?: string
}) => { }) => {
const off = () => proxiesPreviewType() === PROXIES_PREVIEW_TYPE.OFF const off = () => proxiesPreviewType() === PROXIES_PREVIEW_TYPE.OFF
@ -34,6 +35,7 @@ export const ProxyNodePreview = (props: {
<Match when={isShowBar()}> <Match when={isShowBar()}>
<ProxyPreviewBar <ProxyPreviewBar
proxyNameList={props.proxyNameList} proxyNameList={props.proxyNameList}
testUrl={props.testUrl}
now={props.now} now={props.now}
/> />
</Match> </Match>
@ -41,6 +43,7 @@ export const ProxyNodePreview = (props: {
<Match when={isShowDots()}> <Match when={isShowDots()}>
<ProxyPreviewDots <ProxyPreviewDots
proxyNameList={props.proxyNameList} proxyNameList={props.proxyNameList}
testUrl={props.testUrl}
now={props.now} now={props.now}
/> />
</Match> </Match>

View File

@ -3,11 +3,12 @@ import { latencyQualityMap, useProxies } from '~/signals'
export const ProxyPreviewBar = (props: { export const ProxyPreviewBar = (props: {
proxyNameList: string[] proxyNameList: string[]
testUrl: string | null
now?: string now?: string
}) => { }) => {
const { getLatencyByName } = useProxies() const { getLatencyByName } = useProxies()
const latencyList = createMemo(() => const latencyList = createMemo(() =>
props.proxyNameList.map((name) => getLatencyByName(name)), props.proxyNameList.map((name) => getLatencyByName(name, props.testUrl)),
) )
const all = createMemo(() => latencyList().length) const all = createMemo(() => latencyList().length)
@ -69,7 +70,7 @@ export const ProxyPreviewBar = (props: {
</div> </div>
<Show when={props.now}> <Show when={props.now}>
<Latency proxyName={props.now!} /> <Latency proxyName={props.now!} testUrl={props.testUrl} />
</Show> </Show>
</div> </div>
) )

View File

@ -38,6 +38,7 @@ const LatencyDot = (props: {
export const ProxyPreviewDots = (props: { export const ProxyPreviewDots = (props: {
proxyNameList: string[] proxyNameList: string[]
testUrl: string | null
now?: string now?: string
}) => { }) => {
const { getLatencyByName } = useProxies() const { getLatencyByName } = useProxies()
@ -48,7 +49,7 @@ export const ProxyPreviewDots = (props: {
<For <For
each={props.proxyNameList.map((name): [string, number] => [ each={props.proxyNameList.map((name): [string, number] => [
name, name,
getLatencyByName(name), getLatencyByName(name, props.testUrl),
])} ])}
> >
{([name, latency]) => ( {([name, latency]) => (
@ -62,7 +63,7 @@ export const ProxyPreviewDots = (props: {
</div> </div>
<Show when={props.now}> <Show when={props.now}>
<Latency proxyName={props.now!} /> <Latency proxyName={props.now!} testUrl={props.testUrl} />
</Show> </Show>
</div> </div>
) )

View File

@ -1,6 +1,6 @@
import { LATENCY_QUALITY_MAP_HTTP, PROXIES_ORDERING_TYPE } from '~/constants' import { LATENCY_QUALITY_MAP_HTTP, PROXIES_ORDERING_TYPE } from '~/constants'
import { useI18n } from '~/i18n' import { useI18n } from '~/i18n'
import { latencyQualityMap, useProxies } from '~/signals' import { latencyQualityMap, urlForLatencyTest, useProxies } from '~/signals'
export const formatProxyType = (type = '') => { export const formatProxyType = (type = '') => {
const [t] = useI18n() const [t] = useI18n()
@ -55,19 +55,26 @@ export const filterSpecialProxyType = (type = '') => {
return !conditions.includes(type.toLowerCase()) return !conditions.includes(type.toLowerCase())
} }
export const sortProxiesByOrderingType = ( export const sortProxiesByOrderingType = ({
proxyNames: string[], proxyNames,
orderingType: PROXIES_ORDERING_TYPE, orderingType,
) => { testUrl,
}: {
proxyNames: string[]
orderingType: PROXIES_ORDERING_TYPE
testUrl: string | null
}) => {
const { getLatencyByName } = useProxies() const { getLatencyByName } = useProxies()
if (orderingType === PROXIES_ORDERING_TYPE.NATURAL) { if (orderingType === PROXIES_ORDERING_TYPE.NATURAL) {
return proxyNames return proxyNames
} }
const finalTestUrl = testUrl || urlForLatencyTest()
return proxyNames.sort((a, b) => { return proxyNames.sort((a, b) => {
const prevLatency = getLatencyByName(a) const prevLatency = getLatencyByName(a, finalTestUrl)
const nextLatency = getLatencyByName(b) const nextLatency = getLatencyByName(b, finalTestUrl)
switch (orderingType) { switch (orderingType) {
case PROXIES_ORDERING_TYPE.LATENCY_ASC: case PROXIES_ORDERING_TYPE.LATENCY_ASC:
@ -96,19 +103,27 @@ export const sortProxiesByOrderingType = (
}) })
} }
export const filterProxiesByAvailability = ( export const filterProxiesByAvailability = ({
proxyNames: string[], proxyNames,
enabled?: boolean, enabled,
) => { testUrl,
}: {
proxyNames: string[]
enabled?: boolean
testUrl: string | null
}) => {
const { getLatencyByName, isProxyGroup } = useProxies() const { getLatencyByName, isProxyGroup } = useProxies()
const finalTestUrl = testUrl || urlForLatencyTest()
return enabled return enabled
? proxyNames.filter( ? proxyNames.filter(
// we need proxy node with connected or the node is a group it self // we need proxy node with connected or the node is a group it self
(name) => { (name) => {
return ( return (
isProxyGroup(name) || isProxyGroup(name) ||
getLatencyByName(name) !== latencyQualityMap().NOT_CONNECTED getLatencyByName(name, finalTestUrl) !==
latencyQualityMap().NOT_CONNECTED
) )
}, },
) )

View File

@ -198,13 +198,15 @@ export default () => {
<For each={renderProxies()}> <For each={renderProxies()}>
{(proxyGroup) => { {(proxyGroup) => {
const sortedProxyNames = createMemo(() => const sortedProxyNames = createMemo(() =>
filterProxiesByAvailability( filterProxiesByAvailability({
sortProxiesByOrderingType( proxyNames: sortProxiesByOrderingType({
proxyGroup.all ?? [], proxyNames: proxyGroup.all ?? [],
proxiesOrderingType(), orderingType: proxiesOrderingType(),
), testUrl: proxyGroup.testUrl || null,
hideUnAvailableProxies(), }),
), enabled: hideUnAvailableProxies(),
testUrl: proxyGroup.testUrl || null,
}),
) )
const title = ( const title = (
@ -291,6 +293,7 @@ export default () => {
<ProxyNodePreview <ProxyNodePreview
proxyNameList={sortedProxyNames()} proxyNameList={sortedProxyNames()}
now={proxyGroup.now} now={proxyGroup.now}
testUrl={proxyGroup.testUrl || null}
/> />
</Show> </Show>
</div> </div>
@ -308,6 +311,8 @@ export default () => {
{(proxyName) => ( {(proxyName) => (
<ProxyNodeCard <ProxyNodeCard
proxyName={proxyName} proxyName={proxyName}
testUrl={proxyGroup.testUrl || null}
timeout={proxyGroup.timeout ?? null}
isSelected={proxyGroup.now === proxyName} isSelected={proxyGroup.now === proxyName}
onClick={() => onClick={() =>
void selectProxyInGroup(proxyGroup, proxyName) void selectProxyInGroup(proxyGroup, proxyName)
@ -327,10 +332,12 @@ export default () => {
<For each={proxyProviders()}> <For each={proxyProviders()}>
{(proxyProvider) => { {(proxyProvider) => {
const sortedProxyNames = createMemo(() => const sortedProxyNames = createMemo(() =>
sortProxiesByOrderingType( sortProxiesByOrderingType({
proxyProvider.proxies.map((i) => i.name) ?? [], orderingType: proxiesOrderingType(),
proxiesOrderingType(), proxyNames:
), proxyProvider.proxies.map((i) => i.name) ?? [],
testUrl: proxyProvider.testUrl,
}),
) )
const title = ( const title = (
@ -397,7 +404,10 @@ export default () => {
</div> </div>
<Show when={!collapsedMap()[proxyProvider.name]}> <Show when={!collapsedMap()[proxyProvider.name]}>
<ProxyNodePreview proxyNameList={sortedProxyNames()} /> <ProxyNodePreview
proxyNameList={sortedProxyNames()}
testUrl={proxyProvider.testUrl}
/>
</Show> </Show>
</> </>
) )
@ -411,7 +421,13 @@ export default () => {
} }
> >
<For each={sortedProxyNames()}> <For each={sortedProxyNames()}>
{(proxyName) => <ProxyNodeCard proxyName={proxyName} />} {(proxyName) => (
<ProxyNodeCard
proxyName={proxyName}
testUrl={proxyProvider.testUrl}
timeout={proxyProvider.timeout ?? null}
/>
)}
</For> </For>
</Collapse> </Collapse>
) )

View File

@ -23,10 +23,7 @@ type ProxyInfo = {
name: string name: string
udp: boolean udp: boolean
tfo: boolean tfo: boolean
latencyTestHistory: { latencyTestHistory: Record<string, Proxy['history'] | undefined>
time: string
delay: number
}[]
latency: string latency: string
xudp: boolean xudp: boolean
type: string type: string
@ -59,31 +56,61 @@ const [proxyProviders, setProxyProviders] = createSignal<
>([]) >([])
// DO NOT use latency from latency map directly use getLatencyByName instead // DO NOT use latency from latency map directly use getLatencyByName instead
const [latencyMap, setLatencyMap] = createSignal<Record<string, number>>({}) const [latencyMap, setLatencyMap] = createSignal<
Record<string, Record<string, number> | undefined>
>({})
const [proxyNodeMap, setProxyNodeMap] = createSignal<Record<string, ProxyInfo>>( const [proxyNodeMap, setProxyNodeMap] = createSignal<Record<string, ProxyInfo>>(
{}, {},
) )
type AllTestUrlLatencyInfo = {
allTestUrlLatency: Record<string, number>
allTestUrlLatencyHistory: Record<string, Proxy['history'] | undefined>
}
const getLatencyFromProxy = ( const getLatencyFromProxy = (
proxy: Pick<Proxy, 'extra' | 'history'>, proxy: Pick<Proxy, 'extra' | 'history'>,
url: string,
fallbackDefault = true, fallbackDefault = true,
) => { ): AllTestUrlLatencyInfo => {
const extra = proxy.extra?.[url] as Proxy['history'] | undefined const extra = (proxy.extra || {}) as Record<
string,
if (Array.isArray(extra)) { {
const delay = extra.at(-1)?.delay history?: Proxy['history']
if (delay) {
return delay
} }
>
if (!Object.keys(extra).length && fallbackDefault) {
const testUrl = urlForLatencyTest()
const delay =
proxy.history?.at(-1)?.delay ?? latencyQualityMap().NOT_CONNECTED
const allTestUrlLatency = { [testUrl]: delay }
const allTestUrlLatencyHistory = { [testUrl]: proxy.history }
return {
allTestUrlLatency,
allTestUrlLatencyHistory,
} as AllTestUrlLatencyInfo
} }
if (!fallbackDefault) { return Object.keys(extra).reduce(
return latencyQualityMap().NOT_CONNECTED (acc, testUrl) => {
} const data = extra[testUrl]
const delay =
data?.history?.at(-1)?.delay ?? latencyQualityMap().NOT_CONNECTED
return proxy.history?.at(-1)?.delay ?? latencyQualityMap().NOT_CONNECTED acc.allTestUrlLatency[testUrl] = delay
acc.allTestUrlLatencyHistory[testUrl] = data.history
return acc
},
{
allTestUrlLatency: {},
allTestUrlLatencyHistory: {},
} as AllTestUrlLatencyInfo,
)
} }
const setProxiesInfo = ( const setProxiesInfo = (
@ -93,20 +120,23 @@ const setProxiesInfo = (
const newLatencyMap = { ...latencyMap() } const newLatencyMap = { ...latencyMap() }
proxies.forEach((proxy) => { proxies.forEach((proxy) => {
const { udp, xudp, type, now, history, name, tfo, provider = '' } = proxy const { allTestUrlLatency, allTestUrlLatencyHistory } =
getLatencyFromProxy(proxy)
const { udp, xudp, type, now, name, tfo, provider = '' } = proxy
newProxyNodeMap[proxy.name] = { newProxyNodeMap[proxy.name] = {
udp, udp,
xudp, xudp,
type, type,
latency: now, latency: now,
latencyTestHistory: history, latencyTestHistory: allTestUrlLatencyHistory,
name, name,
tfo, tfo,
provider, provider,
} }
newLatencyMap[proxy.name] = getLatencyFromProxy(proxy, urlForLatencyTest()) newLatencyMap[proxy.name] = allTestUrlLatency
}) })
batch(() => { batch(() => {
@ -122,8 +152,18 @@ export const useProxies = () => {
fetchProxiesAPI(), fetchProxiesAPI(),
]) ])
const proxiesWithTestUrl = Object.values(proxies).map((proxy) => {
if (proxy.all?.length) {
const { testUrl, timeout } = providers?.[proxy.name] || {}
return { ...proxy, testUrl, timeout }
} else {
return proxy
}
})
const sortIndex = [...(proxies['GLOBAL'].all ?? []), 'GLOBAL'] const sortIndex = [...(proxies['GLOBAL'].all ?? []), 'GLOBAL']
const sortedProxies = Object.values(proxies) const sortedProxies = Object.values(proxiesWithTestUrl)
.filter((proxy) => proxy.all?.length) .filter((proxy) => proxy.all?.length)
.sort( .sort(
(prev, next) => (prev, next) =>
@ -135,7 +175,7 @@ export const useProxies = () => {
) )
const allProxies = [ const allProxies = [
...Object.values(proxies), ...proxiesWithTestUrl,
...sortedProviders.flatMap((provider) => ...sortedProviders.flatMap((provider) =>
provider.proxies provider.proxies
.filter((proxy) => !(proxy.name in proxies)) .filter((proxy) => !(proxy.name in proxies))
@ -177,37 +217,53 @@ export const useProxies = () => {
} }
} }
const proxyLatencyTest = async (proxyName: string, provider: string) => { const proxyLatencyTest = async (
proxyName: string,
provider: string,
testUrl: string | null,
timmeout: number | null,
) => {
const nodeName = getNowProxyNodeName(proxyName) const nodeName = getNowProxyNodeName(proxyName)
setProxyLatencyTestingMap(nodeName, async () => { setProxyLatencyTestingMap(nodeName, async () => {
const finalTestUrl = testUrl || urlForLatencyTest()
const currentNodeLatency = latencyMap()?.[nodeName] || {}
try { try {
const { delay } = await proxyLatencyTestAPI( const { delay } = await proxyLatencyTestAPI(
nodeName, nodeName,
provider, provider,
urlForLatencyTest(), finalTestUrl,
latencyTestTimeoutDuration(), timmeout ?? latencyTestTimeoutDuration(),
) )
setLatencyMap((latencyMap) => ({ currentNodeLatency[finalTestUrl] = delay
...latencyMap,
[nodeName]: delay, setLatencyMap((latencyMap) => {
})) return {
...latencyMap,
[nodeName]: currentNodeLatency,
}
})
} catch { } catch {
currentNodeLatency[finalTestUrl] = latencyQualityMap().NOT_CONNECTED
setLatencyMap((latencyMap) => ({ setLatencyMap((latencyMap) => ({
...latencyMap, ...latencyMap,
[nodeName]: latencyQualityMap().NOT_CONNECTED, [nodeName]: currentNodeLatency,
})) }))
} }
}) })
} }
const proxyGroupLatencyTest = async (proxyGroupName: string) => { const proxyGroupLatencyTest = async (proxyGroupName: string) => {
const currentProxyGroups = proxies()
setProxyGroupLatencyTestingMap(proxyGroupName, async () => { setProxyGroupLatencyTestingMap(proxyGroupName, async () => {
const currentProxyGroup = currentProxyGroups.find(
(item) => item.name === proxyGroupName,
)
await proxyGroupLatencyTestAPI( await proxyGroupLatencyTestAPI(
proxyGroupName, proxyGroupName,
urlForLatencyTest(), currentProxyGroup?.testUrl || urlForLatencyTest(),
latencyTestTimeoutDuration(), currentProxyGroup?.timeout ?? latencyTestTimeoutDuration(),
) )
await fetchProxies() await fetchProxies()
}) })
@ -263,8 +319,10 @@ export const useProxies = () => {
return node.name return node.name
} }
const getLatencyByName = (name: string) => { const getLatencyByName = (name: string, testUrl: string | null) => {
return latencyMap()[getNowProxyNodeName(name)] const finalTestUrl = testUrl || urlForLatencyTest()
return latencyMap()[getNowProxyNodeName(name)]?.[finalTestUrl] || 0
} }
const isProxyGroup = (name: string) => { const isProxyGroup = (name: string) => {

View File

@ -23,6 +23,8 @@ export type Proxy = {
xudp: boolean xudp: boolean
tfo: boolean tfo: boolean
now: string now: string
testUrl?: string
timeout?: number
} }
export type ProxyNode = { export type ProxyNode = {
@ -53,6 +55,7 @@ export type ProxyProvider = {
name: string name: string
proxies: ProxyNode[] proxies: ProxyNode[]
testUrl: string testUrl: string
timeout?: number
updatedAt: string updatedAt: string
vehicleType: string vehicleType: string
} }