Caputchin
集成指南

移动应用集成

Caputchin 通过一个托管的嵌入页在原生应用里运行。你把一个 WebView 指向那个页面,访客在它里面通过挑战,页面再把得到的令牌(以及任何错误)桥接回你的原生代码。验证本身和网页上的完全一样;只是宿主外壳不同。

1. 打开嵌入页

把一个 WebView 指向 https://caputchin.com/embed,至少带一个站点密钥:

https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory

省略游戏参数即为朴素的复选框。一个 games=a,b 列表会逐会话随机挑一个。

查询参数

参数必需作用
sitekey你的公钥(cpt_pub_...)。缺失则渲染一个不带组件的错误外壳。
game单个应用市场游戏 id(owner/repo,或 owner/repo/leaf)。
games一个逗号分隔的列表;逐会话随机挑一个。
game-src你自己托管的游戏包的 URL,用于自托管游戏。
layoutinlinemodalfullscreenauto。在嵌入上默认为 inline
locale / skin / config转发给组件以挑选语言、皮肤或配置。
no-verify跑游戏但不带验证关卡(仅游戏,无令牌)。
postMessageTarget仅供浏览器 iframe 嵌入者(见下文)。在原生 WebView 里被忽略。

game / games / game-src 三者全都省略即为朴素的复选框。没有 mode 参数;组件自己驱动触发。

2. 把令牌桥接到你的代码

当访客通过时,嵌入页会把令牌通过与你宿主相匹配的通道推送出去。注册相匹配的处理器。

iOS(WKWebView,Swift)

令牌作为一个原始字符串到达一个名为 caputchin 的消息处理器:

class TokenHandler: NSObject, WKScriptMessageHandler {
  func userContentController(_ controller: WKUserContentController,
                             didReceive message: WKScriptMessage) {
    guard let token = message.body as? String else { return }
    // send `token` to your backend
  }
}

let config = WKWebViewConfiguration()
config.userContentController.add(TokenHandler(), name: "caputchin")
let webView = WKWebView(frame: .zero, configuration: config)
webView.load(URLRequest(url: URL(string: "https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory")!))

Android(WebView,Kotlin)

令牌作为一个原始字符串到达一个名为 caputchin@JavascriptInterface

class TokenBridge {
  @JavascriptInterface
  fun onToken(token: String) {
    // send `token` to your backend
  }
}

webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(TokenBridge(), "caputchin")
webView.loadUrl("https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory")

React Native(react-native-webview)

嵌入会在单一的 React Native 通道上发出一个 JSON 字符串。解析它,并据 type 分支:

<WebView
  source={{ uri: "https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory" }}
  onMessage={(e) => {
    const msg = JSON.parse(e.nativeEvent.data);
    if (msg.type === "caputchin-token") {
      // send msg.token to your backend
    } else if (msg.type === "caputchin-error") {
      // msg.code, msg.message, msg.originalCode (optional)
    }
  }}
/>

浏览器 iframe(postMessage)

如果你把页面嵌入一个浏览器 <iframe> 而不是原生 WebView,那么父级转发 默认是关的,只有当你通过 ?postMessageTarget=<origin> 用一个精确的目标来源主动选用时才会触发(例如 postMessageTarget=https://app.example.com)。通配符(*)、一个路径、或任何无法解析成一个光秃秃的协议、主机和端口的东西都会被拒绝,于是这个一次性、绑定站点的令牌绝不会被广播给一个未经核验的父级。在父级上监听,并在信任消息之前检查 event.origin

window.addEventListener("message", (event) => {
  if (event.origin !== "https://embed-host.example.com") return;
  const msg = event.data;
  if (msg.type === "caputchin-token") {
    // msg.token
  } else if (msg.type === "caputchin-error") {
    // msg.code, msg.message
  }
});

3. 在你的后端核验

这和网页上一样:带你的密钥把令牌 POST 到 /siteverify。见 服务端集成

接收错误(可选)

每当组件遇到错误时,嵌入页会通过一组并行通道把它镜像出来:iOS 的 caputchinError 消息处理器、Android 的 caputchinError 接口(onError(json))、React Native 通道上的 { type: "caputchin-error", ... }、以及同一个设了关卡的 iframe 转发。载荷携带 { code, message, originalCode? }。iOS 和 Android 把它作为一个 JSON 字符串接收;React Native 和 iframe 转发接收带类型的信封。

这完全是可选的。一个不注册任何错误处理器的应用就只是收不到错误而已,而无论如何,嵌入页本来就在 WebView 里显示一个重试 UI。

另见

本页内容