---
type: how-to
title: 移动应用集成
summary: 把组件嵌入 iOS、Android、React Native 或浏览器 iframe 的 WebView 里，并把已核验的令牌桥接回你的代码。
---

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

## 1. 打开嵌入页

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

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

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

<Callout type="warn">
如果站点密钥打开了游戏关卡（它的 **要求一个游戏来验证** 设置），那么朴素的复选框无法通过：在一个设了关卡的密钥上，嵌入必须跑一个游戏。传一个 `game`（或 `games`）参数；不带游戏参数的光秃秃复选框嵌入会在那里抛出一个 `gate-unavailable` 错误。这是有意为之的，因为关卡是在 Caputchin 这一侧强制执行的。见 [游戏关卡](/docs/site-keys/games)。
</Callout>

### 查询参数

| 参数 | 必需 | 作用 |
|---|---|---|
| `sitekey` | 是 | 你的公钥（`cpt_pub_...`）。缺失则渲染一个不带组件的错误外壳。 |
| `game` | 否 | 单个应用市场游戏 id（`owner/repo`，或 `owner/repo/leaf`）。 |
| `games` | 否 | 一个逗号分隔的列表；逐会话随机挑一个。 |
| `game-src` | 否 | 你自己托管的游戏包的 URL，用于自托管游戏。 |
| `layout` | 否 | `inline`、`modal`、`fullscreen` 或 `auto`。在嵌入上默认为 `inline`。 |
| `overlay-width` / `overlay-height` | 否 | 在 `modal` / `fullscreen` 下，为游戏对话框设尺寸：`full`、一个像素数，或 `auto`（游戏自己偏好的尺寸）。见 [尺寸与布局](/docs/references/widget-sizing)。 |
| `locale` / `skin` / `config` | 否 | 转发给组件以挑选语言、皮肤或配置。 |
| `no-verify` | 否 | 跑游戏但不带验证关卡（仅游戏，无令牌）。 |
| `postMessageTarget` | 否 | 仅供浏览器 iframe 嵌入者（见下文）。在原生 WebView 里被忽略。 |

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

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

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

### iOS（WKWebView，Swift）

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

```swift
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`：

```kotlin
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` 分支：

```jsx
<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`：

```js
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`。见 [服务端集成](/docs/integration-guides/verify-on-your-backend)。

## 接收错误（可选）

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

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

## 另见

- [服务端集成](/docs/integration-guides/verify-on-your-backend)，看令牌检查。
- [客户端集成](/docs/integration-guides/cdn)，看网页上的对应做法。
