All files / src/hooks useWakeLock.ts

0% Statements 0/48
0% Branches 0/20
0% Functions 0/10
0% Lines 0/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                                                                                                                                               
import { useEffect, useRef, useCallback } from 'react'
import {
  createSilentVideo,
  requestNativeWakeLock,
  releaseNativeWakeLock,
} from './wakeLockUtils'
 
/**
 * useWakeLock - Prevents the screen from locking while active
 *
 * Uses the Screen Wake Lock API (iOS 16.4+, Chrome 84+) with a silent video
 * fallback for older browsers.
 *
 * @param enabled - Whether wake lock should be active (default: true)
 */
export function useWakeLock(enabled: boolean = true): void {
  const wakeLockRef = useRef<WakeLockSentinel | null>(null)
  const videoRef = useRef<HTMLVideoElement | null>(null)
  const usingFallbackRef = useRef(false)
 
  const startVideoFallback = useCallback(() => {
    if (videoRef.current) return
    const video = createSilentVideo()
    document.body.appendChild(video)
    videoRef.current = video
    video.play().catch(() => {})
    usingFallbackRef.current = true
  }, [])
 
  const stopVideoFallback = useCallback(() => {
    if (videoRef.current) {
      videoRef.current.pause()
      videoRef.current.remove()
      videoRef.current = null
    }
    usingFallbackRef.current = false
  }, [])
 
  useEffect(() => {
    if (!enabled) {
      releaseNativeWakeLock(wakeLockRef.current)
      wakeLockRef.current = null
      stopVideoFallback()
      return
    }
 
    let mounted = true
 
    const enableWakeLock = async () => {
      const sentinel = await requestNativeWakeLock()
      if (sentinel) {
        wakeLockRef.current = sentinel
        sentinel.addEventListener('release', () => {
          wakeLockRef.current = null
        })
      } else if (mounted) {
        startVideoFallback()
      }
    }
 
    enableWakeLock()
 
    const handleVisibilityChange = async () => {
      if (document.visibilityState === 'visible' && mounted && enabled) {
        if (!usingFallbackRef.current) {
          const sentinel = await requestNativeWakeLock()
          if (sentinel) {
            wakeLockRef.current = sentinel
            sentinel.addEventListener('release', () => {
              wakeLockRef.current = null
            })
          }
        }
      }
    }
 
    document.addEventListener('visibilitychange', handleVisibilityChange)
 
    return () => {
      mounted = false
      releaseNativeWakeLock(wakeLockRef.current)
      wakeLockRef.current = null
      stopVideoFallback()
      document.removeEventListener('visibilitychange', handleVisibilityChange)
    }
  }, [enabled, startVideoFallback, stopVideoFallback])
}
 
← Back to Dashboard