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

@declarion/embed

v0.12.4

Published

Host integration SDK for embedding Declarion screens as white-label iframes.

Readme

@declarion/embed

Host integration SDK for embedding Declarion screens as white-label iframes in a third-party app.

A Declarion screen route renders without Declarion shell chrome when loaded with ?embed=1. The host app owns the outer shell, navigation, and identity; Declarion owns screen rendering, data access, and tenant isolation. This package is the typed integration layer for both sides:

  • Browser core (@declarion/embed) - dependency-free. Creates the iframe, runs the auth handshake, auto-resizes, mirrors navigation.
  • React binding (@declarion/embed/react) - <DeclarionEmbed />.
  • Server helper (@declarion/embed/server) - createEmbedSession, the Node-side mint call. Keeps the dk: API key out of browser code.

The host installs only @declarion/embed; it does not depend on @declarion/react (the full Declarion UI SDK).

Install

npm install @declarion/embed

react and react-dom are optional peer dependencies, required only by the @declarion/embed/react entry.

How embedding works

  1. The host backend mints a short-lived, scoped token for a target tenant and user with createEmbedSession (@declarion/embed/server), which calls the auth.create_embed_session action with a server-held dk: API key.
  2. The host frontend embeds a screen with createDeclarionEmbed or <DeclarionEmbed />, passing a getToken callback that fetches a token from the host backend.
  3. The SDK runs a postMessage handshake: the iframe asks for a token, the SDK delivers it, and refreshes it whenever the iframe reports expiry.

The dk: API key never leaves the host backend. For multi-tenant hosts, use a least-privilege dk:_global key whose service-account owner holds only action:auth.create_embed_session; tenant-scoped keys can mint only in their own tenant.

Token model and the persistent iframe

The embed token is minted per (user, tenant) and carries that user's full effective permissions. ONE token serves every screen the user can open - it is NOT per screen. Mint it once per identity and reuse it; getToken should be bound to the identity, not the current screen.

Switching screens is fast because the iframe is persistent: changing the route (the route prop, or handle.navigate(route)) posts a soft SPA-navigation to the existing iframe. The app keeps its loaded schema and session - it does NOT reload or re-fetch the schema per screen. The first load fetches the schema once; later screen switches are frontend-speed.

Changing the embedded identity (a different user or tenant) MUST rebuild the iframe, because the in-iframe schema cache is authority-shaped. With the React binding, pass a stable identityKey (e.g. `${tenant}|${user}`); changing it rebuilds the iframe and re-mints. With the core, create a new embed for the new identity. Per-screen access is enforced inside the iframe at navigate time, never by narrowing the token.

Quickstart: browser core

import { createDeclarionEmbed } from "@declarion/embed";

const embed = createDeclarionEmbed({
  container: document.getElementById("declarion")!,
  declarionOrigin: "https://app.example.com",
  route: "/cases",
  getToken: async () => {
    const res = await fetch("/host-api/embed-token");
    return res.json(); // { token, expires_at }
  },
  onError: (err) => console.error(err.code, err.message),
});
// Later: embed.navigate("/cases/42"); embed.setTheme("dark"); embed.destroy();

Quickstart: React

import { DeclarionEmbed } from "@declarion/embed/react";

export function CasesPanel({ tenant, user, route }) {
  return (
    <DeclarionEmbed
      declarionOrigin="https://app.example.com"
      route={route}
      // Stable per identity. Changing `route` soft-navigates the SAME iframe;
      // changing `identityKey` rebuilds it (a different user must not reuse the
      // prior user's authority-shaped schema).
      identityKey={`${tenant}|${user}`}
      getToken={async () => (await fetch("/host-api/embed-token")).json()}
      onError={(err) => console.error(err.code, err.message)}
    />
  );
}

Quickstart: server helper (Node)

import { createEmbedSession } from "@declarion/embed/server";

// In a host backend route. The dk: API key is read from server config.
const session = await createEmbedSession({
  declarionOrigin: "https://app.example.com",
  // Prefer a least-privilege dk:_global key whose owner holds only
  // action:auth.create_embed_session. Keep it server-side only.
  apiKey: process.env.DECLARION_EMBED_API_KEY!,
  tenantCode: "acme", // target tenant
  userEmail: "[email protected]",
  screenCode: "cases_list",
});
// Return `session` ({ token, expires_at }) to the host frontend's getToken.

Hosts on a non-Node backend call the POST /api/actions/auth.create_embed_session action over HTTP directly with the same dk: API key in the Authorization header.

Script tag (no build step)

