# @dodomain/node 中文文档

规范地址：https://npmcn.com/package/%40dodomain/node/

版本：0\.8\.0
npm 元数据更新时间：2026-10-15T21:24:43.842Z
中文译文更新时间：2026-11-12T14:33:56.601Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40dodomain/node
原文对照：https://npmcn.com/package/%40dodomain/node/?lang=original
项目主页：https://dodomain.io/
源码仓库：https://github.com/DevinoSolutions/dodomain-sdk

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：4.7 MB，9 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=20
- 运行依赖：0 个
- 首次发布：2026-07-31T04:05:40.917Z
- 最新版本发布：2026-10-15T21:24:43.486Z
- 近 30 天下载量：9146（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：8（近 12 个月 8 个）

## 中文简介

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

## 安装

```sh
npm install @dodomain/node
```

也可以使用：`pnpm add @dodomain/node`、`yarn add @dodomain/node`、`bun add @dodomain/node`

## README

# @ dodomain/ 节点

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

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

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

## 安装

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

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

## 使用量

```ts
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
```

### 管理连接

```ts
// 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);
```

### 回读一个会话

```ts
// 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
```

### 飞行前域

```ts
// 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
```

### 管理网点

```ts
// 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);
```

### 旋转您的密钥

```ts
// 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

```ts
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 |
| --- | --- | --- |
| `secretKey` | `string` | 要求。 必须从 `dd_sk_`。 。 。 . |
| `baseUrl` | `string` | API基础. 默认为 `https://app.dodomain.io` (自备/海关部署超过此数). |
| `fetchImpl` | `typeof 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`](https://npmcn.com/package/%40dodomain/node/src/index.ts) 为整个地表.

## 常见问题

### @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 和提交记录判断。

