본문 바로가기

React

React 앱에서 브라우저 Wake Lock API 활용하기

화면 꺼짐을 방지해 지도, 영상, 운동/학습, 키오스크 등 장시간 상호작용이 필요한 React 앱에서 브라우저 Wake Lock API를 활용하는 방법을 안내합니다. 실무에서 바로 적용 가능한 훅과 UI 패턴, 비지원 브라우저 대안, 배포 체크리스트를 제공드립니다.

1. 지원 현황과 주의점

- 지원: Chromium 기반 브라우저(Chrome, Edge, Opera, Samsung Internet)에서 Screen Wake Lock을 지원합니다. 안드로이드와 데스크톱 일부 환경에서 동작하며 iOS Safari는 아직 미지원입니다.
- 조건: HTTPS(secure context)와 사용자 제스처(클릭 등)로 요청해야 실패율이 낮습니다.
- 수명: 탭이 백그라운드로 가거나 시스템 정책(배터리 절약 모드 등)으로 해제될 수 있어 재요청 로직이 필요합니다.
- iframe: 교차 출처 iframe에서는 Permissions-Policy로 허용돼야 합니다.

2. 최소 구현 체크리스트

- 사용자 버튼으로 활성화/해제 제공(토글 UI).
- document.visibilitychange에서 복귀 시 재요청.
- sentinel.release 이벤트 처리로 상태 동기화.
- isSupported 분기와 SSR 환경에서 안전한 navigator 검사.
- 미지원 브라우저(iOS Safari 등)는 대안 로직 적용.

3. React 훅 구현: useWakeLock

실사용 가능한 훅입니다. 사용자 제스처로 request를 호출하도록 UI에서 연결합니다.

import { useEffect, useRef, useState, useCallback } from 'react';

export function useWakeLock() {
  const [isSupported, setIsSupported] = useState(false);
  const [active, setActive] = useState(false);
  const [error, setError] = useState(null);
  const sentinelRef = useRef(null);

  useEffect(() => {
    setIsSupported(typeof navigator !== 'undefined' && 'wakeLock' in navigator);
  }, []);

  const release = useCallback(async () => {
    try {
      if (sentinelRef.current) {
        await sentinelRef.current.release();
        sentinelRef.current = null;
      }
      setActive(false);
    } catch (e) {
      setError(e);
    }
  }, []);

  const request = useCallback(async () => {
    if (!isSupported) return;
    try {
      // 사용자 제스처(클릭)로 호출해야 성공률이 높습니다.
      const sentinel = await navigator.wakeLock.request('screen');
      sentinelRef.current = sentinel;
      setActive(true);
      setError(null);

      const onRelease = () => {
        // 시스템 또는 브라우저가 해제했을 때 동기화
        setActive(false);
      };
      sentinel.addEventListener('release', onRelease, { once: true });
    } catch (e) {
      setError(e);
      setActive(false);
    }
  }, [isSupported]);

  useEffect(() => {
    const onVisibilityChange = () => {
      // 탭 복귀 시 활성 상태면 재요청
      if (document.visibilityState === 'visible' && active) {
        request().catch(() => {});
      }
    };
    document.addEventListener('visibilitychange', onVisibilityChange);
    return () => {
      document.removeEventListener('visibilitychange', onVisibilityChange);
    };
  }, [active, request]);

  useEffect(() => () => { release(); }, [release]);

  return { isSupported, active, error, request, release };
}

4. 예시 컴포넌트: 토글 버튼 UI

사용자 제스처로 request를 호출하고 상태를 안내합니다.

import React from 'react';
import { useWakeLock } from './useWakeLock';

export function KeepScreenAwake() {
  const { isSupported, active, error, request, release } = useWakeLock();

  const handleToggle = async () => {
    if (active) {
      await release();
    } else {
      await request();
    }
  };

  return (
    <div>
      <button onClick={handleToggle} disabled={!isSupported}>
        {active ? '화면 깨우기 해제' : '화면 꺼짐 방지 활성화'}
      </button>
      {!isSupported && <p>브라우저가 Wake Lock API를 지원하지 않습니다.</p>}
      {error && <p>오류: {String(error.message || error)}</p>}
      {active && <p>활성화되었습니다. 장시간 작업을 안전하게 진행하세요.</p>}
    </div>
  );
}

5. 비지원 브라우저(iOS Safari 등) 대안

