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

@uekichinos/sentinel

v0.2.0

Published

Lightweight idle detection for the browser. Fires callbacks and optionally notifies a backend when the user goes inactive. Supports TTL timeouts, tab visibility pausing, activity throttling, and dynamic auth headers. Zero dependencies.

Readme

@uekichinos/sentinel

Socket Badge

Lightweight idle detection for the browser. Fires callbacks and optionally notifies a backend when the user goes inactive. Zero dependencies.

const sentinel = createSentinel({
  timeout: '15m',
  onIdle: () => showLogoutWarning(),
  onActive: () => hideLogoutWarning(),
})

sentinel.start()

Installation

npm install @uekichinos/sentinel

Quick start

import { createSentinel } from '@uekichinos/sentinel'

const sentinel = createSentinel({
  timeout: '15m',
  onIdle: () => console.log('User is idle'),
  onActive: () => console.log('User is back'),
})

sentinel.start()

API

createSentinel(options)

Returns a SentinelInstance.

createSentinel(options: SentinelOptions): SentinelInstance

sentinel.start()

Begins listening for user activity and starts the idle countdown. Safe to call multiple times — idempotent.

sentinel.stop()

Removes all event listeners and cancels the countdown. Resets internal state so start() can be called again.

sentinel.reset()

Restarts the idle countdown from zero. If currently idle, transitions back to active and fires onActive.

sentinel.isIdle()

Returns true if the user is currently idle.

sentinel.getRemainingMs()

Returns the number of milliseconds remaining until the user is considered idle. Returns 0 when already idle or when the sentinel has not been started.

Useful for building countdown indicators or progress bars:

setInterval(() => {
  progressBar.style.width = `${(sentinel.getRemainingMs() / timeoutMs) * 100}%`
}, 100)

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | timeout | TtlInput | — | How long before the user is considered idle | | onIdle | () => void | — | Called when the user transitions active → idle | | onActive | () => void | — | Called when the user transitions idle → active | | notify | NotifyOptions | — | Fetch a backend endpoint when idle (see below) | | events | string[] | see below | DOM events that count as activity | | throttle | number | 500 | Min ms between activity handler calls | | watchVisibility | boolean | true | Pause countdown when the tab is hidden |

Default events: mousemove, keydown, scroll, click, touchstart


TTL formats

| Format | Duration | |--------|----------| | '30s' | 30 seconds | | '5m' | 5 minutes | | '15m' | 15 minutes | | '1h' | 1 hour | | 5000 | 5000 milliseconds |


notify — backend notification

Fire a fetch request automatically when the user goes idle. Useful for invalidating server-side sessions or logging inactivity.

const sentinel = createSentinel({
  timeout: '15m',
  notify: {
    url: '/api/session/idle',
    method: 'POST',                          // default
    headers: () => ({                        // function — evaluated at idle time
      Authorization: `Bearer ${getToken()}`,
      'Content-Type': 'application/json',
    }),
    body: { reason: 'idle' },
  },
})

| Option | Type | Default | Description | |--------|------|---------|-------------| | url | string | — | Endpoint to call | | method | string | 'POST' | HTTP method | | headers | Record<string, string> \| () => Record<string, string> \| Promise<Record<string, string>> | — | Static or dynamic headers (sync or async) | | body | unknown \| () => unknown | — | Request body — serialised to JSON, or a factory evaluated at idle time |

Headers as a function — the function is called at the moment idle fires, not at init. Supports both sync and async functions. This ensures you always send a fresh token rather than one captured when the page loaded.

// Token captured at init — may be stale after a refresh
headers: { Authorization: `Bearer ${getToken()}` }

// Token evaluated at idle time — always fresh (sync)
headers: () => ({ Authorization: `Bearer ${getToken()}` })

// Async token refresh — awaited before the request fires
headers: async () => ({ Authorization: `Bearer ${await refreshToken()}` })

Body as a function — like headers, a body factory is evaluated at idle time rather than at init. Useful for capturing dynamic state:

body: () => ({ userId: store.user.id, sessionId: store.session.id })

The notify request fails silently on network error — onIdle always fires regardless.


Examples

Auto-logout with session warning

let warningTimer

const sentinel = createSentinel({
  timeout: '14m',
  onIdle: () => {
    showWarning('You will be logged out in 1 minute')
    warningTimer = setTimeout(() => logout(), 60_000)
  },
  onActive: () => {
    hideWarning()
    clearTimeout(warningTimer)
  },
  notify: {
    url: '/api/session/extend',
    headers: () => ({ Authorization: `Bearer ${getToken()}` }),
  },
})

sentinel.start()

Pause API polling when idle

let pollInterval

const sentinel = createSentinel({
  timeout: '5m',
  onIdle: () => {
    clearInterval(pollInterval)
  },
  onActive: () => {
    pollInterval = setInterval(fetchData, 5000)
  },
})

pollInterval = setInterval(fetchData, 5000)
sentinel.start()

Countdown indicator

const TIMEOUT_MS = 15 * 60 * 1000 // 15m in ms

const sentinel = createSentinel({ timeout: TIMEOUT_MS })
sentinel.start()

setInterval(() => {
  const pct = (sentinel.getRemainingMs() / TIMEOUT_MS) * 100
  progressBar.style.width = `${pct}%`
}, 200)

Stop on page unload

sentinel.start()
window.addEventListener('beforeunload', () => sentinel.stop())

Via <script> tag (no bundler)

<script src="https://unpkg.com/@uekichinos/sentinel/dist/index.global.js"></script>
<script>
  const sentinel = Sentinel.createSentinel({
    timeout: '15m',
    onIdle: () => console.log('idle'),
  })
  sentinel.start()
</script>

License

MIT © uekichinos