@dodomain/node

中文文档
0.8.0发布于 2026/10/16MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 9146

SaaS——官方域名服务器SDK的自定义域:薄荷连接会话(Cloudflare一击,域名连接,被引导DNS),读取连接,并验证所签名的webhooks.

中文使用文档
文档目录27 节
  1. 安装
  2. 使用量
  3. 管理连接
  4. 回读一个会话
  5. 飞行前域
  6. 管理网点
  7. 旋转您的密钥
  8. 正在校验webhooks
  9. API 密码
  10. new DoDomain(options)
  11. dodomain.sessions.create(input) → Promise<ConnectSessionSum…
  12. dodomain.connections.list(filters?) → Promise<ListConnectio…
  13. dodomain.connections.get(connectionId) → Promise<Connection>
  14. dodomain.connections.delete(connectionId) → Promise<Disconn…
  15. dodomain.connections.reverify(connectionId) → Promise<Rever…
  16. dodomain.sessions.get(sessionId) → Promise<IntegratorSessio…
  17. dodomain.apps.list() → Promise<ListAppsResult>
  18. dodomain.domains.check(input) → Promise<CheckDomainResult>
  19. dodomain.webhookEndpoints.list() → Promise<ListWebhookEndpo…
  20. dodomain.webhookEndpoints.create(input) → Promise<WebhookEn…
  21. dodomain.webhookEndpoints.update(endpointId, input) → Promi…
  22. dodomain.webhookEndpoints.resume(endpointId) → Promise<Webh…
  23. dodomain.webhookEndpoints.delete(endpointId) → Promise<Dele…
  24. dodomain.webhookEndpoints.rotateSecret(endpointId) → Promis…
  25. dodomain.keys.rotate() → Promise<RotateSecretKeyResult>
  26. verifyWebhook(secret, body, header, toleranceMs?, nowMs?) →…
  27. 类型

@ dodomain/ 节点

自定义 SaaS 域名,来自您的服务器: 官方节点/ TypeScript SDK 域名。在 DoDomain REST API 上输入的瘦客户端——薄荷 连接会话 让你的客户通过 Cloudflare OAuth 申请 签名 域连接 在其 DNS 提供者支持的情况下应用 它,或者在其它任何地方的引导记录;然后验证签名的webhooks域名发送一次 记录在权威的命名服务器现场直播. 浏览器 @dodomain/connect 部件驱动前端的会话 .

用你们的 密钥 (单位:千美元)dd_sk_…) (中文(简体) ). 放在服务器上 永远不要把它送到浏览器.

📖 附有可操作示例的指南 : dodomain.io/docs/节点-sdk · 所有文件: dodomain.io/docs (英语)。 。 。 .

安装

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

零运行时间依赖性——构建内含一切(双重无害环境管理+CJS+自成一体) .d.ts. 节点20+.

使用量

import { DoDomain } from "@dodomain/node";

const dodomain = new DoDomain({ secretKey: process.env.DODOMAIN_SECRET_KEY! });

const session = await dodomain.sessions.create({
  domain: "customer.com",
  records: [
    { type: "CNAME", host: "app", value: "cname.sendly.io" },
    { type: "TXT", host: "_verify", value: "sendly-domain-abc123" },
  ],
  returnUrl: "https://dashboard.sendly.io/domains",
});

// session.token   → hand to @dodomain/connect in the browser
// session.connectUrl → or redirect the user to the hosted flow directly

管理连接

// One page of live connections, newest first.
const { connections, nextCursor } = await dodomain.connections.list({ limit: 50 });

// Walk every page (nextCursor is null on the last one).
let cursor: string | undefined;
do {
  const page = await dodomain.connections.list({ cursor, limit: 100 });
  cursor = page.nextCursor ?? undefined;
} while (cursor);

// Read ONE — the id every connection.* webhook payload carries.
const connection = await dodomain.connections.get(connectionId);
connection.status; // "active" | "broken"

// Queue an on-demand DNS recheck; the verdict arrives as a webhook.
await dodomain.connections.reverify(connectionId);

