Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions client/src/hooks/use-wake-lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useCallback, useEffect, useRef, useState } from "react";

/**
* A custom hook to manage a screen wake lock.
*
* @returns An object with an `isLocked` boolean and `request`/`release` functions.
*/
Comment thread
Copilot marked this conversation as resolved.
export const useWakeLock = () => {
const wakeLock = useRef<WakeLockSentinel | null>(null);
const shouldHoldLock = useRef(false);
const requestInFlight = useRef(false);
const [isLocked, setIsLocked] = useState(false);
Comment thread
Copilot marked this conversation as resolved.
Comment thread
Copilot marked this conversation as resolved.

const request = useCallback(async () => {
if (!("wakeLock" in navigator)) {
console.warn("Screen Wake Lock API not supported");
return;
}

shouldHoldLock.current = true;

if (wakeLock.current || requestInFlight.current) {
return;
}
Comment thread
Copilot marked this conversation as resolved.

requestInFlight.current = true;

try {
const sentinel = await navigator.wakeLock.request("screen");
wakeLock.current = sentinel;
setIsLocked(true);
requestInFlight.current = false;

sentinel.addEventListener(
"release",
() => {
if (wakeLock.current === sentinel) {
wakeLock.current = null;
setIsLocked(false);
}
},
{ once: true }
);
} catch (err) {
requestInFlight.current = false;
console.error("Wake Lock request failed:", err);
setIsLocked(false);
}
Comment thread
Copilot marked this conversation as resolved.
Comment thread
spring1843 marked this conversation as resolved.
}, []);
Comment thread
Copilot marked this conversation as resolved.

const release = useCallback(async () => {
shouldHoldLock.current = false;

if (!wakeLock.current) {
setIsLocked(false);
return;
}

try {
await wakeLock.current.release();
} catch {
// ignore (it may have already been released by the browser)
} finally {
wakeLock.current = null;
setIsLocked(false);
}
}, []);
Comment thread
Copilot marked this conversation as resolved.

useEffect(() => {
const handleVisibilityChange = () => {
if (
shouldHoldLock.current &&
!isLocked &&
document.visibilityState === "visible"
) {
void request();
}
};

document.addEventListener("visibilitychange", handleVisibilityChange);
document.addEventListener("fullscreenchange", handleVisibilityChange);

return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
document.removeEventListener("fullscreenchange", handleVisibilityChange);
void release();
};
Comment thread
Copilot marked this conversation as resolved.
Comment on lines +103 to +108
}, [request, release, isLocked]);

return { isLocked, request, release };
};
16 changes: 16 additions & 0 deletions client/src/pages/tools/timer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from "@/lib/time-tools";
import { getParam, updateURL, copyShareableURL } from "@/lib/url-sharing";
import { useToast } from "@/hooks/use-toast";
import { useWakeLock } from "@/hooks/use-wake-lock";
import {
ToolButton,
ResetButton,
Expand Down Expand Up @@ -68,6 +69,21 @@ export default function Timer() {
const audioContextRef = useRef<AudioContext | null>(null);
const alarmIntervalsRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
const { toast } = useToast();
const {
isLocked,
request: requestWakeLock,
release: releaseWakeLock,
} = useWakeLock();

// Effect to manage the wake lock
useEffect(() => {
const hasRunningTimers = timers.some(timer => timer.isRunning);
if (hasRunningTimers && !isLocked) {
void requestWakeLock();
} else if (!hasRunningTimers && isLocked) {
void releaseWakeLock();
}
}, [timers, isLocked, requestWakeLock, releaseWakeLock]);
Comment thread
Copilot marked this conversation as resolved.
Comment on lines +78 to +87

// Clear any alarm handle (interval or timeout) robustly
const clearAlarmHandle = useCallback((handle: NodeJS.Timeout) => {
Expand Down
Loading