fix: admin/logs: risk of duplicated lines

Massimo Melina committed Feb 7, 2025 at 14:19 UTC 1b40122e1ce750cab8b7cc0ce9f299552877c63a
1 file changed +16 -18
shared/api.ts
+16 -18
@@ -66,7 +66,8 @@ export function apiCall<T=any>(cmd: string, params?: Dict, options: ApiCallOptio
66 }).finally(() => clearTimeout(timeout)), {
67 abort() {
68 controller.abort(aborted='cancel')
69 - }
69 + },
70 + aborted: () => controller.signal.aborted
71 })
72 }
73
@@ -81,32 +82,29 @@ export class ApiError extends Error {
82
83 export type UseApi<T=unknown> = ReturnType<typeof useApi<T>>
84 export function useApi<T=any>(cmd: string | Falsy, params?: object, options: ApiCallOptions={}) {
84 - const [data, setData] = useStateMounted<Awaited<ReturnType<typeof apiCall<T>>> | undefined>(undefined)
85 + const [data, setData, getData] = useStateMounted<Awaited<ReturnType<typeof apiCall<T>>> | undefined>(undefined)
86 const [error, setError] = useStateMounted<Error | undefined>(undefined)
87 const [forcer, setForcer] = useStateMounted(0)
88 const [loading, setLoading, getLoading] = useStateMounted<undefined | ReturnType<typeof apiCall>>(undefined)
89 const reloadPromise = useRef<any>()
89 - const dataRef = useRef<any>()
90 useEffect(() => {
91 setError(undefined)
92 - let aborted = false
93 - let req: undefined | ReturnType<typeof apiCall>
92 + const aborted = () => getLoading()?.aborted()
93 const wholePromise = wait(0) // postpone a bit, so that if it is aborted immediately, it is never really fired (happens mostly in dev mode)
95 - .then(() => !cmd || aborted ? undefined : req = apiCall<T>(cmd, params, options))
96 - .then(res => aborted || setData(dataRef.current = res as any) || setError(undefined), err => {
97 - if (aborted) return
98 - setError(err)
99 - setData(dataRef.current = undefined)
94 + .then(() => {
95 + const ret = !cmd || aborted() ? undefined : apiCall<T>(cmd, params, options)
96 + setLoading(ret)
97 + return ret
98 })
99 + .then(res => aborted() || setData(res as any) || setError(undefined),
100 + err => {
101 + if (aborted()) return
102 + setError(err)
103 + setData(undefined)
104 + })
105 .finally(() => setLoading(reloadPromise.current = undefined))
102 - if (cmd && !aborted) setLoading(Object.assign(wholePromise, {
103 - abort() {
104 - aborted = true
105 - req?.abort()
106 - }
107 - }))
106 reloadPromise.current?.resolve(wholePromise)
109 - return () => getLoading()?.abort()
107 + return () => { wholePromise.finally(() => getLoading()?.abort()) }
108 }, [cmd, JSON.stringify(params), forcer]) //eslint-disable-line -- json-ize to detect deep changes
109 const reload = useCallback(() => {
110 if (getLoading()) return
@@ -116,7 +114,7 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object, options: Api
114 const ee = useMemo(() => new BetterEventEmitter, [])
115 const sub = useCallback((cb: Callback) => ee.on('data', cb), [ee])
116 useEffect(() => { ee.emit('data') }, [data])
119 - return { data, setData, error, reload, sub, loading, getData: () => dataRef.current }
117 + return { data, setData, getData, error, reload, sub, loading }
118 }
119
120 type EventHandler = (type:string, data?:any) => void