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

@sentinel-nvr/web

v0.16.2

Published

Shared browser code for the Sentinel NVR plugin for Scrypted: API model + client (/api), player (/player) and React UI with i18n + themes (/ui). Consumed by the plugin's own web UI and the HAPulse dashboard.

Readme

sentinel-nvr-web

Shared browser code for the Sentinel NVR plugin for Scrypted (a private repository, Muggedadscher/sentinel-nvr; its HTTP/WebSocket API is described in that repository's docs/API.md). Two consumers use these packages so their camera UI stays identical and bugs are fixed once:

  • the plugin's own web UI (sentinel-nvr/ui)
  • the native Sentinel NVR integration in the HAPulse dashboard

Package

One npm package, @sentinel-nvr/web, with subpath entry points (one version, one bump in both consumers):

| Entry point | Status | Contents | |---|---|---| | @sentinel-nvr/web/api | available | DOM-free data model + types mirroring the plugin's docs/API.md, URL/setup parsing, event classification, storage forecast, timeline clip-run merge, locale formatters, and a fetch/WebSocket SentinelClient. No framework deps. | | @sentinel-nvr/web/player | available | PlayerController, WebRTC signaling, the no-reneg recorded-playback relay client, watchdog and the live/recorded fallback chain. | | @sentinel-nvr/web/ui | available | React components — the whole camera page (CameraPage: stage, control pill, info bar, tabs, class filters, vertical timeline, events list, date chip; CameraTitle header row), class badges, event strip, camera tiles, date picker, stat cards — the i18n dictionaries (nvr.*, 7 locales) and the theme system (4 identities × light/dark/auto + accent hue). Styles in @sentinel-nvr/web/ui/ui.css (HAPulse tokens). |

The earlier @sentinel-nvr/[email protected] is deprecated in favour of @sentinel-nvr/web/api.

Server compatibility (recorded playback)

The player talks to the plugin's relay endpoints (docs/API.md in sentinel-nvr). Newer packages degrade gracefully on older servers, but only get their full behaviour with the matching plugin version (plugin tags v<version>; 1.1.0 = 2026-09-26, 1.2.0 = 2026-09-28):

| Package | Needs plugin | Relay feature | |---|---|---| | 0.7.x | ≥ 1.1.0 | scrub by target: api/relay-target (server-side servo stops at the timeline centre), relay-pos returns {t, r} | | 0.8.x | ≥ 1.1.0 | jumps with a still picture: relay-seek&mark=1&avoid=W answers {w}; the still is lifted on the first frame of that width (older servers: timer) | | 0.9.x | ≥ 1.1.0 | speed buttons in place: api/relay-speed (older servers: a seek to the displayed position); stills fade out two frames after the video runs | | 0.10.x | any | calendar days instead of ±24 h (DST days are 23/25 h: no duplicate clips/events on 25.10., "next day" works, time picker and axis on the wall clock), SentinelSetup.prefix / sentinelPublicBase(origin, prefix) / exchangeSentinelToken(…, prefix) for a reverse-proxy path prefix. No server change needed. | | 0.11.x | any (storage warning: ≥ 1.1.0) | robustness: the chosen speed survives a new relay session (pause/play, tab switch, recovery), control/seek/poll requests time out, answers of a finished session are ignored, a camera switch stops the old stream at once, forced TURN relay only for 10 min, live watchdog (frozen WebRTC → restart → fallback, WebRTC retried out of a fallback with back-off), MJPEG retry, telemetry rate limit, keyboard shortcuts leave modifiers/buttons alone, fallback transport shown, second deep link to the same camera works, storage-unavailable warning in the hero (stats.storageOk). sideEffects keeps the CSS, NOTICE.md shipped, react/lucide-react optional peers. | | 0.12.x | any | +15 s at the recording edge goes live; 0.12.1: open telemetry per camera opening. | | 0.13.x | any (smaller pictures: ≥ 1.2.0; older plugins ignore w= and send full-size snapshots) | opening a camera: the tile's loaded picture is the poster at once (tiles load with crossOrigin="anonymous", rememberTileSnapshot exported for host cards), live starts without waiting for the day loads (a failed load no longer blocks it), tiles ask for api/snapshot&w=640/960, the fresh poster for 1280. 0.13.1: an MJPEG fallback taken before the codec was known switches to MSE-live once the day loads bring it — client only. | | 0.14.x | any | Picture-in-Picture note + "Open in Safari" in iPhone/iPad Home-Screen apps (Apple blocks PiP there); PlayerController.pip() resolves its outcome. Client only. | | 0.15.x | any (priority order from the server: ≥ 1.3.0; older servers are sorted client-side) | event classes by priority (a cyclist is a person), class filters hide an event only when all its classes are off, chips count every contained class — client only. | | 0.16.x | any (spans: ≥ 1.3.0) | events as time spans: bar from first sighting to last movement on the timeline, running events pulse, duration/„running“ in the list (endTs/open); older servers show plain markers. |

Lab note: Scrypted's WebRTC sink re-encodes (1280 px, 15 fps) for clients that are not Windows/macOS/iOS and report a screen below 1920 physical pixels, unless the source declares a width ≤ 1280. Plugin ≥ 1.2.0 declares it for recorded playback, so the sink passes it through; for lab measurements still emulate screenWidth/screenHeight (headless Chromium reports 800×600).

The server keeps its own types; docs/API.md in the plugin repository is the contract both sides follow.

Design contracts (for ui)

  • Theming: components read only --* CSS custom properties (HAPulse token names). applyTheme(identity, mode, accentHue) sets them on <html>; hosts that already define the tokens (HAPulse) skip it. Fonts and radii are static host tokens.
  • i18n: components take a t(key, vars) + locale via SentinelUiProvider. ui ships the nvr.* dictionaries (@sentinel-nvr/web/ui/locales/<code>.json); hosts merge them.
  • Camera page: hosts render <CameraPage …/> with header (their page header, e.g. <CameraTitle/> + actions) and renderDatePicker (their modal around useDatePicker). Everything visible inside comes from the package, so both consumers look identical. Optional externalUrl = this camera outside the app (Sentinel's public entry, no token): offered as "Open in Safari" when an iPhone/iPad Home-Screen app refuses Picture-in-Picture.

Develop

npm install
npm run typecheck
npm test
npm run build

Releases: bump version in package.json, add a CHANGELOG.md entry, merge, tag v<version> on main and push the tag. The release workflow publishes from the tag via npm Trusted Publishing (no stored token; typecheck, lint, format check, tests in three time zones, build, pack check, npm publish --provenance, registry tarball compared with dist). A manual run with dry checks a tag without publishing. The maintainer's snvrweb-publish.sh <version> remains the fallback; it needs an npm token, so with "disallow tokens" set in the package's npm settings tokens have to be allowed again for that publish. prepublishOnly refuses any publish without the matching tag.

License

MIT — see LICENSE and NOTICE.md.