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

@castwise/ignition

v1.4.0

Published

A tiny, framework-agnostic runtime for building digital signage apps.

Readme

Ignition

A tiny, framework-agnostic runtime for building digital signage "apps" — self-contained widgets (weather, announcements, RSS tickers, image/video players, etc.) that run inside a signage player.

Instead of every app reimplementing config parsing, API caching, and playback lifecycle handling from scratch, an app built on Ignition just calls one function and gets back everything it needs to render.

The problem it solves

A signage app needs to:

  • Read its own per-device configuration (what API to call, how often to refresh, fallback content).
  • Fetch its data, without hammering the network every few seconds.
  • Know when it's actually visible (playing), paused, muted, or about to be skipped, so it can start/stop animations, timers, or media accordingly.

Ignition handles all of this once, so an app only has to worry about rendering.

Core concept

import { init, updateConfig } from "@castwise/ignition";

const app = await init({
  appName: "weather-widget",
  config: { /* app-specific settings */ },
});

// app.data        -> the fetched (or cached/fallback) API data
// app.config       -> the resolved per-device configuration
app.onPlay(() => { /* start rendering/animating */ });
app.onPause(() => { /* stop/freeze */ });
app.onMute(() => { /* ... */ });
app.onUnmute(() => { /* ... */ });
app.onTimeout(() => { /* this app's time slot ended */ });

// Later — a config change arrived (e.g. the host's settings screen), or a
// periodic refresh is due. Same shape as init(), minus the lifecycle hooks
// (the app already has those from init()).
const updated = await updateConfig({
  appName: "weather-widget",
  config: { /* the new settings */ },
});

Ignition never renders anything itself — it only resolves configuration, data, and lifecycle state. Rendering is entirely up to the app.

Receiving config from the host

An app embedded inside a host player (an iframe) doesn't get its config handed to it directly — it has to ask for it over postMessage, since the host and the app usually run on different origins. watchHostConfig handles that handshake:

import { watchHostConfig } from "@castwise/ignition";

const unwatch = watchHostConfig((config) => {
  // Called with the host's config — once on initial load, and again on
  // every later change (e.g. someone editing this app's settings live).
  // Opened directly outside any host (not embedded)? Never called at all —
  // the app should fall back to its own default/demo config in that case.
});

// Call this when the app is torn down, to stop listening.
unwatch();

Internally this sends { type: "APP_READY" } to the parent window once, then listens indefinitely for { type: "DFM_CONFIG", config } messages — it does not resolve once and stop, since a host can push an updated config at any point after the initial handshake, not just at startup.

Data and offline

init() and updateConfig() fetch an app's data from the api block of its config, { url, cacheKey, maxAgeMs, fallbackData, timeoutMs }:

  1. A saved copy younger than maxAgeMs is used straight away, with no network call.
  2. Otherwise the URL is fetched (given up on after timeoutMs, 10 seconds by default) and the result is saved.
  3. If the fetch fails (no internet, timeout, bad response), the last saved copy is used however old it is; fallbackData only if nothing was ever saved.

So a screen that has lost its internet keeps showing the last weather or news it had. Images, videos and the app bundle itself are not Ignition's job: the player page saves those (see the CastWise README, "Offline screens").

Terms

  • App — a self-contained signage widget built on top of Ignition.
  • Config — per-device/per-deployment settings for one app instance (API endpoint, refresh interval, fallback data).
  • Payload — the object init() resolves with: config + data + lifecycle hooks, everything an app needs to render.
  • Lifecycle event — play / pause / mute / unmute / timeout — signals from the host player about this app's current state.

Design principles

  • Functional, not class-based. No new, no this — everything is plain functions and closures.
  • One environment. Built for the browser. No native-bridge detection, no dual-transport abstraction — if it ever needs to run somewhere else, that's a separate adapter, not baked into the core.
  • Fail soft. A failed API call falls back to the last saved data (however old), then to static fallback data, instead of leaving the app with nothing to show. A fetch that never answers is given up on after a timeout.

Install

npm install @castwise/ignition

Version

import { VERSION } from "@castwise/ignition";

Kept in sync with package.json on every publish — apps use this to report which Ignition build they're actually running against, e.g. in a startup diagnostic log.

Testing

Tested with Jest, run in ESM mode (this package uses native import/ export, not CommonJS).

npm test
npm test > test-results.txt 2>&1

One test file per source file, co-located under src/__tests__/:

| File | What it covers | |---|---| | logger.test.js | log/warn/error fire the right console method, and stay silent when enabled: false. | | lifecycle.test.js | Registered callbacks fire on the matching trigger() event, don't fire on a different event, and multiple callbacks on the same event all run. | | cache.test.js | A fresh cached value round-trips correctly, a never-set key returns null, an expired (older than maxAgeMs) value is treated as a miss, and getStale still returns that expired value. | | fetchData.test.js | A successful fetch returns and caches its data; a valid cache hit skips the network call entirely; a thrown error or a non-ok response falls back to fallbackData instead of throwing; when a saved copy exists (even an expired one) it is returned instead of fallbackData; a fetch that never answers is given up on after timeoutMs. | | hostConfig.test.js | Not embedded: onConfig is never called. Embedded: posts APP_READY, calls onConfig on every DFM_CONFIG message (not just the first), ignores other message types, and the returned unsubscribe actually removes the listener. | | config.test.js | buildConfig assembles appName/config/data correctly from its inputs. | | index.test.js | init() end-to-end: resolves to fallbackData with no api configured, returns working lifecycle methods, and trigger() actually fires a registered callback. |

License

MIT