# @akedly/shield 中文文档

规范地址：https://npmcn.com/package/%40akedly/shield/

版本：1\.1\.0
npm 元数据更新时间：2026-08-04T10:35:40.242Z
中文译文更新时间：2026-11-06T08:31:08.456Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40akedly/shield
原文对照：https://npmcn.com/package/%40akedly/shield/?lang=original
项目主页：https://github.com/Akedly-Org/Akedly#readme
源码仓库：https://github.com/Akedly-Org/Akedly

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

- 包体积（解压后）：58.5 kB，6 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：0 个
- 首次发布：2026-03-09T09:35:21.386Z
- 最新版本发布：2026-08-04T10:35:39.902Z
- 近 30 天下载量：3828（2026-10-06 至 2026-11-04，npm 官方下载统计）
- 已发布版本数：4（近 12 个月 4 个）

## 中文简介

客户端 PoW 解析器, Turnstile 帮助器, 并托管 Akedly Shield V1.2 的密码仪式启动器

## 安装

```sh
npm install @akedly/shield
```

也可以使用：`pnpm add @akedly/shield`、`yarn add @akedly/shield`、`bun add @akedly/shield`

## README

# @akedly/屏蔽

客户端验证-工作解析器,Turnstile帮助器,并托管了Akedly Shield V1.2的密码仪式启动器.

## 安装

```bash
npm install @akedly/shield
```

或者通过CDN:

```html
<script src="https://unpkg.com/@akedly/shield/dist/akedly-shield.min.js"></script>
```

## 快速启动

```javascript
import { solvePow } from '@akedly/shield';

// 1. Get challenge from your server
const res = await fetch('/api/v1.2/transactions/challenge?APIKey=...&pipelineID=...');
const { data } = await res.json();

// 2. Solve PoW
const { nonce } = await solvePow(data.challenge, data.difficulty);

// 3. Send OTP with solution
await fetch('/api/v1.2/transactions/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    APIKey: '...',
    pipelineID: '...',
    verificationAddress: { phoneNumber: '+201234567890' },
    powSolution: { challengeToken: data.challengeToken, nonce }
  })
});
```

## API 参考

### `solvePow(challenge, difficulty, options?)`

方便功能可以创建解子,找到非ce,并清理.

