mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-11 13:02:22 +08:00
fix(proxies): derive mounted delays from shared snapshots
Remove mirrored item delay state so profile cache invalidation cannot leave reused rows blank until remount. Read current node history when the shared cache is empty and preserve snapshot identity for React subscriptions.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useLockFn } from 'ahooks'
|
||||
import { useCallback, useEffect, useReducer } from 'react'
|
||||
import { useCallback, useSyncExternalStore } from 'react'
|
||||
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import delayManager, { type DelayUpdate } from '@/services/delay'
|
||||
@@ -17,13 +17,6 @@ const PRESET_PROXY_NAMES = [
|
||||
'COMPATIBLE',
|
||||
]
|
||||
|
||||
const identity = (previous: DelayUpdate, next: DelayUpdate): DelayUpdate =>
|
||||
previous.delay === next.delay &&
|
||||
previous.elapsed === next.elapsed &&
|
||||
previous.updatedAt === next.updatedAt
|
||||
? previous
|
||||
: next
|
||||
|
||||
const INITIAL_DELAY: DelayUpdate = { delay: -1, updatedAt: 0 }
|
||||
|
||||
export interface UseProxyDelayState {
|
||||
@@ -42,48 +35,33 @@ export function useProxyDelayState(
|
||||
const details = memberDetails(member)
|
||||
const unresolved = member.kind === 'unresolved'
|
||||
const isPreset = unresolved || PRESET_PROXY_NAMES.includes(name)
|
||||
const [delayState, setDelayState] = useReducer(identity, INITIAL_DELAY)
|
||||
const { verge } = useVerge()
|
||||
const timeout = verge?.default_latency_timeout || 10000
|
||||
|
||||
useEffect(() => {
|
||||
if (isPreset) return
|
||||
return delayManager.addListener(name, groupName, setDelayState)
|
||||
}, [name, groupName, isPreset])
|
||||
const subscribe = useCallback(
|
||||
(onChange: () => void) =>
|
||||
isPreset ? () => {} : delayManager.addListener(name, groupName, onChange),
|
||||
[name, groupName, isPreset],
|
||||
)
|
||||
const read = useCallback(
|
||||
() =>
|
||||
unresolved ? undefined : delayManager.getDelayUpdate(name, groupName),
|
||||
[name, groupName, unresolved],
|
||||
)
|
||||
const cachedUpdate = useSyncExternalStore(subscribe, read, read)
|
||||
|
||||
const updateDelay = useCallback(() => {
|
||||
if (unresolved) {
|
||||
setDelayState(INITIAL_DELAY)
|
||||
return
|
||||
}
|
||||
const cachedUpdate = delayManager.getDelayUpdate(name, groupName)
|
||||
if (cachedUpdate) {
|
||||
setDelayState({ ...cachedUpdate })
|
||||
return
|
||||
}
|
||||
|
||||
const fallbackDelay = delayManager.getDelayFix(member, groupName)
|
||||
if (fallbackDelay === -1) {
|
||||
setDelayState({ delay: -1, updatedAt: 0 })
|
||||
return
|
||||
}
|
||||
|
||||
let updatedAt = 0
|
||||
const history = details?.history
|
||||
if (history && history.length > 0) {
|
||||
const lastRecord = history[history.length - 1]
|
||||
const parsed = Date.parse(lastRecord.time)
|
||||
if (!Number.isNaN(parsed)) {
|
||||
updatedAt = parsed
|
||||
}
|
||||
}
|
||||
|
||||
setDelayState({ delay: fallbackDelay, updatedAt })
|
||||
}, [details?.history, groupName, member, name, unresolved])
|
||||
|
||||
useEffect(() => {
|
||||
updateDelay()
|
||||
}, [updateDelay])
|
||||
const fallbackDelay = delayManager.getDelayFix(member, groupName)
|
||||
const history = details?.history
|
||||
const lastRecord = history?.[history.length - 1]
|
||||
const parsed = lastRecord ? Date.parse(lastRecord.time) : NaN
|
||||
const delayState =
|
||||
cachedUpdate ??
|
||||
(fallbackDelay === -1
|
||||
? INITIAL_DELAY
|
||||
: {
|
||||
delay: fallbackDelay,
|
||||
updatedAt: Number.isNaN(parsed) ? 0 : parsed,
|
||||
})
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
if (!isInteractableMember(member)) return
|
||||
|
||||
@@ -74,9 +74,12 @@ describe('profile delay isolation', () => {
|
||||
const stopSecond = delayManager.addListener('a', 'g', second)
|
||||
try {
|
||||
delayManager.setDelay('a', 'g', -2)
|
||||
const pending = delayManager.getDelayUpdate('a', 'g')
|
||||
expect(delayManager.getDelayUpdate('a', 'g')).toBe(pending)
|
||||
await flush()
|
||||
stopFirst()
|
||||
delayManager.setDelay('a', 'g', 120)
|
||||
expect(delayManager.getDelayUpdate('a', 'g')).not.toBe(pending)
|
||||
await flush()
|
||||
expect(first.mock.calls.map(([update]) => update.delay)).toEqual([-2])
|
||||
expect(second.mock.calls.map(([update]) => update.delay)).toEqual([
|
||||
|
||||
@@ -274,7 +274,8 @@ class DelayManager {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return { ...entry }
|
||||
// React's external-store reads must retain identity until the value changes.
|
||||
return entry
|
||||
}
|
||||
|
||||
getDelay(name: string, group: string) {
|
||||
|
||||
Reference in New Issue
Block a user