veza/apps/web/src/hooks/useLongRunningOperation.ts

117 lines
2.8 KiB
TypeScript
Raw Normal View History

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<T = void>(
operation: () => Promise<T>,
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<AbortController | null>(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,
};
}