@declarion/embed

中文文档
0.244.0发布于 2026/11/12MITTypeScriptES Module零依赖近 30 天下载 1.9万

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

原始使用文档
文档目录15 节
  1. Install
  2. How embedding works
  3. Token model and the persistent iframe
  4. Quickstart: browser core
  5. Quickstart: React
  6. Quickstart: server helper (Node)
  7. Script tag (no build step)
  8. Navigation modes
  9. Navigation failures
  10. Iframe sandbox
  11. Unsaved changes
  12. Staying current while embedded
  13. Diagnostics
  14. Putting it together
  15. License

@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 dcl_ 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 dcl_ 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 dcl_ API key never leaves the host backend. For multi-tenant hosts, use a least-privilege dcl_ key 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 dcl_ API key is read from server config.
const session = await createEmbedSession({
  declarionOrigin: "https://app.example.com",
  // Prefer a least-privilege dcl_ 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 dcl_ 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 embed.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:

  • embed.invalid_options - a required option is missing or malformed.
  • embed.get_token_failed - getToken rejected or returned a malformed result.
  • embed.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).
  • embed.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 dcl_ 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 dcl_ 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

版本与发布记录

@declarion/embed 共发布 229 个版本,首次发布于 2026/05/22;过去 12 个月发布了 229 个版本。

版本发布日期距今
0.244.0latest2026/11/12今天
0.241.02026/11/11今天
0.240.02026/11/11今天
0.239.02026/11/11今天
0.238.02026/11/11今天
0.237.02026/11/11今天

关于 @declarion/embed 的常见问题

@declarion/embed 是什么?

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

如何安装 @declarion/embed?

在项目目录运行 npm install @declarion/embed,或使用 pnpm add / yarn add / bun add。

@declarion/embed 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@declarion/embed 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@declarion/embed 可以商用吗?

@declarion/embed 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@declarion/embed 的下载量有多少?

根据 npm 官方下载统计,@declarion/embed 在 2026/10/12 – 2026/11/10 这 30 天内被下载 19,382 次,日均约 646 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@declarion/embed 还在维护吗?

最新版本 0.244.0 发布于 2026/11/12(今天);至今共发布 229 个版本,其中过去 12 个月发布了 229 个;首次发布于 2026/05/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。