---
type: reference
title: 组件方法与事件
summary: 你能在 Caputchin 组件元素上调用的每一个方法、它们发出的每一个事件，带每个的确切载荷、哪个元素暴露它、以及它何时有效。
---

Caputchin 组件交付两个自定义元素，而你的页面以两个方向和它们交互：你在元素上 **调用方法** 来驱动它，并 **监听** 它发出的 **事件** 来作出反应。这一页是这两个面的详尽参考。

每个事件都是一个以 `bubbles: true` 和 `composed: true` 派发的 `CustomEvent`，所以它逃出组件的 shadow DOM，你可以直接在元素上（或在任何祖先上）监听它。数据始终在 `event.detail` 上。

## 两个元素一览

| 元素 | 用途 | 方法 |
|---|---|---|
| `<caputchin-widget>` | 只验证（proof-of-work / 复选框）。 | `start()` |
| `<caputchin-game>` | 游戏宿主，带可选验证。 | `pass()`、`fail()` |

复选框组件只有 `start()`；它 **不** 暴露 `pass()` / `fail()`。游戏宿主没有 `start()`（它的回合对 inline 在挂载时上线，对 modal / fullscreen 在第一次对话框打开时上线）。两个元素都发出 `start`、`pass`、`error` 和 `degraded`；游戏宿主额外发出 `dialog-shown` 和 `dialog-hidden`。覆盖范围在下面的 [事件表](#events) 里。

## 方法

### `start()`，仅 `<caputchin-widget>`

立即开始验证。它的用途是那个 **手动触发**：当元素有 `trigger="manual"` 时，你调用 `start()` 来从你自己的手势（例如，在你表单的提交上）触发 proof-of-work 求解，而非内置复选框。用默认的 `auto` / `click` 触发，元素接好它自己的激活，所以你不调用这个。

```js
const widget = document.querySelector("caputchin-widget");
widget.start();
```

游戏宿主没有 `start()`。完整的自己驱动它的演练，见 [手动模式](/docs/custom-game-development/manual-mode)。

### `pass(payload)`，仅 `<caputchin-game>`

释放验证关卡：访客成功了。**仅当 `trigger="manual"` 时有效**；在一个非手动游戏上调用它会发出一个带码 `invalid-call` 的 `error`。第一次 `pass()` 兑现并 **锁定** 那个裁定；之后的调用被默默忽略（每会话一个回合）。

```js
const widget = document.querySelector("caputchin-game");
widget.pass({ trace: roundRecord });
```

| `payload` 字段 | 类型 | 含义 |
|---|---|---|
| `trace` | `string` | 你游戏产出的那条不透明回合记录。在一个设了关卡的回合上，服务器重放它以推导那个权威裁定；在一个未设关卡或纯游戏的密钥上，它被原样接受。如果你的交互没有可回放的记录，就传一个空字符串。 |

这个方法上没有 `score` 字段，那个关卡是服务器对 `trace` 的回放，而非一个客户端声称的分数。（一个 `score` 确实出现在 [`pass` 事件](#the-pass-event-and-the-token) 上，那是组件向你回报，而非你向组件。）

在回合开始之前（modal / fullscreen，对话框尚未打开）调用 `pass()` 会发出一个带码 `invalid-call` 的 `error`。在一个纯游戏的密钥上（没有 `sitekey`）没有关卡可释放，所以 `pass()` 就只是发出带 `token: null` 的 `pass` 事件。

### `fail(payload?)`，仅 `<caputchin-game>`

中止回合：一次决定性的落败。**仅当 `trigger="manual"` 时有效**（否则 `invalid-call`）。可选，一个未完成的回合无论如何都被当作非通过。它中止在途的验证（proof-of-work 和探测检查），并浮现一个带码 `game-error-relayed` 的 `error` 事件。

```js
widget.fail({ code: "out-of-moves", message: "no moves left" });
```

| `payload` 字段 | 类型 | 含义 |
|---|---|---|
| `code` | `string`（可选） | 你的诊断码；作为 `error` 事件的 `originalCode` 浮现。默认为 `game-failed`。 |
| `message` | `string`（可选） | 人类可读的原因。 |

## 事件 [#events]

用 `addEventListener(type, handler)` 监听。每个载荷都住在 `event.detail` 上。

| 事件 | 由谁发出 | `event.detail` | 何时触发 |
|---|---|---|---|
| `start` | 两者 | `{ gameId: string \| null }` | 验证开始（挂载时自动、激活时、或经 `start()`）。`gameId` 对一个游戏回合被设置，对朴素组件为 `null`。 |
| `pass` | 两者 | `{ token: string \| null, score: number \| null, durationMs: number \| null }` | 验证被释放。`token` 是要发给你后端的那个包装令牌；在一个纯游戏的密钥上（没有 `sitekey`）为 `null`。 |
| `error` | 两者 | `{ code, message, severity, originalCode? }` | 从一个无害的配置警告到一次硬失败的任何东西。见 [error 事件](#the-error-event)。 |
| `degraded` | 两者 | `{ reason: "timeout" \| "network" \| "http" \| "malformed" }` | 组件没能及时解析你配置的尺寸 / skin / locale，于是用它捆绑的默认值渲染了。这不是一个错误。见 [degraded 事件](#the-degraded-event)。 |
| `dialog-shown` | `<caputchin-game>` | `{ layout: "modal" \| "fullscreen" }` | 一个覆盖层游戏对话框打开（程序化或第一次点击）。 |
| `dialog-hidden` | `<caputchin-game>` | `{ layout: "modal" \| "fullscreen" }` | 一个覆盖层游戏对话框关闭（程序化、Escape 或背幕点击）。 |

### `pass` 事件与令牌 [#the-pass-event-and-the-token]

`pass` 事件是你验证成功了的信号，但它 **不是** 那个信任决定。那个信任决定是你的后端确认 `detail.token`。始终 [在你的后端核验令牌](/docs/integration-guides/verify-on-your-backend)；绝不要仅凭前端事件就授予访问权。在一个表单里，组件也把令牌作为一个 `caputchin-token` 字段注入，好让一次普通的表单 POST 带上它。这个事件上的 `score` 和 `durationMs` 是客户端报告的分析数据，可能为 `null`；绝不要把它们当作一个信任信号。

### `error` 事件 [#the-error-event]

`error` 事件携带一个稳定的 `code`、一个人类的 `message`、一个 `severity`，有时还有一个 `originalCode`。每个码都有一个固定的默认严重度，好让你 **不必** 硬编码一张码到严重度的表，就能把“仍在运行”的警告和“真的坏了”的失败筛开，只读 `detail.severity`。完整的码集：

| `code` | `severity` | 含义 |
|---|---|---|
| `invalid-config` | `warn` | 一个组件拒绝的属性或组合；它优雅地降级并继续运行。 |
| `invalid-call` | `warn` | 一个在它无效时被调用的方法（例如在一个非手动游戏上、或在回合开始之前的 `pass()`）。 |
| `verification-failed` | `error` | proof-of-work 或探测检查（或令牌兑换）失败；没有令牌被签发。 |
| `game-load-failed` | `error` | 游戏包无法被解析、加载或注册。 |
| `gate-unavailable` | `error` | 服务器在引导时返回一个权威拒绝（一个设了关卡的密钥无法提供一个有效的游戏）。 |
| `game-error-relayed` | `error` | 一个从游戏内部浮现的错误（从 iframe 转达，或从一次手动 `fail()`）。 |

`detail.originalCode` 在公开的 `code` 是一个更具体内部原因的概括时存在（例如，一次作为 `game-load-failed` 转达的 iframe 加载失败，在 `originalCode` 里携带原始的 `iframe-load-failed` / `iframe-script-blocked` / `game-not-registered`）。用 `code` 来分支，只用 `originalCode` 来诊断。

### `degraded` 事件 [#the-degraded-event]

在挂载时，组件向 Caputchin 做一次简短的调用，来解析你配置的尺寸、skin 和 locale（对一个市场游戏，还有它偏好的占位）。若那次调用失败或太慢，组件不会阻塞也不会崩坏：它用烘焙进捆绑包里的值渲染，并发出 `degraded`，好让这种回退绝不悄无声息。在一次 `degraded` 事件之后，组件仍完全可用，只是解析出来的呈现可能跟你配置的不同（一个游戏可能以它的默认尺寸出现，或复选框以它的默认主题出现）。组件在回退之前会重试一次慢的解析，所以 `degraded` 意味着重试已耗尽，而非一次尝试慢了。

`detail.reason` 告诉你解析为何回退，好让你能在你自己的遥测里把它呈现出来：

| `reason` | 含义 |
|---|---|
| `timeout` | 解析没在它的时间预算内作答（服务慢或不可达）。 |
| `network` | 请求本身失败了（离线、DNS、被拦、或连接被重置）。 |
| `http` | 服务以一个错误状态作答。 |
| `malformed` | 服务作答了，但主体不合法。 |

`degraded` 是一则通知，而非一次失败，所以它被刻意地 **不** 设为一个 `error` 事件：一次慢的解析不该触发你为真正故障接好的那些处理器。只有当你想观察降级渲染时才监听它，例如在你的访客正看到回退呈现时报警。

## 监听，实践中

```js
const widget = document.querySelector("caputchin-game");

const onPass = (e) => {
  // send e.detail.token to your backend to verify
};
const onError = (e) => {
  if (e.detail.severity === "error") {
    console.error(e.detail.code, e.detail.message);
  }
};

widget.addEventListener("pass", onPass);
widget.addEventListener("error", onError);

// Clean up when you tear down your view:
widget.removeEventListener("pass", onPass);
widget.removeEventListener("error", onError);
```

这些是原生的 DOM `CustomEvent`，所以在 React 里你用一个 `ref` 和 `addEventListener` 附上它们（不是 JSX 的 `onPass` props）；[前端示例](/docs/integration-guides/frontend-examples) 展示框架专属的接线。因为每个事件都冒泡且被 composed，你也可以从一个容器元素委托，而不是逐个绑定每个组件。

## 另见

- [添加组件](/docs/getting-started/add-the-widget)：你在标记里设置的那些配置属性（这个输出侧的输入侧）。
- [用手动模式自己驱动验证](/docs/custom-game-development/manual-mode)：用 `start()` / `pass()` / `fail()` 的那篇教程。
- [在你的后端核验](/docs/integration-guides/verify-on-your-backend)：拿 `pass` 事件的令牌做什么。
- [前端示例](/docs/integration-guides/frontend-examples)：在 React、Vue 和纯 JS 里接好这些事件。