-   `challenge` 服务器的64 char 十六进制字符串
-   `difficulty` −整数(需为正六分数)
-   `options.onProgress(hashesChecked)` ——回调进度
-   `options.useWorker` —— 说 `true`, (中文(简体) ), `false`,或 `'auto'` (默认 : `'auto'`(中文(简体) )
-   回返 `Promise<{ nonce: number }>`

### `new AkedlyShield(options?)`

可重复使用的解析器实例 .

-   `options.useWorker` —— 说 `true`, (中文(简体) ), `false`,或 `'auto'` (默认 : `'auto'`(中文(简体) )

#### `.solve(challenge, difficulty, options?)`

与 `solvePow` 但却重用这个例子.

#### `.terminate()`

杀死活动 Web Worker并发布 Blob URL.

### `getTurnstileToken(siteKey, options?)`

只浏览器 。 创建隐藏的 Turnstile 部件, 用符号解决, 并清理 .

-   `siteKey` — Cloudflare Turnstile 站点密钥
-   `options.theme` —— 说 `'light'`, (中文(简体) ), `'dark'`,或 `'auto'`
-   `options.size` —— 说 `'normal'` 或者说 `'compact'`
-   回返 `Promise<string>`

### `renderTurnstile(siteKey, container, options?)`

将 Turnstile 部件变成 DOM 元素 .

-   `container` - DOM 元素可制成
-   回返 `Promise<string>` (铃声)

### `openPasskeyCeremony(token, options?)`

只浏览器 。 打开托管的 V1.2 密钥仪式(`auth.akedly.io/pk`作为弹出和 以页面中继结果解决。 两个都用吧 **注册l** (通过 `enrollmentToken` 从成功的 `/verify`和 **认证** (通过 `ceremonyToken`  后端从 `POST /auth-options`) (中文(简体) ).

> **必须在直接用户处理器内调用** (例如点击处理器)或 浏览器会屏蔽弹出 。 如果您需要后端回转来回 来铸造代币 _之后_ 单击 **认证** 案例 - `/auth-options`中同步打开空白窗口 处理器通过 `options.popup` (见说明2).

-   `token` ——由后端铸造的仪式代币(" your")`enrollmentToken` 入学, `ceremonyToken` 要认证)
-   `options.ceremonyOrigin` — 覆盖主机源( 默认) `https://auth.akedly.io`(中文(简体) )
-   `options.popup` a 包括: `Window` 您在点击时同步打开; SDK 导航它而不是打开它自己的(在手势后获得令牌时使用)
-   `options.onResult(result)` ——侧道回调(有效载荷与解析值相同)
-   `options.onTelemetry(evt)` 火灾和遗忘漏斗事件(`passkey_opened`, (中文(简体) ), `passkey_verified`, (中文(简体) ), `passkey_cancelled`, (...) (中文(简体) )
-   `options.windowFeatures` 编号 : `options.windowName` —— 说 `window.open` 覆盖
-   `options.timeoutMs` 自动解决 `reason: 'timeout'` 在 N ms 之后( 默认) `0` = 没有超时)
-   回返 `Promise<PasskeyCeremonyResult>`数字 :

```ts
{
  verified: boolean;        // true only on a completed, successful ceremony
  supported: boolean;       // false only when WebAuthn is unavailable
  purpose: string | null;   // 'enroll' | 'auth'
  transactionId: string | null; // the passkey requestID
  resultToken: string | null;   // signed proof of a verified outcome — verify on YOUR backend
  code: string | null;
  reason: string | null;    // null when verified; else 'closed' | 'popup_blocked'
                            //   | 'unsupported' | 'timeout' | 'no_proof'
                            //   | 'failed' | <server code>
                            // NOTE: /pk does not send a server `code` today, so `code` is
                            //   always null and `reason` is only ever one of the local
                            //   values above. Reserved in the contract, not a live branch.
}
```

> ⚠️ **别相信生来的东西 `verified` 校验 `resultToken`。 。 。 .** 信息本身是 一个客户信号,并可以伪造。 但每个 **已核实** 结果包含一个 **`resultToken`**数字 : 紧凑值 **HMAC 用您的账户 API 密钥签名**所以你的后端证实了 **离线** —— 说 **同步进行,没有投票,也没有往返到Akedly**。见 [校验结果](https://npmcn.com/package/%40akedly/shield/#readme-verify-the-result-seamless-no-polling) 见下文。 (另外两个渠道) 存在: 如果您的管道集 `backendCallbackURL`,凯德利 **(单位:千美元)** 点火服务器到服务器 调用时 **认证** 达到 `/auth-verify` ——你没有选择加入,因为 这是 **未签名** 这是送货信号而不是证据 没错 **没有** 射击入学, 也绝不是那些从未到达服务器的结果 `closed`, (中文(简体) ), `popup_blocked`, (中文(简体) ), `unsupported`, (中文(简体) ), `timeout` 因此,不要在网络上等待这些。 还有一个 `GET /api/v1.2/transactions/passkey/result?token=<ceremonyToken>` 寻找——只举行认证仪式. )

承诺 **绝不拒绝正常的结果** (它以 `reason` 这样你就能摔倒了 它只在编程出错(丢失了令牌)时拒绝.

### `isPasskeySupported()`

同步 `boolean` —— 说 `true` 当浏览器曝光时 `window.PublicKeyCredential`。使用它来 跳过不支持的浏览器提供的密码( 直接通过 OTP) .

## 平台支持

| 平台 | PoW 解决方案 | 转弯 |
| --- | --- | --- |
| 浏览器 (工人) | 网络工人( 脱线) | `getTurnstileToken()` |
| 浏览器( 无工作) | 断开主线 | `getTurnstileToken()` |
| 原生反应 | 断开主线 | 使用桥页 |
| 节点.js | 与本地加密同步 | 不详 |

## 网络工人行为

何时 `useWorker` 实值 `'auto'` (默认),库:

1.  检查是否 `Worker`, (中文(简体) ), `Blob`,以及 `URL.createObjectURL` 备有
2.  如果是,将解析器代码捆绑到 Blob URL Worker (不需要单独的文件)
3.  如果无( 如 React Industrial) , 则返回批量主线解析 `setTimeout(fn, 0)` 在批次之间保持用户界面响应

## 算法

```
hash = SHA256(challenge + ":" + String(nonce))   // hex digest
valid = hash.startsWith("0".repeat(difficulty))   // leading hex zeros
```

解析器递增 `nonce` 从 0 到找到有效的散列.

## 完整整合示例

```javascript
import { solvePow, getTurnstileToken } from '@akedly/shield';

async function sendOTP(phoneNumber, apiKey, pipelineID) {
  // Step 1: Get challenge
  const challengeRes = await fetch(
    `https://api.akedly.io/api/v1.2/transactions/challenge?APIKey=${apiKey}&pipelineID=${pipelineID}`
  );
  const { data } = await challengeRes.json();

  // Step 2: Solve PoW (if required)
  let powSolution;
  if (data.challengeRequired) {
    const { nonce } = await solvePow(data.challenge, data.difficulty, {
      onProgress: (checked) => console.log(`${checked} hashes checked...`)
    });
    powSolution = { challengeToken: data.challengeToken, nonce };
  }

  // Step 3: Get Turnstile token (if required)
  let turnstileToken;
  if (data.turnstile?.required) {
    turnstileToken = await getTurnstileToken(data.turnstile.siteKey);
  }

  // Step 4: Send OTP
  const sendRes = await fetch('https://api.akedly.io/api/v1.2/transactions/send', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      APIKey: apiKey,
      pipelineID,
      verificationAddress: { phoneNumber },
      powSolution,
      turnstileToken
    })
  });

  return sendRes.json();
}
```

## 密码(V1.2)

`openPasskeyCeremony` 是一个 **无国籍发射器** - 它从不打电话 `/challenge`, (中文(简体) ), `/send`,或 `/verify`。你拥有这些,就像你今天所做的一样;密码会添加三个客户驱动的食谱 在顶上。 主办单位 `auth.akedly.io/pk` 页面运行 WebAuthn 仪式(完全标出自您的 并转发结果. **总是确认服务器侧.**

### 道具1——在检察官办公室成功核实后立即输入密码

A V1.2 数据 `/verify` 成功现在返回添加剂 **`enrollmentToken`** (有效~2分,单人) 使用) (中文(简体) ). 立即入学:

> ⚠️ **过 `returnTarget` 改为 `/verify` 如果你想要可核实的入学结果.** 主机页面 中继 a `resultToken` 仅改为a **服务器签名** 返回目标,并携带入学标志 只有一个,如果你要求它。 关闭它和入学仍然成功——密码是创建的 但仪式决定了 `verified:false, reason:"no_proof"`因为每个Akedly SDK 拒绝调用未经核实的结果. **所以不要打开您的"密码已启用!" UI on `result.verified` 除非你通过了 `returnTarget`· ;** 将录取结果作为咨询 否则,并让下一个成功的签注成为证据.

```javascript
import { openPasskeyCeremony, isPasskeySupported } from '@akedly/shield';

