npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ngrithms/idle

v0.4.0

Published

Modern Angular user-inactivity detector — standalone, signals-first, provideIdle(), SSR-safe, zero dependencies.

Readme

@ngrithms/idle

npm License: MIT

Signal-first user-inactivity detector for Angular 17+. Standalone, signals primary (observables as bridges), SSR-safe, zero runtime dependencies. Includes multi-tab synchronisation via BroadcastChannel, a sleep watchdog for laptop-lid-closed scenarios, and an optional @ngrithms/idle/keepalive companion that keeps your server's session expiry in lockstep with the browser.

provideIdle({ idleAfter: 5 * 60_000, timeout: 30_000 });

Install

npm install @ngrithms/idle

Peer-compatible with Angular >=17.2.0 <22.0.0. No runtime dependencies.

Quick start

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideIdle } from '@ngrithms/idle';

export const appConfig: ApplicationConfig = {
  providers: [
    provideIdle({
      idleAfter: 5 * 60_000,  // ms of silence before ACTIVE → IDLE
      timeout: 30_000,        // ms more before IDLE → TIMED_OUT
    }),
  ],
};
// any component
import { inject } from '@angular/core';
import { IdleService, IfIdleDirective } from '@ngrithms/idle';

@Component({
  imports: [IfIdleDirective],
  template: `
    <p>Status: {{ idle.state() }} ({{ idle.countdown() }}s)</p>
    <div *ngrIfIdle>Looks like you stepped away.</div>
    <div *ngrIfIdle="'timedOut'">Session ended. <button (click)="idle.reset()">Resume</button></div>
  `,
})
export class Status {
  protected readonly idle = inject(IdleService);
}

How it works

A three-state machine driven by passive DOM listeners on document:

ACTIVE  --(idleAfter ms quiet)-->  IDLE  --(timeout ms quiet)-->  TIMED_OUT
   ^------------------- activity ----------------------|

Activity after TIMED_OUT does not auto-restart — callers must invoke reset() explicitly. This makes TIMED_OUT a deliberate state for auto-logout flows that require fresh authentication on resume.

IDLE is the "soft warning" phase. countdown() ticks 1/sec during this window so you can render an "Are you still there?" prompt with a live countdown.

API

provideIdle(config)

Registers the service tree. Call once in your ApplicationConfig.

IdleService — injected via inject(IdleService)

| Member | Type | Description | |---|---|---| | state | Signal<'active' \| 'idle' \| 'timedOut'> | Current state machine value. | | countdown | Signal<number> | Seconds remaining in the timeout window while 'idle'; 0 otherwise. | | lastActivity | Signal<number> | Date.now() of the most-recent activity (local or remote). | | interrupts | Signal<readonly string[]> | Event names currently wired. Empty while stop()-ped. | | onIdleStart | Observable<void> | Fires once each time the state transitions to 'idle'. | | onTimeout | Observable<void> | Fires once each time the state transitions to 'timedOut'. | | onInterrupt | Observable<void> | Fires on every (post-throttle) activity event. | | reset() | void | Resets to 'active'. Broadcast to other tabs if crossTabReset is on. | | watch() | void | Manually start watching. No-op if autoStart: true (the default). | | stop() | void | Detach listeners + close channel. State is preserved. |

*ngrIfIdle

Structural directive that renders its template while the state matches.

<div *ngrIfIdle>Are you still there?</div>          <!-- default match: 'idle' -->
<div *ngrIfIdle="'timedOut'">Session ended.</div>
<div *ngrIfIdle="'active'">Welcome back.</div>

Configuration reference

| Option | Type | Default | Description | |---|---|---|---| | idleAfter | number | 300000 (5 min) | Milliseconds of silence before ACTIVE → IDLE. | | timeout | number | 30000 (30 s) | Milliseconds in IDLE before IDLE → TIMED_OUT. | | events | readonly string[] | ['mousemove', 'keydown', 'touchstart', 'scroll', 'click', 'wheel'] | DOM events on document treated as activity. | | throttleMs | number | 250 | Leading-edge throttle for the activity handler. Prevents change-detection churn on continuous mousemove. | | autoStart | boolean | true | Start watching on service construction. Set false and call watch() manually for deferred starts. | | pauseWhenHidden | boolean | true | When document.hidden is true (tab not focused), freeze the timer; resume on visible. Prevents backgrounded tabs from timing out instantly under browser throttling. | | multiTabSync | boolean | true | Open a BroadcastChannel('ngrithms-idle') so activity in any tab resets the timer in every listening tab. | | crossTabTimeout | boolean | true | When multiTabSync is on, a TIMED_OUT transition in any tab puts every tab into 'timedOut'. Aligns session-expiry semantics across the app. | | crossTabReset | boolean | true | When multiTabSync is on, a reset() call in any tab returns every tab to 'active'. Lets a single "Stay signed in" click revive all tabs. | | crossTabRevival | boolean | false | When on, a remote activity message arriving in a 'timedOut' tab calls reset() on it. Opt-in because most session-expiry flows want 'timedOut' to be terminal. | | onSystemSleep | 'timeout' \| 'pause' | 'timeout' | What to do when a wall-clock skew is detected (laptop lid closed, tab hard-throttled). 'timeout' snaps to 'timedOut'. 'pause' shifts the anchor timestamp forward by the detected sleep duration so the counter effectively pauses through sleep. |

Recipes

Auto-logout

