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

@terminal3/seal-web

v0.1.2

Published

T3n sealed delivery in the browser - open items sealed to a device-bound key, and fill signed forms whose answers only their named recipients can read, with no runtime dependencies

Readme

T3n sealed delivery for the browser

@terminal3/seal-web is the customer's side of sealed delivery: data her assistant carries but cannot read, in both directions.

  • Out, a sealed item. T3N encrypts something sensitive (card details, a transfer to confirm) to a key that exists only in her browser. The assistant holds a reference (sealed:…); this package fetches the ciphertext through your app and opens it on her device.
  • In, a sealed request. T3N signs a form (what it asks, who may read the answers, where they may go) and makes a form key inside its enclave. This package verifies the signature against your pinned signing key, draws the form, and encrypts her answers to the form key. The assistant only ever learns the status.

The package has no runtime dependencies. It needs WebCrypto, IndexedDB, custom elements and WebAuthn, so it runs in current browsers on a secure origin.

Installation

pnpm add @terminal3/seal-web

Usage

import * as Seal from "@terminal3/seal-web";

Seal.configure({
  rpName: "Example Bank",
  api: {
    // Adds her app session; keep `init.headers`, which carries the JSON content type.
    fetch: (url, init) => fetch(url, { ...init, headers: { ...init.headers, authorization: `Bearer ${token}` } }),
    passkey: "/api/passkey",
    nonce: "/api/seal/nonce",
    bind: "/api/seal/bind",
    item: "/api/seal/items/{ref}",
    confirm: "/api/seal/items/{ref}/confirm",
    describe: "/api/seal/requests/{ref}",
    submit: "/api/seal/requests/{ref}",
    signingKey: "/api/seal/signing-key",
  },
});

await Seal.registerPasskey({ id: customerId, name: customerName }); // once, at sign-in
await Seal.startSession({ sid, nonce });                            // after she allows the assistant
<t3-safebox ref="sealed:…" label="Your card"></t3-safebox>
<t3-safebox-form ref="request:…"></t3-safebox-form>

Importing the package defines both elements. Call Seal.reset() at sign-out and when she revokes the assistant.

configure

configure({ api, rpName, rpId?, shadow? }) throws a SealError with code invalid_config, and keeps the previous configuration, if api or rpName is missing, or any part is mistyped or unknown.

| Option | | | --- | --- | | api | One endpoint per call, below. | | rpName | Your name, shown in the passkey prompt and on both elements. | | rpId | The passkey relying party ID; the page's host name by default. | | shadow | "closed" (default) hides the elements' shadow roots from element.shadowRoot and the page's selectors; "open" lets a test drive them. Neither mode is a boundary against a script running on the page, which can instrument the page before this package runs. |

Each endpoint is a URL or path. The four that act on one item or request carry {ref}, which is replaced by the URL-encoded reference without its sealed: or request: prefix. POST bodies are JSON. An error answer should be a JSON object with error (a code) and optionally message.

| Entry | Method | Takes | Answers | | --- | --- | --- | --- | | fetch | | (url, init) | A Response. Optional; fetch by default. | | passkey | GET | | { credential_id } of her registered passkey, read before each passkey prompt; credential_id is null when she has none | | passkey | POST | { nonce, registration }, below | | | nonce | POST | { purpose: "register" } | { nonce }, single-use, for her signed-in session | | nonce | POST | { purpose: "bind" } or { purpose: "get" } | { nonce, sid }, single-use for that purpose | | bind | POST | { seal_jwk, nonce, assertion } | | | item | POST | {}, or { nonce, assertion } after a fresh unlock | { jwe, label? }; error fresh_unlock_required asks for an unlock | | confirm | POST | {} | | | describe | GET | | { description }, the form's compact JWS; error already_submitted once answered | | submit | POST | { jwe } | { status, fields_present? } | | signingKey | GET | | { jwk }, the public key forms are signed with | | sources | GET | | Optional map of further item endpoints answering { jwe, label? }, chosen by <t3-safebox source="name"> |

Your app server verifies what the browser sends and relays each call to T3N; the browser never talks to T3N directly.