// The customer removed their domain from your product — stop the monitoring
// (idempotent; a repeat reports alreadyDisconnected).
const { disconnectedAt, alreadyDisconnected } = await dodomain.connections.delete(connectionId);

回读一个会话

// Sessions are addressable by the id every webhook payload carries — the token
// never appears in one. This is how a session.abandoned receiver asks what
// actually happened.
const session = await dodomain.sessions.get(event.data.sessionId as string);

session.expired; // derived at read — do NOT infer expiry from session.status
session.connectionId; // non-null once the session finalized
session.records; // the composed fqdns DoDomain verifies, not the raw echo

飞行前域

// Which provider, which zone, which connect flow — without creating a session.
const check = await dodomain.domains.check({ domain: "app.customer.com" });
check.tier; // 1 = Cloudflare one-click · 2 = Domain Connect · 3 = manual
check.oneClickAvailable; // false on tier 2 when the provider serves none of our templates (e.g. GoDaddy)
check.guide.steps; // what to tell the user if it comes to manual records

管理网点

// Register a delivery target. The secret is returned ONCE — store it here.
const endpoint = await dodomain.webhookEndpoints.create({
  url: "https://hooks.sendly.io/dodomain",
});
await saveWebhookSecret(endpoint.secret); // whsec_… — feed to verifyWebhook

const { endpoints } = await dodomain.webhookEndpoints.list(); // never carries secrets
await dodomain.webhookEndpoints.update(endpoint.id, { url: "https://hooks2.sendly.io/dodomain" });

// Rotation cuts over IMMEDIATELY — deploy the new secret before you rotate.
const rekeyed = await dodomain.webhookEndpoints.rotateSecret(endpoint.id);
await dodomain.webhookEndpoints.delete(endpoint.id);

旋转您的密钥

// The calling key replaces itself. By DEFAULT there is NO overlap window: the
// old key stops working the instant this returns, and this is the only copy of
// the new one — persist it before doing anything else.
const rotated = await dodomain.keys.rotate();
await saveSecretKey(rotated.secretKey); // dd_sk_…
const next = new DoDomain({ secretKey: rotated.secretKey }); // this client still holds the old key

// Opt-in zero-downtime rotation: both keys work until previousKeyExpiresAt
// (1 or 24 hours). Exactly ONE previous key is kept — rotating again replaces
// it (killing key n-1 instantly), and rotating with the default overlapHours: 0
// ends a live window early: for a leaked key, rotation IS the revoke.
const overlapped = await dodomain.keys.rotate({ overlapHours: 1 });
await saveSecretKey(overlapped.secretKey);
console.log(`old key dies at ${overlapped.previousKeyExpiresAt}`);

正在校验webhooks

import { verifyWebhook } from "@dodomain/node";
import type { WebhookEvent } from "@dodomain/node";

// req.headers["x-dodomain-signature"] is `t=<unixMs>,v1=<hex hmac>`
const ok = verifyWebhook(process.env.DODOMAIN_WEBHOOK_SECRET!, rawBody, signatureHeader);
if (!ok) return new Response("bad signature", { status: 400 });

const event = JSON.parse(rawBody) as WebhookEvent; // { id, type, occurredAt, data }
// Dedupe on event.id (retries reuse it); branch on event.type.

这是拼接器唯一需要的包: 它覆盖整个 /api/v1 整合器表面—— 会话,连接,应用,域名飞行前,webhook-endpoint生命周期和密钥旋转 - 加上 webhook 签名验证.

其余部分 /api/v1 路线(detect, (中文(简体) ), verify,和 Cloudflare / 域连接 一击入点)为 浏览器 表面:这些表面由会话符号认证 uRL 而不是您的密钥,以及主机流和 @dodomain/connect 部件驱动器 他们。 他们被故意缺席 在服务器SDK.

API 密码

new DoDomain(options)