The package also ships an IIFE bundle of the core. Load it and use window.DeclarionEmbed.createDeclarionEmbed:

<script src="https://unpkg.com/@declarion/embed/dist/declarion-embed.iife.js"></script>
<script>
  DeclarionEmbed.createDeclarionEmbed({ /* options */ });
</script>

Navigation modes

  • navigation: "self" (default) - the iframe routes EVERY in-frame link internally (the token is identity-scoped, so the iframe is already authorized to open anywhere the user can go) and emits onNavigate({ mode: "self", route, screenCode, ... }) so the host can mirror its URL.
  • navigation: "delegated" - the iframe never moves on an in-frame link; it emits onNavigate({ mode: "delegated", route, screenCode, ... }) and the host decides what to open (drive it back via the route prop / handle.navigate). Use this to pin the embed to a single screen.

The onNavigate event carries { mode, route, screenCode?, entity?, recordId? }.

A host-driven navigate (the route prop changing, or handle.navigate) is a deep-link command: it MOVES the iframe in BOTH modes (it is the host asking), as a soft SPA navigation - only in-frame LINKS are intercepted/delegated. The same token authorizes every screen, so no re-mint is needed to switch.

Navigation failures

When an in-frame navigation cannot be handled - an unknown route, or a 404 - the iframe stays put and emits onNavigationFailed({ route, reason }) (reason is not_found or unresolved). The host owns recovery: show its own not-found, or reset its sidebar selection. Never break the frame.

createDeclarionEmbed({
  // ...
  onNavigationFailed({ route, reason }) {
    hostRouter.showNotFound(route, reason);
  },
});

Iframe sandbox

The SDK sets a sandbox on the iframe by default that runs the full app but omits the top-navigation tokens, so the framed app cannot navigate the host's top window. Widen it with the extraSandbox option if needed - the top-navigation tokens are stripped from it and can never be re-added. Keep your CSP frame-ancestors configured to control who may frame the app.

Unsaved changes

A Declarion screen can hold unsaved edits. When the host navigates the iframe away from such a screen - via its own menu, or handle.navigate() - it should confirm with the user first. The iframe reports its edit state through the onEvent callback:

let embedDirty = false;
createDeclarionEmbed({
  // ...
  onEvent(event) {
    if (event.type === "dirty-changed") embedDirty = event.payload.dirty;
  },
});

// Before the host moves the iframe away:
if (embedDirty && !confirm("Discard unsaved changes?")) return;

Navigation inside the iframe (a row click, a Back button) AND a host-driven route change / handle.navigate are both already guarded by Declarion: the iframe shows its own confirm dialog before discarding edits. The dirty-changed event is for host actions the iframe CANNOT intercept - the host leaving the embed via its own page navigation, or replacing the embed on an identity switch. Use it to guard those.

Staying current while embedded

When the Declarion deployment is redeployed while a screen is embedded, the iframe does NOT show Declarion's own reload chrome (that would be wrong inside a white-label host). Instead it asks the host to reload it via a reload-required event (surfaced through onError with code reload-required and through onEvent). The host decides: reload the iframe, show its own banner, or ignore. The persistent iframe otherwise keeps its loaded schema across screen switches and only re-validates it on a real version change or SSE session refresh.

Diagnostics

A misconfiguration is surfaced loudly through onError (a typed EmbedError with a stable code) and console.error. Untrusted cross-origin postMessage frames are dropped silently. The SDK detects:

  • invalid-options - a required option is missing or malformed.
  • get-token-failed - getToken rejected or returned a malformed result.
  • handshake-timeout - no handshake within the post-load window, usually a declarionOrigin mismatch or framing denied by the Declarion CSP (DECLARION_FRAME_ANCESTORS does not list the host origin).
  • reload-required - the iframe asked the host to reload it.

A protocol version mismatch between this SDK and the Declarion deployment produces a clear console.warn naming both versions.

Putting it together

A real integration is two halves, and the snippets above are the whole contract:

  • Host frontend - renders <DeclarionEmbed /> (React) or calls createDeclarionEmbed (any framework) and supplies the async getToken callback. The SDK owns the iframe, the ready -> set-token handshake, resize, navigation, and token refresh.
  • Host backend - holds the dk: API key (server-side only) and mints short-lived, scoped embed tokens with createEmbedSession from @declarion/embed/server. Your getToken callback fetches one from your own backend endpoint.

The dk: key never leaves your server; the browser only ever holds a short-lived, scoped embed token. That split is the entire security model - keep @declarion/embed/server out of any browser bundle.

License

MIT