Keys and passkeys

  • Session key. startSession makes a P-256 ECDH key pair with WebCrypto as non-extractable, so the private key never exists outside the browser's key store. It lives in IndexedDB for the session (10 minutes idle, 30 minutes at most).
  • Registration. registerPasskey runs a WebAuthn registration ceremony over a register nonce, with user verification required. Its challenge is SHA-256 of the JCS of { t: "t3n-seal/register", nonce }. It excludes her registered credential, so a device that holds it makes no second one. It posts { nonce, registration }, where registration is { credential_id, clientDataJSON, attestationObject, authenticatorData?, publicKey?, publicKeyAlgorithm?, transports }: the binary members base64url, publicKey the SPKI and publicKeyAlgorithm its COSE algorithm where the browser reports them. Your IdP consumes the nonce, then checks that clientDataJSON has type webauthn.create, that challenge and your origin, and that the authenticator data has your RP ID hash and the user-present and user-verified flags. It stores the credential ID and public key from the attestation's authenticator data; publicKey must match them. Attestation is none, so the ceremony proves freshness and the origin, not the authenticator: accept a registration only within a fresh sign-in.
  • Binding. Her passkey binds the session key's public half to her consent: an assertion over SHA-256 of the JCS (RFC 8785) of { t: "t3n-seal/bind", kid, sid, nonce }. Your IdP verifies it against the registered passkey and only then puts seal_jwk in the assistant's delegation token.
  • Fresh unlock. An item that asks for one (a card number) opens only after another assertion, over { t: "t3n-seal/get", ref, sid, nonce }.

Formats

  • Compact JWE (RFC 7516), alg ECDH-ES, enc A256GCM, over P-256: one-round Concat KDF with empty apu/apv, the base64url protected header as the AES-GCM additional data, and the recipient key's RFC 7638 thumbprint as kid. Headers carry ref, purpose and optionally exp (Unix seconds).
  • A form's description is a compact JWS, ES256, typ t3n-seal-form+jws, verified over the exact bytes received against the key your signingKey endpoint answers. That key's kid is <tid>.<generation>, and the description's header must name the same kid. Submitted answers carry the description's SHA-256 as fp.
  • Every public key received is checked to be on P-256. A kid named by any key but the signing key must be its own thumbprint.

Functions

| Function | | | --- | --- | | registerPasskey({ id, name }) | Registers her passkey on this device, unless it already holds her registered credential; answers the credential ID. | | startSession({ sid, nonce }) | Makes and binds a session key; answers its public JWK. | | ensureSession() | Binds a session key with a fresh nonce if hers is gone. | | hasSession(sid?), reset() | Whether a live session key is bound; delete it. | | openItem(ref, source?) | Fetches and opens a sealed item: { content, meta }. | | open(jwe, ref?) | Opens a JWE with the session key. | | confirmItem(ref) | Confirms a sealed item. | | describe(ref), submit(ref, description, answers) | Verifies a form's description; seals and submits her answers. | | thumbprint(jwk) | RFC 7638 thumbprint of a P-256 public JWK. |

Every refusal is a SealError with a stable code and a message fit to show her. The format refusals are invalid_key, jwe_malformed, header_mismatch, jwe_not_authentic and expired; others include no_session, wrong_reference, not_signed and not_configured. A passkey prompt that is refused or fails gives passkey_cancelled (she dismissed it, or it timed out), passkey_unsupported, passkey_not_allowed or passkey_failed.

Elements

  • <t3-safebox ref="sealed:…" label="…"> shows a sealed item once she opens it, for a minute. auto-open opens it on connection; source="name" fetches it from api.sources.name. An item with a confirm button fires seal-confirmed after the confirm endpoint answers, or seal-confirm (with run_id) for a confirmation your app runs itself.
  • <t3-safebox-form ref="request:…"> draws a sealed request from its verified description, and fires seal-submitted once the answers are sent.
  • Both may be in the page before configure runs: they wait in data-state="waiting" and start once it has. Both render again on each later configure; their shadow root keeps the mode it was attached with.
  • Both reflect their state in data-state, and take their accent colour from the CSS custom properties --seal-accent and --seal-accent-soft.