From 0915600ee491c130fc489d118d2d1272ede204eb Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 18:44:36 -0700 Subject: [PATCH 01/15] keep phone awake on timer --- client/src/hooks/use-wake-lock.ts | 55 +++++++++++++++++++++++++++++++ client/src/pages/tools/timer.tsx | 16 +++++++++ 2 files changed, 71 insertions(+) create mode 100644 client/src/hooks/use-wake-lock.ts diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts new file mode 100644 index 00000000..facab561 --- /dev/null +++ b/client/src/hooks/use-wake-lock.ts @@ -0,0 +1,55 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +/** + * A custom hook to manage a screen wake lock. + * + * @returns {Object} An object with a `wakeLockEnabled` boolean and a `toggleWakeLock` function. + */ +export const useWakeLock = () => { + const wakeLock = useRef(null); + const [isLocked, setIsLocked] = useState(false); + + const request = useCallback(async () => { + if (!("wakeLock" in navigator)) { + console.warn("Screen Wake Lock API not supported"); + return; + } + try { + wakeLock.current = await navigator.wakeLock.request("screen"); + setIsLocked(true); + wakeLock.current.addEventListener("release", () => { + setIsLocked(false); + }); + } catch (err) { + console.error(`Wake Lock request failed: ${err}`); + setIsLocked(false); + } + }, []); + + const release = useCallback(async () => { + if (wakeLock.current) { + await wakeLock.current.release(); + wakeLock.current = null; + setIsLocked(false); + } + }, []); + + useEffect(() => { + const handleVisibilityChange = () => { + if (wakeLock.current && document.visibilityState === "visible") { + request(); + } + }; + + document.addEventListener("visibilitychange", handleVisibilityChange); + document.addEventListener("fullscreenchange", handleVisibilityChange); + + return () => { + document.removeEventListener("visibilitychange", handleVisibilityChange); + document.removeEventListener("fullscreenchange", handleVisibilityChange); + release(); + }; + }, [request, release]); + + return { isLocked, request, release }; +}; diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index bf180fac..8439eed4 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -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, @@ -68,6 +69,21 @@ export default function Timer() { const audioContextRef = useRef(null); const alarmIntervalsRef = useRef>(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) { + requestWakeLock(); + } else if (!hasRunningTimers && isLocked) { + releaseWakeLock(); + } + }, [timers, isLocked, requestWakeLock, releaseWakeLock]); // Clear any alarm handle (interval or timeout) robustly const clearAlarmHandle = useCallback((handle: NodeJS.Timeout) => { From 9f8a189fa38c7de0c44d40d63fdb3cf89ee6d3cb Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 18:53:28 -0700 Subject: [PATCH 02/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index facab561..14dd513e 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; /** * A custom hook to manage a screen wake lock. * - * @returns {Object} An object with a `wakeLockEnabled` boolean and a `toggleWakeLock` function. + * @returns An object with an `isLocked` boolean and `request`/`release` functions. */ export const useWakeLock = () => { const wakeLock = useRef(null); From 2a62e3d049285d64daeac74c5bb0bbd7cbaf6f18 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 18:53:39 -0700 Subject: [PATCH 03/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index 14dd513e..e1466480 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -14,12 +14,24 @@ export const useWakeLock = () => { console.warn("Screen Wake Lock API not supported"); return; } + + shouldHoldLock.current = true; + try { - wakeLock.current = await navigator.wakeLock.request("screen"); + const sentinel = await navigator.wakeLock.request("screen"); + wakeLock.current = sentinel; setIsLocked(true); - wakeLock.current.addEventListener("release", () => { - setIsLocked(false); - }); + + sentinel.addEventListener( + "release", + () => { + if (wakeLock.current === sentinel) { + wakeLock.current = null; + } + setIsLocked(false); + }, + { once: true } + ); } catch (err) { console.error(`Wake Lock request failed: ${err}`); setIsLocked(false); From ea841318e5e3ba0d60226e4398845ed7091c5d9e Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 18:53:53 -0700 Subject: [PATCH 04/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index e1466480..37037a04 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -48,8 +48,12 @@ export const useWakeLock = () => { useEffect(() => { const handleVisibilityChange = () => { - if (wakeLock.current && document.visibilityState === "visible") { - request(); + if ( + shouldHoldLock.current && + !isLocked && + document.visibilityState === "visible" + ) { + void request(); } }; @@ -61,7 +65,7 @@ export const useWakeLock = () => { document.removeEventListener("fullscreenchange", handleVisibilityChange); release(); }; - }, [request, release]); + }, [request, release, isLocked]); return { isLocked, request, release }; }; From 9a161af10ee2f97e1c7cb0d67e59103ddb58ea46 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 18:54:06 -0700 Subject: [PATCH 05/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index 37037a04..469d9450 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -39,8 +39,18 @@ export const useWakeLock = () => { }, []); const release = useCallback(async () => { - if (wakeLock.current) { + 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); } From ad1d6e2ed950fa152a1051175dcf1cc5df4f556c Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 18:54:17 -0700 Subject: [PATCH 06/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index 469d9450..a0ade1b5 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -7,6 +7,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; */ export const useWakeLock = () => { const wakeLock = useRef(null); + const shouldHoldLock = useRef(false); const [isLocked, setIsLocked] = useState(false); const request = useCallback(async () => { From 7f93551dce530e3cfe41fcdeb3cd5952dedfd1af Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:07:03 -0700 Subject: [PATCH 07/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/pages/tools/timer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index 8439eed4..74164ef9 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -79,9 +79,9 @@ export default function Timer() { useEffect(() => { const hasRunningTimers = timers.some(timer => timer.isRunning); if (hasRunningTimers && !isLocked) { - requestWakeLock(); + void requestWakeLock(); } else if (!hasRunningTimers && isLocked) { - releaseWakeLock(); + void releaseWakeLock(); } }, [timers, isLocked, requestWakeLock, releaseWakeLock]); From a87adcc88d5c1a2a1fe813e667d2c511e5f37901 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:07:34 -0700 Subject: [PATCH 08/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index a0ade1b5..30db4373 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -18,23 +18,31 @@ export const useWakeLock = () => { shouldHoldLock.current = true; + if (wakeLock.current || requestInFlight.current) { + return; + } + + 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); } - setIsLocked(false); }, { once: true } ); } catch (err) { - console.error(`Wake Lock request failed: ${err}`); + requestInFlight.current = false; + console.error("Wake Lock request failed:", err); setIsLocked(false); } }, []); From bcce1714d905d6590c15d9b89c48adebcffe600d Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:07:42 -0700 Subject: [PATCH 09/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index 30db4373..64956574 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -82,7 +82,7 @@ export const useWakeLock = () => { return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); document.removeEventListener("fullscreenchange", handleVisibilityChange); - release(); + void release(); }; }, [request, release, isLocked]); From 3de5e52b49e8b278082908dda320d5ef650325e9 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:07:50 -0700 Subject: [PATCH 10/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index 64956574..31fb23f3 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -8,6 +8,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; export const useWakeLock = () => { const wakeLock = useRef(null); const shouldHoldLock = useRef(false); + const requestInFlight = useRef(false); const [isLocked, setIsLocked] = useState(false); const request = useCallback(async () => { From 4523e89704d1e144c33c594133dd226c6c81a574 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:16:40 -0700 Subject: [PATCH 11/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/pages/tools/timer.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index 74164ef9..3f5df428 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -78,9 +78,12 @@ export default function Timer() { // Effect to manage the wake lock useEffect(() => { const hasRunningTimers = timers.some(timer => timer.isRunning); - if (hasRunningTimers && !isLocked) { - void requestWakeLock(); - } else if (!hasRunningTimers && isLocked) { + + if (hasRunningTimers) { + if (!isLocked) { + void requestWakeLock(); + } + } else { void releaseWakeLock(); } }, [timers, isLocked, requestWakeLock, releaseWakeLock]); From 3773d7e826b92f46af536da731b473300b84186f Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:16:55 -0700 Subject: [PATCH 12/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index 31fb23f3..21297253 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -83,7 +83,12 @@ export const useWakeLock = () => { return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); document.removeEventListener("fullscreenchange", handleVisibilityChange); - void release(); + + shouldHoldLock.current = false; + const sentinel = wakeLock.current; + wakeLock.current = null; + requestInFlight.current = false; + void sentinel?.release(); }; }, [request, release, isLocked]); From 631469c627810b55043f1b4b311071bf9a51b612 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:17:03 -0700 Subject: [PATCH 13/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- client/src/hooks/use-wake-lock.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index 21297253..bd830ed1 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -9,14 +9,22 @@ export const useWakeLock = () => { const wakeLock = useRef(null); const shouldHoldLock = useRef(false); const requestInFlight = useRef(false); + const wakeLockSupported = useRef(null); const [isLocked, setIsLocked] = useState(false); const request = useCallback(async () => { - if (!("wakeLock" in navigator)) { - console.warn("Screen Wake Lock API not supported"); + if (wakeLockSupported.current === false) { return; } + if (wakeLockSupported.current === null) { + wakeLockSupported.current = "wakeLock" in navigator; + if (!wakeLockSupported.current) { + console.warn("Screen Wake Lock API not supported"); + return; + } + } + shouldHoldLock.current = true; if (wakeLock.current || requestInFlight.current) { From 6fc5dd87c9c6396c56e4d890109afb9bd26d81a6 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:22:10 -0700 Subject: [PATCH 14/15] add condition --- client/src/pages/tools/timer.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index 3f5df428..75130d96 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -80,9 +80,7 @@ export default function Timer() { const hasRunningTimers = timers.some(timer => timer.isRunning); if (hasRunningTimers) { - if (!isLocked) { - void requestWakeLock(); - } + void requestWakeLock(); } else { void releaseWakeLock(); } From 026959c84027c6042ee21839efc0ec918478abd7 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Mon, 29 Jun 2026 19:40:41 -0700 Subject: [PATCH 15/15] fix race condition --- client/src/hooks/use-wake-lock.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/use-wake-lock.ts b/client/src/hooks/use-wake-lock.ts index bd830ed1..e2912734 100644 --- a/client/src/hooks/use-wake-lock.ts +++ b/client/src/hooks/use-wake-lock.ts @@ -35,9 +35,16 @@ export const useWakeLock = () => { try { const sentinel = await navigator.wakeLock.request("screen"); + + // If release() was called while the request was in flight, + // release the new sentinel immediately and do nothing. + if (!shouldHoldLock.current) { + void sentinel.release(); + return; + } + wakeLock.current = sentinel; setIsLocked(true); - requestInFlight.current = false; sentinel.addEventListener( "release", @@ -50,9 +57,10 @@ export const useWakeLock = () => { { once: true } ); } catch (err) { - requestInFlight.current = false; console.error("Wake Lock request failed:", err); setIsLocked(false); + } finally { + requestInFlight.current = false; } }, []);