// ...your normal challenge -> (PoW/Turnstile) -> send -> verify flow...
const verify = await verifyOtp(requestID, otp); // your existing call
if (verify.data?.verified && verify.data?.enrollmentToken && isPasskeySupported()) {
  // MUST run in the click handler of an "Enable Face ID / fingerprint" button.
  const result = await openPasskeyCeremony(verify.data.enrollmentToken, {
    onTelemetry: (e) => analytics.track(e.event, e.props)
  });
  if (result.verified) {
    // Enrollment has no /result endpoint — the passkey now simply exists and is proven on
    // the user's next sign-in. Update your own UI / record-keeping as you see fit.
    await fetch('/api/passkey-enrolled', { method: 'POST', body: JSON.stringify({ transactionId: result.transactionId }) });
  }
  // result.verified === false (closed / unsupported / ...) => just don't show "enabled"; harmless.
}
```

### 道具2 — 用密码认证返回的用户

 **后端** 以呼叫开始仪式 `POST /auth-options` (它带着秘密API) 键和清除PoW/Turnstile门,然后手拿 `ceremonyToken` 转到浏览器 :

> ⚠️ **网络:包括 `returnTarget.origin` 输入 `/auth-options` 电话.** 主机页面将 从不转发签名 `resultToken` 到未知的网络来源——省略 `returnTarget` 弹出 员额 `"*"`,所以门户 **剥离符号** (单位:千美元)`result.resultToken` 来了 `null` 财务报告和审定财务报表 离线核查不能完成). 传出你的地址来源,所以信使是中继的. 报名 **进取也需要一个——更正为2026-07-28.** 此行曾表示"招生流程不需要 符号,而原生回路-URL /桥道流在不带 `returnTarget`两分之一 错了 录取的证明是完全一样的 所以通过 `returnTarget` 改为 `/verify` (收录一)或收录结果未证实. 在本土道路上,只有 **网络视频桥** 不带符号 `returnTarget`· ; **返回URL重定向被剥离 同样的方式** android和Flutter的电话.

```javascript
// --- your backend ---
// POST https://api.akedly.io/api/v1.2/transactions/passkey/auth-options
//   { APIKey, pipelineID, verificationAddress: { phoneNumber },
//     returnTarget: { origin: 'https://your-site.example' }, // <-- REQUIRED for the web resultToken
//     powSolution?, turnstileToken? }
// -> { data: { ceremonyToken, requestID } }   (or a "no passkey" code => use OTP)

