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

@openbrf/display

v0.1.2

Published

The Open BRF screen content contract, the rotation builder, the renderer and the fetch loop that every screen showing an association's content is built from.

Readme

@openbrf/display

What every screen showing a housing cooperative's content is built from, in Open BRF: the screen content contract, the rotation builder, the renderer and the fetch loop. The core's display mode (a browser opened on a display link) and the digital signage module's player both use all four, so a screen shows the association's news, events, the notice of the next general meeting and free/busy availability the same way whichever of them it runs.

It uses no framework and no DOM library, and has one dependency, @openbrf/tokens, for the default theme's colours.

Installing

npm install @openbrf/display

The four parts

  1. The screen content contract. The ScreenContent document, version 1, as types (ScreenContent, Slide, the body kinds), its limits (SCREEN_CONTENT_LIMITS, SLIDE_ID_PATTERN), and parseScreenContent, which reads a document by the player's rules: unknown fields are ignored, a slide of an unknown or malformed kind is skipped, and an unknown version is refused with a ScreenContentError.
  2. The rotation builder. buildRotation turns the news items, occurrences, meeting notices, bookable resources and busy intervals a caller has read, and the moment of the fetch, into the news, events, meeting and availability slides. It reads nothing on its own. availabilityWindow gives the window an availability slide covers: from the minute of the fetch to the end of the next day in the association's time zone.
  3. The renderer. mountRenderer shows a document on an element: the rotation with each slide's seconds and until, the schedule, the clock band, the orientation, the theme and every body kind, in Swedish or English. Nothing a slide holds is cut off: a slide the screen cannot hold whole is shown a page at a time, each page for the slide's seconds. A list, such as eight events, goes as many items to a page as fit whole; a page still too tall is set smaller, and a text too tall even then, such as a news item of fifty paragraphs, continues on the next page, broken between two words, or a word longer than a page between two of its characters. It fetches nothing; a picture comes from a function you hand it. It adds its stylesheet (DISPLAY_STYLESHEET, every class prefixed obrf-display) to the document once, as a <style> element. A page whose content security policy has no 'unsafe-inline' in style-src serves the stylesheet as a file instead: it links the file as <link rel="stylesheet" data-obrf-display href="..."> and passes stylesheet: false to mountRenderer. The renderer then adds no <style> element and sets no style attribute, only properties through the style object, which such a policy allows. A page that has a link or style with data-obrf-display in its head gets no second copy either way.
  4. The fetch loop. startFetchLoop asks for screen content with a device token and acts on each answer: 200 and 304 show content, a 401 with realm="device" and device-token-invalid forgets it and stops, 404 pauses, 429 waits, and anything else shows the last content for at most 24 hours. Where the content is cached is yours to choose.

Usage

import { mountRenderer, startFetchLoop } from "@openbrf/display";

const renderer = mountRenderer(document.getElementById("screen")!, {
  // Fetch the picture with the token in a header, never in its URL.
  picture: async (ref) => {
    const response = await fetch(
      new URL(`pictures/${encodeURIComponent(ref.mediaFileId)}`, contentUrl),
      { headers: { Authorization: `Bearer ${token}` }, credentials: "omit" },
    );
    return response.ok ? URL.createObjectURL(await response.blob()) : null;
  },
  releasePicture: (url) => URL.revokeObjectURL(url),
});
renderer.message("loading", "sv");

startFetchLoop({
  url: contentUrl,
  token: () => localStorage.getItem("token"),
  cache: {
    read: () => JSON.parse(localStorage.getItem("content") ?? "null"),
    write: (entry) => localStorage.setItem("content", JSON.stringify(entry)),
    clear: () => localStorage.removeItem("content"),
  },
  onContent: (content, status) => renderer.show(content, status),
  onRevoked: () => {
    localStorage.removeItem("token");
    renderer.message("link-invalid", "sv");
  },
  onPaused: () => renderer.message("paused", "sv"),
  onDisconnected: () => renderer.message("not-connected", "sv"),
});

Give the element a size, such as the whole viewport: the canvas fills it and is turned by the document's orientation.

Browsers

The renderer and its stylesheet run in Chromium 85, the browser of Samsung's 2021 Tizen displays, without a shim: the package calls nothing newer and writes no CSS newer. A test fails when one of the newer calls or CSS features it lists, such as replaceChildren, Array.prototype.at or inset, is added; anything it does not list is for review to catch. A word longer than a page is broken between characters with Intl.Segmenter where the browser has it (Chromium 87), and between code points where it does not.

Versions

The package carries version 1 of the screen content contract. A field or a body kind may be added within version 1, and an older renderer ignores what it does not know. A change an older renderer would misread raises the contract's version, which is served beside version 1, and the package then carries both.

Documentation

Licence

AGPL-3.0-only, with the Open BRF Module Exception. A player or shell built on this package is a module under that exception and may be licensed on any terms. Both texts are in the package.