Files
PolyWeather/frontend/components/dashboard/scan-terminal/use-scan-terminal-query.ts
T

134 lines
4.0 KiB
TypeScript
Raw Normal View History

2026-04-28 09:06:55 +08:00
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import {
scanTerminalClient,
toRemoteError,
toRemoteLoading,
toRemoteSuccess,
type RemoteData,
} from "@/components/dashboard/scan-terminal/scan-terminal-client";
2026-04-28 09:06:55 +08:00
import type { ScanTerminalResponse } from "@/lib/dashboard-types";
const SCAN_TERMINAL_AUTO_REFRESH_MS = 10 * 60_000;
const SCAN_TERMINAL_MANUAL_REFRESH_COOLDOWN_MS = 2 * 60_000;
export function useScanTerminalQuery({
isPro,
proAccessLoading,
}: {
isPro: boolean;
proAccessLoading: boolean;
}) {
const [terminalData, setTerminalData] = useState<ScanTerminalResponse | null>(null);
const [scanRemote, setScanRemote] = useState<RemoteData<ScanTerminalResponse>>({
status: "idle",
});
2026-04-28 09:06:55 +08:00
const [scanLoading, setScanLoading] = useState(false);
const [scanError, setScanError] = useState<string | null>(null);
const scanAbortRef = useRef<AbortController | null>(null);
2026-04-28 09:06:55 +08:00
const scanRequestSeqRef = useRef(0);
const scanLoadingRef = useRef(false);
const lastForcedScanRefreshAtRef = useRef(0);
const fetchScanTerminal = useCallback(
async ({
forceRefresh = false,
showLoading = false,
}: {
forceRefresh?: boolean;
showLoading?: boolean;
} = {}) => {
if (proAccessLoading || !isPro) return;
const requestSeq = ++scanRequestSeqRef.current;
scanAbortRef.current?.abort();
2026-04-28 09:06:55 +08:00
const controller = new AbortController();
scanAbortRef.current = controller;
2026-04-28 09:06:55 +08:00
if (forceRefresh) {
lastForcedScanRefreshAtRef.current = Date.now();
}
if (showLoading) {
scanLoadingRef.current = true;
setScanLoading(true);
setScanRemote((current) => toRemoteLoading(current));
2026-04-28 09:06:55 +08:00
}
setScanError(null);
try {
const payload = await scanTerminalClient.getTerminal({
forceRefresh,
2026-04-28 09:06:55 +08:00
signal: controller.signal,
});
if (requestSeq !== scanRequestSeqRef.current) return;
setTerminalData(payload);
setScanRemote(toRemoteSuccess(payload));
2026-04-28 09:06:55 +08:00
setScanError(null);
} catch (error) {
if (controller.signal.aborted || requestSeq !== scanRequestSeqRef.current) return;
const message = error instanceof Error ? error.message : String(error);
setScanError(message);
setScanRemote((current) => toRemoteError(error, current));
2026-04-28 09:06:55 +08:00
} finally {
if (scanAbortRef.current === controller) {
scanAbortRef.current = null;
}
2026-04-28 09:06:55 +08:00
if (showLoading) {
scanLoadingRef.current = false;
setScanLoading(false);
}
}
},
[isPro, proAccessLoading],
);
useEffect(() => {
if (proAccessLoading) return;
if (!isPro) {
scanLoadingRef.current = false;
setScanLoading(false);
setScanError(null);
setTerminalData(null);
setScanRemote({ status: "idle" });
2026-04-28 09:06:55 +08:00
return;
}
void fetchScanTerminal({ forceRefresh: false, showLoading: true });
}, [fetchScanTerminal, isPro, proAccessLoading]);
useEffect(() => {
return () => {
scanAbortRef.current?.abort();
};
}, []);
2026-04-28 09:06:55 +08:00
const refreshScanTerminalManually = useCallback(() => {
const now = Date.now();
const lastForced = lastForcedScanRefreshAtRef.current;
const withinCooldown =
lastForced > 0 &&
now - lastForced < SCAN_TERMINAL_MANUAL_REFRESH_COOLDOWN_MS &&
terminalData;
if (withinCooldown) {
setScanError(null);
return;
}
void fetchScanTerminal({ forceRefresh: true, showLoading: true });
}, [fetchScanTerminal, terminalData]);
useEffect(() => {
if (proAccessLoading || !isPro) return;
const intervalId = window.setInterval(() => {
if (document.hidden) return;
if (scanLoadingRef.current) return;
void fetchScanTerminal({ forceRefresh: true, showLoading: false });
}, SCAN_TERMINAL_AUTO_REFRESH_MS);
return () => window.clearInterval(intervalId);
}, [fetchScanTerminal, isPro, proAccessLoading]);
return {
refreshScanTerminalManually,
scanError,
scanLoading,
scanRemote,
2026-04-28 09:06:55 +08:00
terminalData,
};
}