Reputation: 2072
I’m using the worker-timers npm package in a Vue 3 + TypeScript application to implement a lock/unlock mechanism of notes using Ably
(where the notes are shared between multiple users). The idea is that when a user focuses on a note editor, it is locked by that user (implementation is not relevant to the question/issue). After the note is locked and I start an inactivity timer (5 minutes by default). If the user doesn’t interact with the editor for that duration, the note should unlock automatically.
Usage flow:
User focuses on the note’s editor (e.g., clicking in an editable area).
I create a fresh instance of an InactivityTimer
class each time the editor is focused.
This timer uses workerSetTimeout
from worker-timers
to schedule a callback after timeoutDuration
milliseconds.
When the callback fires, it triggers onInactive which unlocks the note.
If the user interacts again (e.g., typing or focusing), I reset or re-init the timer.
This works fine under normal circumstances, including switching tabs or move to different app. worker-timers
helps avoid some throttling issues. However, after the system goes into a deep sleep (e.g., the machine was sleeping for some time), once the user returns and tries to lock a note by focusing on it, the inactivity callback fires immediately instead of waiting the configured 5 minutes.
What I’ve tried:
InactivityTimer
each time focus occurs.init
method which clears all timer, resets composalbe variables and creates new workerTimeoutNone of these attempts have solved the issue. After system resume, the scheduled timeout seems to consider the intended delay already passed, causing the callback to fire instantly.
import { setTimeout as workerSetTimeout, clearTimeout as workerClearTimeout } from "worker-timers";
interface InactivityOptions {
timeout: number
onInactive: () => Promise<void> | void
}
type InactivityCallback = (() => Promise<void> | void) | null
export class InactivityTimer {
private DEFAULT_TIMEOUT = 5 * 60 * 1000;
private THIRTY_SEC_TIMEOUT = 30 * 1000;
private inactivityTimerId: number | null = null;
private warningTimerId: number | null = null;
private inactivityCallback: InactivityCallback = null;
private timeoutDuration = this.DEFAULT_TIMEOUT;
public showWarning = false;
private clearTimers() {
if (this.warningTimerId !== null) {
workerClearTimeout(this.warningTimerId);
this.warningTimerId = null;
}
if (this.inactivityTimerId !== null) {
workerClearTimeout(this.inactivityTimerId);
this.inactivityTimerId = null;
}
}
public resetTimer() {
this.clearTimers();
this.showWarning = false;
if (this.inactivityCallback) {
this.inactivityTimerId = workerSetTimeout(() => {
this.showWarning = true;
this.warningTimerId = workerSetTimeout(() => {
this.showWarning = false;
if (this.inactivityCallback) {
this.inactivityCallback();
}
}, this.THIRTY_SEC_TIMEOUT);
}, 5 * 60 * 1000);
}
}
public initInactivityDetector({ timeout, onInactive }: InactivityOptions) {
this.timeoutDuration = timeout > 0 ? timeout : this.DEFAULT_TIMEOUT;
this.inactivityCallback = onInactive;
this.resetTimer();
}
public stopInactivityDetector() {
this.clearTimers();
this.inactivityCallback = null;
this.showWarning = false;
}
}
this method is run when user clicks on a note to lock it:
const inactivityTimerRef = ref<InactivityTimer | null>(null);
const onInactive = async (noteId: string) => {
// code that unlocks the note
};
const onEditorFocus = async () => {
if (locked.value) return;
const newTimer = new InactivityTimer();
inactivityTimerRef.value = newTimer;
isEditorOnFocus.value = true;
await setFocus(note.value.id);
newTimer.initInactivityDetector({
timeout: UNLOCK_NOTE_TIMEOUT,
onInactive: () => onInactive(note.value.id),
});
newTimer.resetTimer();
};
The problem:
After waking from deep sleep or significant browser suspension, focusing the note again triggers the inactivity callback immediately instead of waiting 5 minutes. It seems the worker timer treats the scheduled time as already elapsed.
Upvotes: 0
Views: 26