组件方法与事件
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。覆盖范围在下面的 事件表 里。
方法
start(),仅 <caputchin-widget>
立即开始验证。它的用途是那个 手动触发:当元素有 trigger="manual" 时,你调用 start() 来从你自己的手势(例如,在你表单的提交上)触发 proof-of-work 求解,而非内置复选框。用默认的 auto / click 触发,元素接好它自己的激活,所以你不调用这个。
const widget = document.querySelector("caputchin-widget");
widget.start();游戏宿主没有 start()。完整的自己驱动它的演练,见 手动模式。
pass(payload),仅 <caputchin-game>
释放验证关卡:访客成功了。仅当 trigger="manual" 时有效;在一个非手动游戏上调用它会发出一个带码 invalid-call 的 error。第一次 pass() 兑现并 锁定 那个裁定;之后的调用被默默忽略(每会话一个回合)。
const widget = document.querySelector("caputchin-game");
widget.pass({ trace: roundRecord });payload 字段 | 类型 | 含义 |
|---|---|---|
trace | string | 你游戏产出的那条不透明回合记录。在一个设了关卡的回合上,服务器重放它以推导那个权威裁定;在一个未设关卡或纯游戏的密钥上,它被原样接受。如果你的交互没有可回放的记录,就传一个空字符串。 |
这个方法上没有 score 字段,那个关卡是服务器对 trace 的回放,而非一个客户端声称的分数。(一个 score 确实出现在 pass 事件 上,那是组件向你回报,而非你向组件。)
在回合开始之前(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 事件。
widget.fail({ code: "out-of-moves", message: "no moves left" });payload 字段 | 类型 | 含义 |
|---|---|---|
code | string(可选) | 你的诊断码;作为 error 事件的 originalCode 浮现。默认为 game-failed。 |
message | string(可选) | 人类可读的原因。 |
事件
用 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 事件。 |
degraded | 两者 | { reason: "timeout" | "network" | "http" | "malformed" } | 组件没能及时解析你配置的尺寸 / skin / locale,于是用它捆绑的默认值渲染了。这不是一个错误。见 degraded 事件。 |
dialog-shown | <caputchin-game> | { layout: "modal" | "fullscreen" } | 一个覆盖层游戏对话框打开(程序化或第一次点击)。 |
dialog-hidden | <caputchin-game> | { layout: "modal" | "fullscreen" } | 一个覆盖层游戏对话框关闭(程序化、Escape 或背幕点击)。 |
pass 事件与令牌
pass 事件是你验证成功了的信号,但它 不是 那个信任决定。那个信任决定是你的后端确认 detail.token。始终 在你的后端核验令牌;绝不要仅凭前端事件就授予访问权。在一个表单里,组件也把令牌作为一个 caputchin-token 字段注入,好让一次普通的表单 POST 带上它。这个事件上的 score 和 durationMs 是客户端报告的分析数据,可能为 null;绝不要把它们当作一个信任信号。
error 事件
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 事件
在挂载时,组件向 Caputchin 做一次简短的调用,来解析你配置的尺寸、skin 和 locale(对一个市场游戏,还有它偏好的占位)。若那次调用失败或太慢,组件不会阻塞也不会崩坏:它用烘焙进捆绑包里的值渲染,并发出 degraded,好让这种回退绝不悄无声息。在一次 degraded 事件之后,组件仍完全可用,只是解析出来的呈现可能跟你配置的不同(一个游戏可能以它的默认尺寸出现,或复选框以它的默认主题出现)。组件在回退之前会重试一次慢的解析,所以 degraded 意味着重试已耗尽,而非一次尝试慢了。
detail.reason 告诉你解析为何回退,好让你能在你自己的遥测里把它呈现出来:
reason | 含义 |
|---|---|
timeout | 解析没在它的时间预算内作答(服务慢或不可达)。 |
network | 请求本身失败了(离线、DNS、被拦、或连接被重置)。 |
http | 服务以一个错误状态作答。 |
malformed | 服务作答了,但主体不合法。 |
degraded 是一则通知,而非一次失败,所以它被刻意地 不 设为一个 error 事件:一次慢的解析不该触发你为真正故障接好的那些处理器。只有当你想观察降级渲染时才监听它,例如在你的访客正看到回退呈现时报警。
监听,实践中
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);前端示例 展示框架专属的接线。因为每个事件都冒泡且被 composed,你也可以从一个容器元素委托,而不是逐个绑定每个组件。
另见
- 添加组件:你在标记里设置的那些配置属性(这个输出侧的输入侧)。
- 用手动模式自己驱动验证:用
start()/pass()/fail()的那篇教程。 - 在你的后端核验:拿
pass事件的令牌做什么。 - 前端示例:在 React、Vue 和纯 JS 里接好这些事件。