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

@fluid-app/droplet-bridge

v0.1.11

Published

Browser SDK for Fluid embedded app session tokens and context.

Readme

@fluid-app/droplet-bridge

Canonical browser client for embedded droplets.

Developer guide: docs/droplet-bridge-developer-guide.md.

import { createFluidBridge } from "@fluid-app/droplet-bridge";

const bridge = createFluidBridge({ clientId, hostOrigin });
const context = await bridge.getContext();
const token = await bridge.getSessionToken();
await fetch("/api/embedded-action", { method: "POST" });
// Use disableFetchPatch: true to opt out of ambient fetch.
await bridge.fetch("/api/embedded-action", { method: "POST" });
// Component teardown, or explicit shutdown:
bridge.destroy();

hostOrigin is the exact HTTP(S) host-shell origin supplied by the shell. The client accepts only that origin and window.parent. It installs its listener before sending UUID-v7 bridge.ready, retries at most eight times with bounded backoff until context arrives, and rejects an incomplete handshake after ten seconds by default. Protocol or SDK mismatches fail closed with typed errors. Only one client may own a document at a time; destroy it before creating another. A real navigation's pagehide (persisted: false) and unload destroy it automatically. A pagehide into the back/forward cache (persisted: true) leaves the bridge intact — the document is frozen, not gone — and pageshow on restore resumes an unresolved handshake; an already-resolved one keeps working unchanged.

getContext() resolves the immutable, unsigned Rails context delivered at handshake; it never fetches context on demand. getSessionToken() requests a fresh identity-only token over session_token.refresh_request every time, coalescing concurrent callers onto one in-flight request. The SDK schedules its own refresh from the token's lifetime relative to receipt, never from comparing exp against this client's clock — an admin's local clock can run well ahead of the host's, and a short-lived token must not be rejected, or refreshed every second, because of it. Decoding is for delivery and scheduling only: the droplet backend verifies signatures, identity and expiry per the verification contract. The SDK uses in-memory delivery binding against app/store/actor/entry/sid and ignores replies for unknown or previous-document request IDs.

Explicit and ambient fetch authenticate only exact origins in the server-delivered fetch.allowedOrigins. There is no iframe appOrigins option. Fluid API origins are excluded first, even if accidentally listed. Ambient fetch also passes a request through untouched — never waiting on the handshake — whenever the handshake has failed, or is still pending and the request targets an origin other than the current document's own. Explicit disallowed requests reject; ambient disallowed requests pass through with their original input/options — including a reference to the ambient wrapper captured before destroy(), for every cross-origin call made through it afterward; a same-origin call through that same reference keeps failing closed. Allowed requests preserve Request bodies, headers and credentials, replace Authorization with the fresh session token, and send the viewer's active language as Accept-Language from store.locale. Missing locale fails closed. Relative request URLs resolve against the document's current location, read at call time, so an in-document route change is honored. Explicit bridge fetch refuses redirects with redirect: "error" — browsers strip the Authorization header across a cross-origin redirect, so forwarding it would be silently insecure — and converts HTTP 401/403 into a typed error. Ambient fetch keeps the caller's own redirect mode and returns 401/403 as an ordinary Response, since same-origin app code expects to read its own response status. Network failures return typed errors without response bodies or raw exception text on both paths. destroy() restores the prior fetch, cancels timers and rejects dangling requests.

First request and later documents

The URL session_token is first-request server bootstrap only. The SDK strips it immediately with history.replaceState and never uses it as a browser credential. The droplet response must set Referrer-Policy: no-referrer before subresources load; the host iframe’s referrer policy does not protect those subresources. Do not log full bootstrap URLs or persist bridge tokens in localStorage, sessionStorage, cookies or window.name.

A full-document navigation inside the iframe keeps the droplet’s current route. Its server must treat any URL token as absent after bootstrap, use its own short HttpOnly app session established from verified first-load identity, or render a bridge-first recovery page. That recovery page initializes this SDK and sends a fresh bridge token to its own backend for verification. A stale URL token must not hard-fail recovery. Every new document initializes the SDK again and obtains fresh context/tokens over postMessage. The host must not rewrite the frozen iframe src to inject another bootstrap token, because it would reset the droplet’s route. An app session cookie stores the app’s own bounded session, never the Fluid bridge token.

Canonical host

createCanonicalBridgeHost uses the shared versioned protocol. The authenticated shell supplies the registered app origin and fixed mount: client, store, actor, installation, entry and sid. Create sid before the first URL mint and reuse it for the same mount. Store/actor/installation/entry/resource changes destroy the host and iframe and create a new mount; document navigation within the same mount preserves src and sid.

Every valid bridge.ready gets current permission-gated server context and a fresh mint, including after the first document. A retry of the same in-flight readiness ID coalesces; a new document ID proceeds even while older work is pending. Readiness and refresh each have their own bounded pending pool, so a storm of refreshes can never cost a new document's bridge.ready a host_unavailable. The client ignores old correlations, binds refreshes to its delivered sid, and coalesces concurrent getSessionToken() callers onto a single in-flight refresh request. Both host adapters must honor AbortSignal and perform uncached authorized requests. Refresh rechecks context and SDK compatibility before minting. Refresh also refuses to mint, leaving the document's existing context and token in place, if an origin ever delivered on this mount has since been removed from the live authorized context — the removal is compared against the union of every origin set delivered by any bridge.ready on this mount, never against a value a prior refresh already advanced, so an earlier removal cannot hide behind a later addition. That baseline is monotonic for the whole mount, not reset by a later bridge.ready: the host cannot reliably tell a new document's readiness from a duplicate or retried one from the same document (an SDK retry race, or a bfcache pageshow resend), so a duplicate ready is never allowed to erase a prior removal. An origin removed mid-session therefore stops refreshing tokens for the rest of that mount's life, even for a document that attaches after the removal; reload the admin page to remount the iframe and pick up the new context. Adding an origin mid-session refreshes normally. Neither adapter trusts iframe identity or origin declarations. createFluidBridgeHost remains the legacy pull host used by the portal until its separately specified member tier and cutover land.

Session tokens are identity assertions, never Fluid API credentials. Backend API access uses the existing OAuth client_credentials flow with a store resource and installation-scoped permission enforcement. Navigation and shell UI actions are outside this client’s token/context scope.