Write createIdleTimer({ target, events, timeout, onIdle, onActive }). It tells you when the user has stopped using the page for timeout ms, for example to warn them before they're signed out, and when they come back.
target is an EventTarget, such as document. events is an array of event types, such as ["mousemove", "keydown"]. An event of one of those types on target is activity. Other events aren't.
The user starts out active. After timeout ms without activity, it calls onIdle(), once, and the user is idle.
Activity while the user is idle calls onActive(), once, and the user is active again. Any activity, whether the user is idle or not, starts the timeout ms over.
It returns an object with two methods. isIdle() returns true while the user is idle, and false otherwise. stop() stops watching for good: it removes its event listeners and leaves no timer running, so onIdle and onActive are never called again.
const idle = createIdleTimer({
target: document,
events: ["mousemove", "keydown", "pointerdown"],
timeout: 60000,
onIdle: () => console.log("idle"),
onActive: () => console.log("back"),
});
// a minute without input: logs "idle"
idle.isIdle(); // true
// then a key press: logs "back"
idle.isIdle(); // false
// another minute without input: logs "idle" again
idle.stop(); // nothing is logged after this
const clock = useFakeTimers();
const onIdle = spy();
const idle = createIdleTimer({ target: new EventTarget(), events: ["keydown"], timeout: 1000, onIdle, onActive: spy() });
expect(idle.isIdle()).toBe(false);
clock.tick(999);
expect(onIdle).toHaveBeenCalledTimes(0);
clock.tick(1);
expect(onIdle).toHaveBeenCalledTimes(1);
expect(idle.isIdle()).toBe(true);
clock.tick(5000);
expect(onIdle).toHaveBeenCalledTimes(1);
const clock = useFakeTimers();
const target = new EventTarget();
const onIdle = spy();
createIdleTimer({ target, events: ["keydown", "mousemove"], timeout: 1000, onIdle, onActive: spy() });
clock.tick(800);
target.dispatchEvent(new Event("keydown"));
clock.tick(800);
target.dispatchEvent(new Event("mousemove"));
clock.tick(999);
expect(onIdle).toHaveBeenCalledTimes(0);
clock.tick(1);
expect(onIdle).toHaveBeenCalledTimes(1);
const clock = useFakeTimers();
const target = new EventTarget();
const onIdle = spy();
createIdleTimer({ target, events: ["keydown"], timeout: 1000, onIdle, onActive: spy() });
clock.tick(500);
target.dispatchEvent(new Event("click"));
clock.tick(500);
expect(onIdle).toHaveBeenCalledTimes(1);
const clock = useFakeTimers();
const target = new EventTarget();
const onActive = spy();
const idle = createIdleTimer({ target, events: ["keydown"], timeout: 1000, onIdle: spy(), onActive });
target.dispatchEvent(new Event("keydown"));
expect(onActive).toHaveBeenCalledTimes(0);
clock.tick(1000);
target.dispatchEvent(new Event("keydown"));
target.dispatchEvent(new Event("keydown"));
expect(onActive).toHaveBeenCalledTimes(1);
expect(idle.isIdle()).toBe(false);
const clock = useFakeTimers();
const target = new EventTarget();
const log = [];
const idle = createIdleTimer({
target,
events: ["keydown"],
timeout: 1000,
onIdle: () => log.push("idle"),
onActive: () => log.push("active"),
});
clock.tick(4000);
target.dispatchEvent(new Event("keydown"));
clock.tick(999);
expect(log).toEqual(["idle", "active"]);
clock.tick(1);
expect(log).toEqual(["idle", "active", "idle"]);
expect(idle.isIdle()).toBe(true);
const clock = useFakeTimers();
const target = new EventTarget();
const onIdle = spy();
const idle = createIdleTimer({ target, events: ["keydown"], timeout: 1000, onIdle, onActive: spy() });
clock.tick(500);
idle.stop();
expect(clock.pending()).toBe(0);
target.dispatchEvent(new Event("keydown"));
expect(clock.pending()).toBe(0);
clock.tick(5000);
expect(onIdle).not.toHaveBeenCalled();
const clock = useFakeTimers();
const target = new EventTarget();
const onActive = spy();
const idle = createIdleTimer({ target, events: ["keydown", "mousemove"], timeout: 1000, onIdle: spy(), onActive });
clock.tick(1000);
idle.stop();
target.dispatchEvent(new Event("keydown"));
target.dispatchEvent(new Event("mousemove"));
expect(onActive).not.toHaveBeenCalled();
expect(clock.pending()).toBe(0);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Idle timer. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.