const idle = inject(IdleService);
const auth = inject(AuthService);
const router = inject(Router);

idle.onTimeout.subscribe(() => {
  auth.logout();
  router.navigate(['/login'], { queryParams: { reason: 'idle' } });
});

"Are you still there?" countdown modal

The library was designed for this pattern. IDLE is the soft-warning phase, TIMED_OUT is the terminal action.

provideIdle({ idleAfter: 14 * 60_000, timeout: 60_000 });  // 14 min then 1 min warning
<dialog *ngrIfIdle>
  Signing out in {{ idle.countdown() }}s.
  <button (click)="idle.reset()">Stay signed in</button>
</dialog>

With crossTabReset: true (default), clicking "Stay signed in" in one tab revives all of them. With crossTabTimeout: true (default), expiring in one tab signs out all of them.

Autosave on idle, logout on extended idle (one service, two phases)

Use the existing IDLE / TIMED_OUT transitions instead of two service instances. onIdleStart fires after the short window; onTimeout fires after the long window.

provideIdle({
  idleAfter: 10_000,                // 10 s → fire autosave
  timeout: 10 * 60_000 - 10_000,    // 9 min 50 s more → fire logout
});
idle.onIdleStart.subscribe(() => this.draft.save());
idle.onTimeout.subscribe(() => this.auth.logout());

Pause real-time polling when the user isn't engaged

effect(() => {
  if (this.idle.state() === 'active') this.polling.start();
  else this.polling.stop();
});

Saves significant server load on chat apps and dashboards.

AFK / presence indicator in collaborative apps

Publish your own state over your existing realtime channel.

effect(() => this.presence.publish({ userId: me.id, state: this.idle.state() }));

Server keepalive (companion entry point)

See Keepalive below.

Multi-tab and multi-browser

Two tabs in the same browser process: BroadcastChannel covers it. Activity, timeouts, and reset() calls sync automatically with the defaults (multiTabSync: true, crossTabTimeout: true, crossTabReset: true). The user can't extend their session merely by being active in a different tab, and "Stay signed in" works from any tab.

Two tabs in different browsers (Chrome AND Firefox simultaneously) or different machines (laptop AND phone): BroadcastChannel does not cross browser processes. Cookies, localStorage, and sessionStorage are also per-browser. The only way to coordinate idle across browsers or devices is server-driven: the server tracks last-seen across all of a user's sessions and pushes "session expired" / "user active elsewhere" events to each connected client via WebSocket / SSE.

This lib makes it easy to be the client end of that system — subscribe to your transport, call idle.reset() on "user-active-elsewhere" messages and call your own logout flow on onTimeout. But the cross-browser orchestration itself isn't something a client-only library can solve.

System sleep

If a laptop lid is closed (or a tab is hard-throttled by the browser) for long enough, the JS event loop pauses. The library detects this on resume via a watchdog that compares the wall-clock gap between expected and actual tick fires.

  • onSystemSleep: 'timeout' (default) — snap to 'timedOut'. Conservative; assumes the user genuinely walked away.
  • onSystemSleep: 'pause' — shift the relevant anchor timestamp forward by the detected sleep duration. Time spent asleep doesn't count toward the idle counter. Useful for laptops-with-lids scenarios where closing the lid shouldn't terminate a session.

The watchdog also catches sleep that spans the ACTIVE → IDLE boundary, not just sleep during the countdown.

Keepalive

Server-side session timeouts and client-side idle timeouts have to agree, or you get 401s on submit when the server expired while the user was actively typing in a form (no API calls of their own).

Pair provideIdle with provideIdleKeepalive from the secondary entry point:

import { provideIdle } from '@ngrithms/idle';
import { provideIdleKeepalive } from '@ngrithms/idle/keepalive';

providers: [
  provideIdle({ idleAfter: 5 * 60_000, timeout: 30_000 }),
  provideIdleKeepalive({
    url: '/api/keepalive',
    intervalSeconds: 60,
    method: 'GET',          // or 'POST', 'HEAD'
    withCredentials: true,  // sends cookies
    headers: { 'X-CSRF': '...' },  // optional
  }),
]
  • Pings the URL every intervalSeconds while state() === 'active'.
  • Stops pinging immediately on 'idle' or 'timedOut'.
  • Best-effort: network errors and non-2xx responses are silently swallowed.
  • SSR-safe (no pings from the server platform).

Ships as a secondary entry point of the same npm package — not a second package. Avoids the version-mismatch issues that the @ng-idle/core@ng-idle/keepalive split has caused for years.

SSR

All DOM listeners, BroadcastChannel, document.hidden, and timers are guarded by isPlatformBrowser. On the server platform the service returns a frozen state = 'active' and watch() is a no-op. Safe to import and inject anywhere — no SSR build-fail.

Comparison to @ng-idle/core

| | @ng-idle/core | @ngrithms/idle | |---|---|---| | Setup | NgModule forRoot | provideIdle({...}) functional config | | State surface | RxJS-only | Signals primary, observables as bridges | | Multi-tab sync | storage events | BroadcastChannel | | System-sleep handling | none / bug-prone | Watchdog (snap or pause, configurable) | | Keepalive | separate npm package | Secondary entry point in same package | | Cross-tab timeout / reset | none | Built-in flags | | Standalone components | ✗ | ✓ | | Signals | ✗ | ✓ | | Bundle (gz, core) | larger | ~4 KB |

License

MIT © Aboud Badra