// --- your frontend (sign-in click handler) ---
// The token needs a backend round-trip, so open a blank popup NOW (in the gesture) and
// hand it to the SDK once the token is ready — a window.open after the await is blocked.
const popup = window.open('about:blank', 'akedly_passkey', 'width=480,height=680');
const { ceremonyToken } = await startAuth(phoneNumber); // your backend -> /auth-options
if (!ceremonyToken) { popup?.close(); return fallbackToOtp(); } // no passkey on file

const result = await openPasskeyCeremony(ceremonyToken, { popup });
if (result.verified) {
  // Seamless + synchronous: hand the proof token to YOUR backend, which verifies it
  // offline (no poll, no round-trip to Akedly) and creates the session. See "Verify the result".
  await completeSignIn(result.resultToken);
} else {
  await fallbackToOtp(); // Recipe 3
}
```

### 核查结果(无间隔,无投票)

每个 **已核实** 仪式决定 **`resultToken`**: 契约,签名证明 结果。 从前端转到后端; 在那里验证 **离线** 还有你的 账户 API 密钥 — 没有回电到 Akedly , 没有网页 hook 接收 .

```
pkrt1.<base64url(payload)>.<base64url(hmac)>
   payload = { v, purpose, transactionId, pipelineId, verified, iat, exp }
   hmac    = HMAC_SHA256("pkrt1." + base64url(payload),  YOUR_API_KEY)
```

```javascript
// --- your backend --- (Node; ~12 lines, zero deps)
import crypto from 'node:crypto';

