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

@ofidj/entry

v3.27.0

Published

The Fidj entry: sign-in, account recovery, the service agreement and the design system, shared verbatim by Fidj, the generated apps and the console.

Readme

@ofidj/entry

The Fidj entry, as a package: the sign-in and account screens, the service agreement, the provider window and the design system. One implementation, shared by Fidj's own console, every generated app and mleweb.

Read the workspace README for the product target and AGENTS.md for the working rules. The flow these screens implement is Entry: one flow, the same everywhere.

Why it exists

These files used to live only in generator-fidj's template, which meant the only way to ship them was to generate an app. Fidj's own console was therefore generated too — fidj-app built as a module, wrapped by the generator, published from .gen/fidj/www — and you could not tell from fidj-app/src what fidj.ovh actually draws. The alternative, keeping a second Angular copy in step by hand, is what produced four unguarded copies of the service agreement.

A package is the third answer: the console imports the same code a generated app renders, so there is one implementation and no hand-porting.

Use it

npm install @ofidj/entry

Three entry points, split by what they need to run:

| Import | Needs | What it holds | | --------------------- | ---------------- | ------------------------------------------------------------------------------ | | @ofidj/entry | nothing | the model: every rule, and every sentence a person is shown, as plain data | | @ofidj/entry/dom | a document | one renderer of that model as HTML strings, and the helpers that drive it | | @ofidj/entry/window | a browser window | the Fidj window and the answer it relays back | | @ofidj/entry/server | a server | the CSP-safe OIDC interaction renderer and its shared password binding |

The root is pure — no DOM, no markup, no view layer. That is deliberate: a screen is a value, so the same entry can be drawn as HTML strings, as React components or as Vue single-file components, and none of them re-derives a rule or re-types a sentence.

On React, Vue, Svelte, or a server

Take the model and render it yourself. Nothing here assumes a browser, so it works under SSR, in a test, or in a plain Node script.

import {
  agreementModel,
  acceptance,
  agreementRequired,
  agreementFromRefusal,
  verificationPending,
  pollVerification,
  signInErrorMessage,
} from "@ofidj/entry";
import { openProviderWindow } from "@ofidj/entry/window";
import "@ofidj/entry/tokens.css";
import "@ofidj/entry/style.css";

function Agreement({ title, agreement, onAccepted }) {
  const [checked, setChecked] = useState(false);
  const screen = agreementModel(title, agreement, { checked });
  return (
    <form
      onSubmit={() =>
        onAccepted(acceptance({ checked, version: screen.version }))
      }
    >
      <h2>{screen.heading}</h2>
      <p className="signin-lead">{screen.lead}</p>
      <p className="fineprint">{screen.versionLabel}</p>
      <div className="agreement-text" tabIndex={0}>
        {screen.text}
      </div>
      <label className="agreement-choice">
        <input
          type="checkbox"
          checked={checked}
          onChange={(e) => setChecked(e.target.checked)}
        />
        <span>{screen.checkboxLabel}</span>
      </label>
      <button className="primary" disabled={screen.submitDisabled}>
        {screen.submitLabel}
      </button>
    </form>
  );
}

acceptance() is the rule @ofidj/entry/dom applies to a checkbox, asked of your own state instead: a tick is evidence only if it was live and only if it names the version that was displayed. Both callers have to get the same answer, or the evidence means different things depending on who collected it.

Authentication itself is not here — that is @ofidj/node. This package is the screens and the flow around them.

On a shell that builds pages out of strings

import { agreementRequired, agreementFromRefusal } from "@ofidj/entry";
import {
  agreementScreen,
  bindAgreementScreen,
  acceptedAgreement,
} from "@ofidj/entry/dom";

tokens.css defines every colour, family and radius the product paints with; nothing else may introduce a literal. fonts.css is shipped separately because each consumer serves its own font files and the url()s differ by one path prefix.

What is here, and what is not

