import { computed, shallowRef, watch } from 'vue'; import type { ComputedRef, ShallowRef } from 'vue'; import { defaultDocument, defaultNavigator } from '@/types'; import type { ConfigurableDocument, ConfigurableNavigator } from '@/types'; import { useEventListener } from '@/composables/browser/useEventListener'; import { useSupported } from '@/composables/utilities/useSupported'; import { useDocumentVisibility } from '@/composables/elements/useDocumentVisibility'; import { tryOnScopeDispose } from '@/composables/lifecycle/tryOnScopeDispose'; export type WakeLockType = 'screen'; export interface WakeLockSentinel extends EventTarget { type: WakeLockType; released: boolean; release: () => Promise; } type NavigatorWithWakeLock = Navigator & { wakeLock: { request: (type: WakeLockType) => Promise }; }; export type UseWakeLockOptions = ConfigurableNavigator & ConfigurableDocument; export interface UseWakeLockReturn { /** * Whether the Screen Wake Lock API is supported. */ isSupported: ComputedRef; /** * The current `WakeLockSentinel`, or `null` when no lock is held. */ sentinel: ShallowRef; /** * Whether a wake lock is currently held AND the document is visible. */ isActive: ComputedRef; /** * Request a wake lock. If the document is hidden, the request is deferred * and automatically (re)acquired once the document becomes visible again. */ request: (type: WakeLockType) => Promise; /** * Request a wake lock immediately, releasing any existing one first. * Will reject (via `onError`) if the document is hidden. */ forceRequest: (type: WakeLockType) => Promise; /** * Release the current wake lock and cancel any deferred request. */ release: () => Promise; } /** * @name useWakeLock * @category Browser * @description Reactive wrapper over the Screen Wake Lock API to keep the screen awake. * Re-acquires a deferred lock automatically when the document returns to visible. * * @param {UseWakeLockOptions} [options={}] Options (custom `navigator`, `document`) * @returns {UseWakeLockReturn} `{ isSupported, sentinel, isActive, request, forceRequest, release }` * * @example * const { isSupported, isActive, request, release } = useWakeLock(); * await request('screen'); * // ...later * await release(); * * @example * // forceRequest re-acquires immediately, dropping any existing lock * const { forceRequest } = useWakeLock(); * await forceRequest('screen'); * * @since 0.0.15 */ export function useWakeLock(options: UseWakeLockOptions = {}): UseWakeLockReturn { const { navigator = defaultNavigator, document = defaultDocument, } = options; // Type to re-acquire once the document becomes visible again, or `false` when none pending. const requestedType = shallowRef(false); const sentinel = shallowRef(null); const visibility = useDocumentVisibility({ document }); const isSupported = useSupported(() => !!navigator && 'wakeLock' in navigator); const isActive = computed(() => !!sentinel.value && visibility.value === 'visible'); async function forceRequest(type: WakeLockType): Promise { await sentinel.value?.release(); sentinel.value = isSupported.value ? await (navigator as NavigatorWithWakeLock).wakeLock.request(type) : null; } async function request(type: WakeLockType): Promise { if (visibility.value === 'visible') await forceRequest(type); else requestedType.value = type; } async function release(): Promise { requestedType.value = false; const current = sentinel.value; sentinel.value = null; await current?.release(); } if (isSupported.value) { // The browser auto-releases the lock when the document is hidden; // remember the type so we can re-acquire on the next visible transition. useEventListener(sentinel, 'release', () => { requestedType.value = sentinel.value?.type ?? false; }, { passive: true }); watch( () => visibility.value === 'visible' && requestedType.value, (type) => { if (!type) return; requestedType.value = false; forceRequest(type); }, ); } tryOnScopeDispose(() => { release(); }); return { isSupported, sentinel, isActive, request, forceRequest, release, }; }