UNPKG

@supunlakmal/hooks

Version:

A collection of reusable React hooks

156 lines 7.03 kB
import { useState, useEffect, useCallback, useRef } from 'react'; const isBrowser = typeof window !== 'undefined'; const IdleDetector = isBrowser ? window.IdleDetector : undefined; /** * Hook to detect user idle state and screen lock status using the Idle Detection API. * * @param options Configuration options for the idle detector. * @returns State and controls for idle detection. */ export function useIdleDetection(options = {}) { const isSupported = !!IdleDetector; const [userState, setUserState] = useState(null); const [screenState, setScreenState] = useState(null); const [permissionState, setPermissionState] = useState(null); const [isActive, setIsActive] = useState(false); const [error, setError] = useState(null); const detectorRef = useRef(null); const abortControllerRef = useRef(null); const optionsRef = useRef(options); // Keep options ref updated useEffect(() => { optionsRef.current = options; }, [options]); const requestPermission = useCallback(async () => { var _a, _b; if (!isSupported) { setError(new Error('Idle Detection API not supported.')); setPermissionState('denied'); return 'denied'; } try { const status = await IdleDetector.requestPermission(); setPermissionState(status); setError(null); return status; } catch (err) { console.error('Error requesting idle detection permission:', err); const currentError = err instanceof Error ? err : new Error('Failed to request idle permission'); setError(currentError); (_b = (_a = optionsRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, currentError); setPermissionState('denied'); return 'denied'; } }, [isSupported]); const startDetector = useCallback(async () => { var _a, _b, _c, _d, _e, _f, _g; if (!isSupported || isActive) return; if (permissionState !== 'granted') { const status = await requestPermission(); if (status !== 'granted') { setError(new Error('Idle detection permission not granted.')); (_b = (_a = optionsRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, new Error('Idle detection permission not granted.')); return; } } if (detectorRef.current) return; // Already started or failed previously try { abortControllerRef.current = new AbortController(); const detector = new IdleDetector(); detectorRef.current = detector; detector.onchange = () => { var _a, _b; setUserState(detector.userState); setScreenState(detector.screenState); setIsActive(true); // Keep active as long as detector exists (_b = (_a = optionsRef.current).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, { userState: detector.userState, screenState: detector.screenState, }); }; await detector.start({ threshold: optionsRef.current.threshold, signal: abortControllerRef.current.signal, }); // Initial state check after start setUserState(detector.userState); setScreenState(detector.screenState); setIsActive(true); setError(null); (_d = (_c = optionsRef.current).onChange) === null || _d === void 0 ? void 0 : _d.call(_c, { userState: detector.userState, screenState: detector.screenState, }); } catch (err) { console.error('Error starting idle detector:', err); const currentError = err instanceof Error ? err : new Error('Failed to start idle detector'); setError(currentError); (_f = (_e = optionsRef.current).onError) === null || _f === void 0 ? void 0 : _f.call(_e, currentError); setIsActive(false); detectorRef.current = null; (_g = abortControllerRef.current) === null || _g === void 0 ? void 0 : _g.abort(); // Clean up abort controller abortControllerRef.current = null; } }, [isSupported, isActive, permissionState, requestPermission]); // Auto-start logic useEffect(() => { var _a; if (isSupported && optionsRef.current.autoStart) { // Check initial permission state without prompting (_a = navigator.permissions) === null || _a === void 0 ? void 0 : _a.query({ name: 'idle-detection' }).then((status) => { setPermissionState(status.state); if (status.state === 'granted') { startDetector(); } status.onchange = () => { var _a; setPermissionState(status.state); if (status.state !== 'granted') { // Stop detector if permission is revoked (_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort(); detectorRef.current = null; setIsActive(false); } }; }).catch((err) => { console.warn('Could not query idle-detection permission state:', err); // Fallback to trying to start, which will request permission if needed startDetector(); }); } }, [isSupported, startDetector]); // Dependencies: isSupported, startDetector (which depends on permissionState) // Cleanup on unmount or when signal is aborted useEffect(() => { const externalSignal = optionsRef.current.signal; const handleAbort = () => { var _a; (_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort(); detectorRef.current = null; setIsActive(false); }; externalSignal === null || externalSignal === void 0 ? void 0 : externalSignal.addEventListener('abort', handleAbort); return () => { var _a; externalSignal === null || externalSignal === void 0 ? void 0 : externalSignal.removeEventListener('abort', handleAbort); (_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort(); // Abort internal controller on unmount detectorRef.current = null; }; }, []); // Empty array: run only once on mount/unmount return { userState, screenState, permissionState, isActive, error, requestPermission, startDetector, isSupported, }; } //# sourceMappingURL=useIdleDetection.js.map