| Module | What it holds | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | model.ts | the refusals and what they mean, the acceptance rule, and a model per screen: entry, agreement, verification wait, account, credentials | | remembered.ts | the address this browser remembers, and forgetting it | | dom.ts | the HTML renderer, password/agreement bindings, the folding form, the fidj@<version> badge, the passkey door and the WebAuthn helpers | | server.ts | the Fidj window (OIDC interaction page) rendered on the server, and its only script | | provider-window.ts | the Fidj window and the answer it relays back | | tokens.css, fonts.css, style.css | the design system |

Two tests hold the split, and they are the reason it stays true. One exercises every model function in a file that never loads jsdom and never defines window or document. The other asserts the HTML renderer says what the model says and invents nothing — because a sentence able to drift between the two is a React app and a generated app telling the same person different things, which is the failure this package exists to end.

Generated shells may own transport-specific form actions and page composition, but they do not own reusable entry components. Credential, signup, recovery and agreement markup rules and their browser bindings live here. When a shell replaces markup dynamically, it binds the newly rendered component through this package rather than reimplementing its behavior locally.

The entry flow

The workspace README defines it; this package implements it, so every surface gets the same one.

import {
  pollVerification, // the wait ends by itself when the link is opened
  agreementRequired, // was this refusal "you owe this app its agreement"?
  agreementFromRefusal, // ...and the agreement it refused with
  verificationPending, // was it "nobody has proved this address yet"?
  credentialsModel, // email, password, two buttons, nothing gated
  verificationWaitModel, // the wait a new account owes its address
  agreementModel, // the second screen, and when its submit is live
} from "@ofidj/entry";
import {
  credentialFields,
  verificationWait,
  agreementScreen,
  bindAgreementScreen,
} from "@ofidj/entry/dom"; // ...the same screens, as markup

The API decides, not the screen. Call login, and when it refuses with 409 agreement_required, show the agreement screen. A screen that decided for itself would have to know which version the account already accepted, which is the API's to know — and an owner publishing a new version is what makes the question owed again.

Read the agreement off the refusal. agreementFromRefusal returns the version and text the API compared against, from FidjError.details. Fetching it again with GET /apps/:appId usually gives the same answer and occasionally not: an owner who publishes between the refusal and the refetch would have somebody accept text the API is about to call stale. It needs @ofidj/node 3.10.2 or later, where login stopped rebuilding its error from err.toString() and losing the body; against an older SDK it returns null and the caller falls back to fetching.

The wait is not a screen. verificationWait returns a block to put under the credential form, which stays where it is. That is what keeps a mistyped address correctable: the field is still there, still filled in — correcting it and creating the account again is the repair, and it reads as one because nothing moved.

pollVerification treats a failed check as no answer. A browser that lost its connection has learned nothing about the address, so the wait continues rather than reporting anything.

The passkey door (3.15.0)

credentialFields(state, {passkey: true}) draws Continue with a passkey first, then "or with your email" and the password form; without the option it draws the form alone. Pass it only where the passkey can run: one relying party, Fidj's own domain, so on Fidj's shell and on the Fidj window, never on another app's own form. walletDoor() draws the EU wallet as a dated, disabled promise.

import {
  passkeySupported, // can this browser run a ceremony at all
  passkeyAssertion, // sign-in: API options in, JSON answer out
  passkeyRegistration, // adding one: creation options in, JSON answer out
} from "@ofidj/entry/dom";

They carry WebAuthn's ArrayBuffers to the API's base64url JSON and back; the HTTP side is the SDK's (passkeyLoginOptions, loginWithPasskey). On the server page, oidcInteractionPage({..., passkey: {ticket, options}}) carries the challenge in the page — its policy allows no request — and the page's script posts the answer as a form with action=passkey. That script is a string in a template literal, where a lone backslash disappears; a test parses it, because a script that does not parse takes every binding on the page down with it.

What went with the old arrangement

agreementMarkup and bindAgreement are gone. They put the checkbox beside the credentials and gated the submit on whether the agreement had loaded, ticked or not; the generated shells both render the two screens now, so nothing called them. agreementScreen and bindAgreementScreen replace them, and the tick is what opens the door.

Release

This package joins the one release series and publishes from ./dist when its package branch is pushed. It sits after @ofidj/contracts and @ofidj/node and before @ofidj/generator-fidj in the dependency chain, because the generator and the console both consume it.