export function verifyAkedlyResult(token, apiKey) {
  if (!apiKey) return null;                                       // fail closed on an empty key
  if (typeof token !== 'string' || !token.startsWith('pkrt1.')) return null;
  const parts = token.slice('pkrt1.'.length).split('.');
  if (parts.length !== 2) return null;                         // exactly two segments
  const [data, sig] = parts;
  const b64u = /^[A-Za-z0-9_-]+$/;                             // strict unpadded base64url
  if (!b64u.test(data) || !b64u.test(sig)) return null;
  // canonical form only — a slack-bit alias decodes to the same bytes and would bypass
  // a string-keyed single-use check
  if (Buffer.from(data, 'base64url').toString('base64url') !== data) return null;
  if (Buffer.from(sig, 'base64url').toString('base64url') !== sig) return null;
  const expected = crypto.createHmac('sha256', apiKey).update('pkrt1.' + data).digest();
  const given = Buffer.from(sig, 'base64url');
  if (expected.length !== given.length || !crypto.timingSafeEqual(expected, given)) return null;
  let payload;
  try { payload = JSON.parse(Buffer.from(data, 'base64url').toString()); } catch (e) { return null; }
  if (!payload.exp || Date.now() > payload.exp) return null;   // expired
  if (payload.verified !== true) return null;                  // only a verified outcome is trustworthy
  return payload; // verified + unexpired — bind transactionId + purpose to the ceremony you started
}

// POST /complete-sign-in  { resultToken }
const claim = verifyAkedlyResult(req.body.resultToken, process.env.AKEDLY_API_KEY);
if (claim?.verified && claim.purpose === 'auth' && claim.transactionId === expectedTxId) {
  createSession(user); // safe: the proof is signed by us, bound to this auth transaction
}
```

把信使当作一次性的认证代码: 它寿命短 (~2分) 你应该接受它 一次 一次吗 旧的确认路径仍然有效,如果你喜欢的话(`backendCallbackURL`,或一个 `GET …/passkey/result?token=` 寻取.

### 道具 3 — 在取消/ 失败/ 缺少证据时返回检察官办公室

每个未经核实的结果都是 `reason` 你可以继续... 直接掉下去 现有检察官办公室 `send` + 键 `verify`数字 :

```javascript
const result = await openPasskeyCeremony(token);
if (!result.verified) {
  // 'closed' | 'popup_blocked' | 'unsupported' | 'timeout' | 'no_proof' | 'failed'
  // (<server code> is reserved in the contract but /pk never sends one today)
  await runOtpFlow(); // your existing challenge -> send -> verify
}
```

A 类 **工作端到端演示** (节点后端驱动真实的 V1.2 REST + a React 前端 入学、认证和返回) [`akedly-v12-test-apps/web`](https://npmcn.com/package/akedly-v12-test-apps/web) ——这些食谱是从中取出.

## 相关软件包

-   **达特/弗卢特**数字 : [`akedly_shield`](https://github.com/Akedly-Org/akedly-shield-dart)
-   **快速( iOS)**数字 : [`AkedlyShield`](https://github.com/Akedly-Org/akedly-shield-swift)
-   **克特林 (Android) (英语)**数字 : [`com.akedly.shield`](https://github.com/Akedly-Org/akedly-shield-kotlin)

## 常见问题

### @akedly/shield 是什么？

客户端 PoW 解析器, Turnstile 帮助器, 并托管 Akedly Shield V1\.2 的密码仪式启动器

### 如何安装 @akedly/shield？

在项目目录运行 npm install @akedly/shield，或使用 pnpm add / yarn add / bun add。

### @akedly/shield 支持 TypeScript 吗？

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

### @akedly/shield 支持 ESM 还是 CommonJS？

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

### @akedly/shield 可以商用吗？

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

### @akedly/shield 的下载量有多少？

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

### @akedly/shield 还在维护吗？

最新版本 1\.1\.0 发布于 2026/08/04（3 个月前）；至今共发布 4 个版本，其中过去 12 个月发布了 4 个；首次发布于 2026/03/09。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

