@dodomain/connect

中文文档
0.3.2发布于 2026/08/20MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 1.4万

Official browser widget for DoDomain — opens the hosted domain-connect flow in a modal iframe and relays its lifecycle events.

原始使用文档
文档目录8 节
  1. Install
  2. Usage
  3. API
  4. showDoDomain(options) → { close }
  5. onError — DoDomainWidgetError
  6. onClose — DoDomainCloseDetail
  7. Origins & CSP
  8. Hosted-URL fallback

@dodomain/connect

Embeddable browser widget for DoDomain. Opens the hosted connect flow in a modal iframe and relays its lifecycle events back to your app. Pairs with @dodomain/node, which mints the session token on your server.

📖 Guide with runnable examples: dodomain.io/docs/widget · all docs: dodomain.io/docs. Using React? @dodomain/react wraps this widget in a hook.

Install

npm install @dodomain/connect
# or: pnpm add @dodomain/connect · yarn add @dodomain/connect

Zero runtime dependencies, ~3KB browser bundle (dual ESM+CJS + self-contained .d.ts; the bundle stays zod-free — see test/build.smoke.test.ts).

Usage

import { showDoDomain } from "@dodomain/connect";

// token comes from your server: POST /api/v1/sessions via @dodomain/node
const { token } = await fetch("/my-api/domain-session").then((r) => r.json());

const handle = showDoDomain({
  token,
  onVerified: ({ domain }) => {
    console.log("connected:", domain);
    location.reload();
  },
  onClose: ({ state }) => {
    // "verified" | "pending" | "failed" | "unknown" — closing now tells you
    // something, so you don't have to re-poll your own backend to find out.
    if (state !== "verified") keepTheConnectDomainPromptVisible();
  },
  onError: (err) => {
    if (err.code === "MOUNT_BLOCKED") {
      // The iframe never mounted (host CSP, network, content blocker).
      // Same session, full page — see "Origins & CSP" below.
      location.assign(err.hostedUrl);
      return;
    }
    console.error("connect flow failed:", err.type, err.code);
  },
});

// handle.close() to dismiss it programmatically

API

showDoDomain(options) → { close }

OptionTypeNotes
tokenstringRequired. Session token (dd_sess_…) from POST /api/v1/sessions.
baseUrlstringDoDomain origin. Defaults to https://app.dodomain.io.
onVerified(detail: { domain?: string }) => voidFires when the domain verifies.
onClose(detail: DoDomainCloseDetail) => voidFires when the modal is dismissed (backdrop click, in-flow close, handle.close()), with the session's last-known state — see below. A zero-arg () => … handler written against an older version keeps working.
onError(detail: DoDomainWidgetError) => voidFires when the flow fails to load or reports a session error — see below. Absent by default (previously: silent).
loadTimeoutMsnumberHow long to wait for the hosted flow's load handshake before treating the embed as failed. Default 15000.

Returns a handle with close(). Messages from the iframe are origin-checked against baseUrl, so only the hosted flow can trigger callbacks. Must run in a browser.

onError — DoDomainWidgetError

A cross-origin iframe's HTTP 404/500 doesn't fire onerror or expose readable content, so a broken embed used to be entirely silent. Every onError detail carries a code and a hostedUrl (the full-page flow for the same session), plus a type:

  • { type: "load-timeout", code: "MOUNT_BLOCKED" } — no load handshake arrived within loadTimeoutMs (a 404, DNS failure, offline network, content blocker, or a host-page CSP — anything that never gets far enough to run the hosted flow's own JS).
  • { type: "load-error", code: "MOUNT_BLOCKED" } — a faster signal for the same fact: the iframe's own error event fired, or your page's CSP reported blocking the frame (securitypolicyviolation on frame-src/child-src/default-src), which lands in milliseconds instead of waiting out loadTimeoutMs.
  • { type: "session-error", code: string } — the hosted page loaded and reported a failure. code matches the same vocabulary the hosted page's own in-page error banner uses (expired, not_found, invalid_request, internal). Two producers: a verify() call failing mid-flow, and — since 2026-08-18 — a token that was already dead when the sheet opened (expired / not_found), which previously reached you as a misleading load-timeout instead. Treat this one as terminal: close the sheet and mint a fresh session rather than retrying the same token.

code === "MOUNT_BLOCKED" is the one check you need for "the embed is impossible here": the sheet never came up, so nothing the user does inside it can succeed — send them to err.hostedUrl instead. It is deliberately not called CSP_BLOCKED: from the parent page a CSP block, a DNS failure and an ad blocker are indistinguishable, and all four want the same fallback.

onClose — DoDomainCloseDetail

onClose receives { state, domain? }, derived from the flow's own postMessage traffic, so a dismissal is informative instead of ambiguous:

stateMeaning
verifiedThe flow reported the domain verified (domain is set). Sticky — a later error can't downgrade it.
pendingThe flow mounted, but the user closed it before any outcome.
failedThe flow reported an error (e.g. a verify() failure) and never verified.
unknownThe widget never heard from the flow at all — pair this with a MOUNT_BLOCKED onError.

The signed connection.verified webhook remains the source of truth; state is a UI cue, and anything in a browser can be spoofed.

Origins & CSP

The widget loads the hosted flow in an iframe from one origin, so a Content-Security-Policy on your page must allow it in frame-src (browsers fall back to child-src, then default-src, so allow it in whichever of those you actually set):

Content-Security-Policy: frame-src https://app.dodomain.io;
  • https://app.dodomain.io is the production origin — the value of DODOMAIN_DEFAULT_ORIGIN (packages/core/src/origin.ts), which is what baseUrl defaults to and the single place this repo defines it. api.dodomain.io and connect.dodomain.io are cosmetic names for the same deployment and are not served today — don't allowlist them.
  • If you pass your own baseUrl (self-hosted or staging), allowlist that origin instead.
  • The widget injects no scripts, styles, fonts or images into your page, so frame-src is the only directive it needs. It does listen for your page's own securitypolicyviolation events to detect a block early — a read-only listener, nothing is reported anywhere.

Hosted-URL fallback

If the frame can't mount, fall back to the same session full-page — no second API call, the token is already yours:

showDoDomain({
  token,
  onError: (err) => {
    if (err.code === "MOUNT_BLOCKED") location.assign(err.hostedUrl); // {baseUrl}/connect/{token}
  },
});

hostedUrl deliberately omits the embed/origin/theme query params the iframe carries: those put the flow into embedded mode, which is wrong for a top-level navigation. Pass returnUrl when you mint the session so the user lands back in your app afterwards.

See src/index.ts for the implementation.

版本与发布记录

@dodomain/connect 共发布 6 个版本,首次发布于 2026/07/31;过去 12 个月发布了 6 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.2latest2026/08/202 个月前
0.3.12026/08/202 个月前
0.3.02026/08/192 个月前
0.2.12026/08/043 个月前
0.2.02026/08/043 个月前
0.1.02026/07/313 个月前

关于 @dodomain/connect 的常见问题

@dodomain/connect 是什么?

Official browser widget for DoDomain — opens the hosted domain-connect flow in a modal iframe and relays its lifecycle events.

如何安装 @dodomain/connect?

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

@dodomain/connect 支持 TypeScript 吗?

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

@dodomain/connect 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dodomain/connect 可以商用吗?

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

@dodomain/connect 的下载量有多少?

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

@dodomain/connect 还在维护吗?

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