완벽하진 않지만, 무음/인라인 loop 비디오를 재생해 화면 꺼짐을 늦추는 방법이 있습니다. 사용자 경험에 미치는 영향을 고려해 조건부로만 사용하세요.

import { useEffect, useRef, useState } from 'react';

function isIOS() {
  if (typeof navigator === 'undefined') return false;
  return /iPhone|iPad|iPod/i.test(navigator.userAgent);
}

export function useIOSWakeFallback() {
  const videoRef = useRef(null);
  const [active, setActive] = useState(false);

  const enable = async () => {
    if (!isIOS() || active) return;
    const v = document.createElement('video');
    v.setAttribute('playsinline', '');
    v.muted = true;
    v.loop = true;
    v.src = 'data:video/mp4;base64,AAAA'; // 아주 짧은 무음 비디오 소스 제공 필요
    v.style.position = 'fixed';
    v.style.width = '1px';
    v.style.height = '1px';
    v.style.opacity = '0';
    document.body.appendChild(v);
    videoRef.current = v;
    try {
      await v.play();
      setActive(true);
    } catch (e) {
      console.warn('iOS fallback 실패', e);
    }
  };

  const disable = async () => {
    const v = videoRef.current;
    if (!v) return;
    try { v.pause(); } catch {}
    v.remove();
    videoRef.current = null;
    setActive(false);
  };

  useEffect(() => () => { disable(); }, []);

  return { active, enable, disable };
}

6. 통합: 지원 여부에 따라 Wake Lock 또는 대안

브라우저 지원에 따라 우선적으로 Wake Lock을 사용하고, iOS 등에서는 대안을 활성화합니다.

import React from 'react';
import { useWakeLock } from './useWakeLock';
import { useIOSWakeFallback } from './useIOSWakeFallback';

export function ScreenAwakeToggle() {
  const { isSupported, active, request, release } = useWakeLock();
  const ios = useIOSWakeFallback();

  const enabled = isSupported ? active : ios.active;

  const onClick = async () => {
    if (enabled) {
      isSupported ? await release() : await ios.disable();
    } else {
      isSupported ? await request() : await ios.enable();
    }
  };

  return (
    <button onClick={onClick}>
      {enabled ? '화면 깨우기 해제' : '화면 꺼짐 방지 활성화'}
    </button>
  );
}

7. 서버 설정: Permissions-Policy로 iframe 허용

교차 출처 iframe에서 Wake Lock을 사용하려면 서버에서 권한 정책을 설정합니다.

// Express 예시
app.use((req, res, next) => {
  res.setHeader('Permissions-Policy', 'screen-wake-lock=(self)');
  next();
});

8. PWA 및 모바일 고려사항

- PWA 설치 상태든 브라우저 탭이든 동일하게 사용자 제스처가 중요합니다.
- 배터리 절약 모드, 저전력 상태, 화면 밝기 제한 등 운영체제 정책이 우선할 수 있습니다.
- 앱이 백그라운드로 가면 Wake Lock이 해제될 수 있어 복귀 시 재요청 로직이 필요합니다.
- 긴 세션에서는 배터리 안내 문구와 손쉬운 해제 버튼을 제공하세요.

9. 에러 처리와 로깅 패턴

- NotAllowedError: 사용자 제스처 없이 호출했을 가능성이 높습니다. 버튼 클릭과 연결하세요.
- AbortError: 시스템 정책으로 거부. 사용자에게 배터리 설정을 안내합니다.
- isSupported false: 기능 안내와 대안을 제시하세요.
- 로그: wakeLock_request, wakeLock_release, wakeLock_error 이벤트를 분석에 기록해 UX를 개선합니다.

10. 테스트 체크리스트

- Android Chrome 실제 기기에서 화면 꺼짐 방지 확인.
- 탭 전환 후 복귀 시 재요청 동작 확인.
- 배터리 절약 모드에서 거부/해제 처리 확인.
- iOS Safari에서 대안 적용 및 UX 문제 없는지 확인.
- iframe, HTTPS, 서버 헤더 설정 검증.

11. 결론

Wake Lock API는 React 앱에서 화면 꺼짐을 방지하는 가장 표준화된 방법입니다. 사용자 제스처 기반 토글, 가시성 복귀 재요청, 명확한 상태 안내, 미지원 브라우저 대안을 함께 구성하면 실무 품질을 확보할 수 있습니다. 위 훅과 예시 컴포넌트를 프로젝트에 적용해 장시간 상호작용 UX를 개선하세요.