import { useCallback, useRef, useEffect } from 'react'; import { useTabVisibility } from './useTabVisibility'; /** * Edge 3.3: Hook to manage long-running operations that should continue when tab is hidden * Ensures operations continue correctly when tab is switched or becomes hidden * * @param operation - The long-running operation function * @param options - Configuration options * @returns Object with operation control functions * * @example * ```typescript * const { start, stop, isRunning } = useLongRunningOperation( * async () => { * await processLargeFile(); * }, * { * continueWhenHidden: true, * onComplete: () => console.log('Done'), * onError: (error) => console.error(error), * } * ); * * // Start operation * start(); * ``` */ export function useLongRunningOperation( operation: () => Promise, options: { continueWhenHidden?: boolean; // Continue operation when tab is hidden (default: true) onComplete?: (result: T) => void; onError?: (error: Error) => void; onStart?: () => void; onStop?: () => void; } = {}, ) { const { continueWhenHidden = true, onComplete, onError, onStart, onStop, } = options; const isRunningRef = useRef(false); const abortControllerRef = useRef(null); const { isVisible } = useTabVisibility(); /** * Start the long-running operation */ const start = useCallback(async () => { // Edge 3.3: Don't start if already running if (isRunningRef.current) { return; } // Edge 3.3: Don't start if tab is hidden and continueWhenHidden is false if (!isVisible && !continueWhenHidden) { return; } isRunningRef.current = true; abortControllerRef.current = new AbortController(); onStart?.(); try { const result = await operation(); onComplete?.(result); return result; } catch (error) { // Edge 3.3: Only handle error if not aborted if (error instanceof Error && error.name !== 'AbortError') { onError?.(error); } throw error; } finally { isRunningRef.current = false; abortControllerRef.current = null; } }, [operation, isVisible, continueWhenHidden, onComplete, onError, onStart]); /** * Stop the long-running operation */ const stop = useCallback(() => { if (abortControllerRef.current) { abortControllerRef.current.abort(); abortControllerRef.current = null; } isRunningRef.current = false; onStop?.(); }, [onStop]); /** * Check if operation is currently running */ const isRunning = useCallback(() => { return isRunningRef.current; }, []); // Edge 3.3: Cleanup on unmount useEffect(() => { return () => { stop(); }; }, [stop]); return { start, stop, isRunning, }; }