选项类型页:1
secretKeystring要求。 必须从 dd_sk_。 。 。 .
baseUrlstringAPI基础. 默认为 https://app.dodomain.io (自备/海关部署超过此数).
fetchImpltypeof fetch覆盖使用的获取( 测试, 自定义代理) .

dodomain.sessions.create(input) → Promise<ConnectSessionSummary>

input数字 : { domain, records, returnUrl?, recipe? } —— 说 domain (两个或两个以上DNS标签,无) 方案/端口/端口点)和 records 两者都需要; returnUrl 必须是https. recipe 被接受为电线相容性,但今天没有被任何消耗,没有 替换为 records. 回归 { id, token, expiresAt, connectUrl, records, warnings? }。 。 。 . 返回类型被命名 Session 在0.5.0之前;该别名仍然被出口,标记 @deprecated,并在下一个主要进口中删除 ConnectSessionSummary。 。 。 .

丢出错误 DoDomainError 与 .status 财务报告和审定财务报表 .body畸形(非JSON)或 系统无效反应也投出 DoDomainError ——绝不是原始站台出错.

dodomain.connections.list(filters?) → Promise<ListConnectionsResult>

filters数字 : { appId?, domain?, limit?, cursor?, includeDisconnected? } ——都是可选的,而且只有 你所通过者去接线(服务器拥有默认值 : limit 50,最大为100; 隐藏断开的连接). 回返 { connections, nextCursor },最先更新;通过 nextCursor 恢复为 cursor 下一页,当它是 null。 。 。 .

dodomain.connections.get(connectionId) → Promise<Connection>

读取一个连接, 包括您已经断开的连接 。 与元素相同的对象形状 联 合 国 list。未知且不是您的两个抛出 DoDomainError 与 .status === 404。 。 。 .

dodomain.connections.delete(connectionId) → Promise<DisconnectConnectionResult>

断开连接: 归档连接并停止其 DNS 监控, 因此您客户的域名 停止放出漂流的网点。 同上,重复返回原样 disconnectedAt 与 alreadyDisconnected: true 并且不会发出第二次网络呼号.

dodomain.connections.reverify(connectionId) → Promise<ReverifyConnectionResult>

点播 DNS 重新检查 。 一旦任务完成,就决定 已接受 (HTTP 202);判决 以一个 connection.verified 编号 : connection.failed 网瘾。 检查内部的连接 最后的10分钟抛出 DoDomainError 与 .status === 429。 。 。 .

dodomain.sessions.get(sessionId) → Promise<IntegratorSession>

读取会话 编号 - 那个 sessionId 每一个网钩有效载荷(符号出现在 - 没有一个。 与公用代号路径不同,它返回一个 过期 届会而不是 拒绝:读一下 expired 旗帜,在读取时生成,因此已经是真的 在死神重写之前 status。通过 a dd_sess_… 符号抛出 DoDomainError 与 .status === 0 ——该标志处理同一路径的公臂,它用不同的回答 形状。 未知的不是你们的 .status === 404。 。 。 .

dodomain.apps.list() → Promise<ListAppsResult>

这个证书可以看见的应用程序. 密钥可以完全看到它自己的应用程序; OAuth 标志 apps:read 看到整个团队。 只有可出版的 pk_* 键被返回.

dodomain.domains.check(input) → Promise<CheckDomainResult>

input数字 : { domain }. 无国籍飞行前-提供方、拥有区域、连接层级和流量,是否单击 实际可用oneClickAvailable数字 : tier: 2 只代表提供商说话 Domain Connect),和手动指南——不创建会话. 没有任何东西存在;每个计划都有.

dodomain.webhookEndpoints.list() → Promise<ListWebhookEndpointsResult>

这个应用程序的传送目标. 从不 返回时 secret - 没有读取表面.

dodomain.webhookEndpoints.create(input) → Promise<WebhookEndpointWithSecret>

input数字 : { url } (https,不是本地主机/私人地址,不是复制件——每次拒绝都扔出) .status === 400 与原因在内 .body.message;计划会投出 .status === 402) (中文(简体) ). 回来的 secret 显示一次 并且不再出现 同样的代码路径.

