- Add visibility suspension hook to detect tab-hidden periods during chat loading - Update useChat and useQuickChat to suppress load-failed toasts triggered by tab suspension resumes - Expand hook test coverage for suspension timing and toast suppression behavior - Add plugin authoring documentation updates and include FN-3838 changeset Fusion-Task-Id: FN-3838
69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
|
|
const SUSPENSION_ERROR_PATTERNS = [
|
|
"load failed",
|
|
"failed to fetch",
|
|
"networkerror when attempting to fetch resource.",
|
|
"connection aborted",
|
|
"connection closed unexpectedly",
|
|
"network error",
|
|
];
|
|
|
|
export function isLikelyTabSuspensionError(message: string): boolean {
|
|
const normalized = message.trim().toLowerCase();
|
|
if (!normalized) {
|
|
return false;
|
|
}
|
|
return SUSPENSION_ERROR_PATTERNS.some((pattern) => normalized.includes(pattern));
|
|
}
|
|
|
|
export function useTabVisibilitySuspension() {
|
|
const lastHiddenAtRef = useRef<number | null>(null);
|
|
const lastVisibleAtRef = useRef<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (typeof document === "undefined") {
|
|
return;
|
|
}
|
|
|
|
const handleVisibilityChange = () => {
|
|
const now = Date.now();
|
|
if (document.visibilityState === "hidden") {
|
|
lastHiddenAtRef.current = now;
|
|
return;
|
|
}
|
|
if (document.visibilityState === "visible") {
|
|
lastVisibleAtRef.current = now;
|
|
}
|
|
};
|
|
|
|
handleVisibilityChange();
|
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
}, []);
|
|
|
|
const isHiddenNow = useCallback(() => typeof document !== "undefined" && document.visibilityState === "hidden", []);
|
|
|
|
const wasRecentlyHidden = useCallback((windowMs = 5000): boolean => {
|
|
const hiddenAt = lastHiddenAtRef.current;
|
|
if (hiddenAt === null) {
|
|
return false;
|
|
}
|
|
const now = Date.now();
|
|
if (isHiddenNow()) {
|
|
return now - hiddenAt <= windowMs;
|
|
}
|
|
|
|
const visibleAt = lastVisibleAtRef.current;
|
|
if (visibleAt === null || visibleAt < hiddenAt) {
|
|
return false;
|
|
}
|
|
return now - visibleAt <= windowMs;
|
|
}, [isHiddenNow]);
|
|
|
|
return useMemo(() => ({
|
|
isHiddenNow,
|
|
wasRecentlyHidden,
|
|
}), [isHiddenNow, wasRecentlyHidden]);
|
|
}
|