File size: 2,681 Bytes
6c3af4e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { useEffect } from "react";
import { addLogEntry } from "@/modules/logEntries";

const describeError = (error: unknown) =>
  error instanceof Error ? error.message : "unknown error";

/**
 * Holds the platform screen wake lock while `isActive` is true, so a phone left
 * untouched during a long answer does not lock while the text streams in.
 *
 * The platform releases the lock on its own whenever the document stops being
 * visible (tab switch, screen lock) and a released sentinel cannot be reused,
 * so a fresh one is requested on every return to visibility. A lock the browser
 * drops for another reason (power saving, low battery) is only replaced on the
 * next return to visibility, because re-requesting straight from the `release`
 * event fights the browser's own decision. The same goes for a visibility round
 * trip that happens entirely while a request is pending: the request in flight
 * wins, and if it fails there is no event left to retry on until the next one.
 * See https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
 */
export function useScreenWakeLock(isActive: boolean) {
  useEffect(() => {
    if (!isActive || !("wakeLock" in navigator)) return;

    let sentinel: WakeLockSentinel | null = null;
    let isEffectActive = true;
    let isRequestInFlight = false;

    const acquireWakeLock = async () => {
      if (
        sentinel ||
        isRequestInFlight ||
        document.visibilityState !== "visible"
      ) {
        return;
      }

      isRequestInFlight = true;

      try {
        const requestedSentinel = await navigator.wakeLock.request("screen");

        if (!isEffectActive) {
          await requestedSentinel.release();
          return;
        }

        if (requestedSentinel.released) return;

        sentinel = requestedSentinel;
        requestedSentinel.addEventListener("release", () => {
          if (sentinel === requestedSentinel) sentinel = null;
        });
        addLogEntry("Screen wake lock acquired");
      } catch (error) {
        addLogEntry(`Screen wake lock unavailable: ${describeError(error)}`);
      } finally {
        isRequestInFlight = false;
      }
    };

    acquireWakeLock();
    document.addEventListener("visibilitychange", acquireWakeLock);

    return () => {
      isEffectActive = false;
      document.removeEventListener("visibilitychange", acquireWakeLock);
      sentinel
        ?.release()
        .then(() => addLogEntry("Screen wake lock released"))
        .catch((error: unknown) => {
          addLogEntry(
            `Failed to release screen wake lock: ${describeError(error)}`,
          );
        });
    };
  }, [isActive]);
}