dodomain.webhookEndpoints.update(endpointId, input) → Promise<WebhookEndpoint>

重新指定新的终点 url签名的秘密是故意的 所以移动 主机从不强迫接收器重键. A 类 url 实际更改也恢复 自动停用端点.

dodomain.webhookEndpoints.resume(endpointId) → Promise<WebhookEndpoint>

恢复自动暂停的结束点域名——一个在7天内没有成功交付的域名 以及在该期间内至少5起无编号的交货(pausedAt 当它被暂停时,它将被设置。 清除 pausedAt 并重新启动7天的时钟;在不暂停的端点上具有偶能。 事件 当它被暂停时,被记录为漏运,而不因这一呼吁而感到不满 把它们从仪表板上重新推开.

dodomain.webhookEndpoints.delete(endpointId) → Promise<DeleteWebhookEndpointResult>

停止发送到终点。 过去交付的一行作为证据幸存下来,但交付失败 至已删除的终点,则不再可以重新驱动.

dodomain.webhookEndpoints.rotateSecret(endpointId) → Promise<WebhookEndpointWithSecret>

刻出新的签名秘密并返回 一次。 。 。 . 立即切换,没有双层秘密窗口 —— 说 工人们在交货时读到秘密现场, 所以签名立即切换, 重复旧的 包括交货。 尽快把新秘密布置到接收器上.

dodomain.keys.rotate() → Promise<RotateSecretKeyResult>

调用应用程序的秘密密钥自旋——可自动调用证书自旋的一半. 无优雅窗口 : 旧密钥停止认证此返回, 而返回 secretKey 是新书的唯一副本。 坚持下去,然后建立一个新的 DoDomain 与它—— 你调用的客户端仍然拿着旧钥匙. publicKey 反射没有改变 所以CI工作可以 说它改写了正确的应用。 故意不创建/列出/撤销其他密钥.

verifyWebhook(secret, body, header, toleranceMs?, nowMs?) → boolean

核实 t=<unixMs>,v1=<hex hmac> 签名域名随每个 Webhook 发送.

类型

DnsRecordType =: "A" | "AAAA" | "CNAME" | "TXT" | "MX"。 。 。 . DnsRecord =: { type, host, value, priority?, ttl? }。 。 。 . Connection =: { id, appId, sessionId, domain, fqdn, recordFqdns, status, verifiedAt, lastCheckedAt, brokenAt, disconnectedAt, createdAt }。 。 。 . IntegratorSession =: { id, appId, domain, records, recipe, status, tier, detectedProvider, connectionId, createdAt, expiresAt, expired }。 。 。 . WebhookEndpoint =: { id, appId, url, createdAt, pausedAt }· ; WebhookEndpointWithSecret 添加 一次显示 secret。 。 。 . RotateSecretKeyResult =: { appId, publicKey, secretKey, rotatedAt }。 。 。 . WebhookEvent =: { id, type, occurredAt, data } - 信封域名发送. WebhookEventWire =: WebhookEvent 加上被腐烂的 event 的别名 type,用于接收器 写作反对2026-08-06前身.

见 src/index.ts 为整个地表.

版本与发布记录

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

版本发布日期距今
0.8.0latest2026/10/1628 天前
0.7.02026/10/021 个月前
0.6.02026/09/092 个月前
0.4.02026/08/202 个月前
0.3.22026/08/202 个月前
0.3.12026/08/202 个月前

关于 @dodomain/node 的常见问题

@dodomain/node 是什么?

SaaS——官方域名服务器SDK的自定义域:薄荷连接会话(Cloudflare一击,域名连接,被引导DNS),读取连接,并验证所签名的webhooks.

如何安装 @dodomain/node?

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

@dodomain/node 支持 TypeScript 吗?

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

@dodomain/node 支持 ESM 还是 CommonJS?

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

@dodomain/node 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dodomain/node 可以商用吗?

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

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

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

@dodomain/node 还在维护吗?

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