fix(countdown): prevent overlapping timers with in-flight lock

- Add countdownInProgress flag in IPC handler to reject concurrent starts
- Add countdownActive state in renderer to block re-entry
- Disable Record button while countdown is active
This commit is contained in:
Varun Kumar
2026-03-16 10:06:07 +05:30
parent 04dcbc32b8
commit dc6a49e1e1
3 changed files with 23 additions and 5 deletions
+9
View File
@@ -81,6 +81,7 @@ let cachedSystemCursorAssets: Record<string, SystemCursorAsset> | null = null
let cachedSystemCursorAssetsSourceMtimeMs: number | null = null
let countdownTimer: ReturnType<typeof setInterval> | null = null
let countdownCancelled = false
let countdownInProgress = false
type SystemCursorAsset = {
dataUrl: string
@@ -2767,6 +2768,11 @@ export function registerIpcHandlers(
})
ipcMain.handle('start-countdown', async (_, seconds: number) => {
if (countdownInProgress) {
return { success: false, error: 'Countdown already in progress' }
}
countdownInProgress = true
countdownCancelled = false
const countdownWin = createCountdownWindow()
@@ -2789,6 +2795,7 @@ export function registerIpcHandlers(
countdownTimer = null
}
closeCountdownWindow()
countdownInProgress = false
resolve({ success: false, cancelled: true })
return
}
@@ -2801,6 +2808,7 @@ export function registerIpcHandlers(
countdownTimer = null
}
closeCountdownWindow()
countdownInProgress = false
resolve({ success: true })
} else {
const win = getCountdownWindow()
@@ -2814,6 +2822,7 @@ export function registerIpcHandlers(
ipcMain.handle('cancel-countdown', () => {
countdownCancelled = true
countdownInProgress = false
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
+2 -1
View File
@@ -31,6 +31,7 @@ export function LaunchWindow() {
const LOCALE_LABELS: Record<string, string> = { en: "EN", es: "ES", "zh-CN": "中文" };
const {
recording,
countdownActive,
toggleRecording,
microphoneEnabled,
setMicrophoneEnabled,
@@ -283,7 +284,7 @@ export function LaunchWindow() {
variant="link"
size="sm"
onClick={hasSelectedSource ? toggleRecording : openSourceSelector}
disabled={!hasSelectedSource && !recording}
disabled={countdownActive || (!hasSelectedSource && !recording)}
className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 text-xs ${styles.electronNoDrag}`}
>
{recording ? (
+12 -4
View File
@@ -28,6 +28,7 @@ const MIC_GAIN_BOOST = 1.4;
type UseScreenRecorderReturn = {
recording: boolean;
countdownActive: boolean;
toggleRecording: () => void;
preparePermissions: (options?: { startup?: boolean }) => Promise<boolean>;
isMacOS: boolean;
@@ -44,6 +45,7 @@ type UseScreenRecorderReturn = {
export function useScreenRecorder(): UseScreenRecorderReturn {
const [recording, setRecording] = useState(false);
const [starting, setStarting] = useState(false);
const [countdownActive, setCountdownActive] = useState(false);
const [isMacOS, setIsMacOS] = useState(false);
const [microphoneEnabled, setMicrophoneEnabled] = useState(false);
const [microphoneDeviceId, setMicrophoneDeviceId] = useState<string | undefined>(undefined);
@@ -570,7 +572,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
};
const toggleRecording = async () => {
if (starting) {
if (starting || countdownActive) {
return;
}
@@ -581,9 +583,14 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
// Start recording with optional countdown
if (countdownDelay > 0) {
const result = await window.electronAPI.startCountdown(countdownDelay);
if (!result.success || result.cancelled) {
return;
setCountdownActive(true);
try {
const result = await window.electronAPI.startCountdown(countdownDelay);
if (!result.success || result.cancelled) {
return;
}
} finally {
setCountdownActive(false);
}
}
@@ -592,6 +599,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
return {
recording,
countdownActive,
toggleRecording,
preparePermissions,
isMacOS,