# 客户端配置指南

应用创建后获取对应客户端 Public Key，在业务前端配置展示即可，无需在后台创建主题或逐个添加 `login`、`register` 场景。Server Key 只在业务后端使用。

## 配置优先级

客户端明确传入的参数 → SDK 默认值。服务端 `/config` 返回的风险规则仍然有效，但 `language`、`product` 不再覆盖 SDK 的前端展示参数。Web 自定义 `loadConfig()` 本身属于前端代码，其展示值可作为默认值，显式顶层参数优先。

`scene` 是业务动作标识；业务后端核验时使用该接口预期的场景。`challengeMode` 是题型偏好，服务端可以拒绝或升级；前端必须按返回的 `challenge_type` 展示实际挑战。

升级时：原先依赖后台 `product=invisible` 或固定语言的业务，应在初始化参数中明确填写对应 `product` / `language`。默认 `popup` 不会自动启动验证，需点击 SDK 触发器或调用 Web 的 `open()`。

## 公开参数

| 参数 | 默认值 / 范围 | 用途 |
| --- | --- | --- |
| Public Key | 必填，各客户端使用自己的凭证 | Web `captchaId`；其余 `publicKey` |
| API 地址 | 按下方各端说明 | HTTPS 服务地址，不能混用站点根路径和完整 API 路径 |
| `scene` | 推荐 `default`，或 `login` / `register` 等 | 前后端必须一致；无需逐个预建 |
| `clientType` / `clientId` | 对应平台与应用标识 | Web/TypeScript 显式传入；原生固定平台；Flutter 自动读取实际包名/Bundle ID |
| `language` | `auto` | 指定语言或使用运行环境语言；原生自绘 UI 还需绑定本地化资源 |
| `product` | `popup`；Flutter 弹窗默认 `invisible` | `popup` 点击触发；`float` 浮层；`bind` 绑定业务按钮；`invisible` 初始化后直接启动，仍可能显示交互挑战 |
| `appearance.theme` | `light` / `dark` / `auto`，默认 `light` | `auto` 跟随系统明暗主题 |
| `appearance.primaryColor` | `#4f6bff`，仅 `#RRGGBB` | 主操作颜色；Web/Flutter 自动选择有对比度的按钮文字颜色 |
| `appearance.borderRadius` | 12，整数 0–24 | 验证外层卡片圆角，单位 CSS px / 逻辑像素；不改变题图坐标 |
| `hapticFeedback` | `true` | 是否允许触觉反馈；无硬件能力时不震动 |
| `challengeMode` | `smart` | `smart`、`slide`、`click`、`grid`、`onetap`、`rotate`、`iconcrush`、`gobang`、`space`、`dynamic`、`voice` |
| 请求超时 | 通常 12 秒，3–30 秒 | Web/Flutter/小程序 `requestTimeoutMs`；Flutter 使用 `Duration`；Swift `timeout` 秒；Kotlin `timeoutMillis`；HarmonyOS `timeoutMs`；TypeScript `request.timeoutMs` |
| 配置重试 | Web 2；Flutter/小程序 0；范围 0–3 | 仅配置 GET 的网络/服务错误可重试，挑战创建和提交不能自动重复；原生请求层由调用方重试配置读取 |

支持语言：`zh-CN`、`zh-TW`、`en`、`es`、`pt-BR`、`fr`、`de`、`ja`、`ko`、`ru`、`ar`、`hi`、`id`、`tr`、`vi`、`th`、`it`、`nl`、`pl`、`uk`、`fa`、`he`、`ms`、`fil`。共享 Web 组件提供这些语言；原生自绘界面须自行提供对应文案。没有浏览器语言环境时建议显式传入设备语言。

以下为 Web 专属交互接口：`container`（挂载点）、`bindTrigger`（绑定已有按钮）、`open()`（手动打开）、`reset()`（重置）、`destroy()`（销毁）、`onSuccess`（取得待核验 Proof）、`onState`（ready/required/verified/error）、`onEvent`（生命周期事件）、`onError`（展示错误）。Flutter 通过 `Future<Proof?>` 返回成功/取消，`onEvent` 用于观察生命周期；其他原生和小程序的完成/错误通过 async/Promise 返回，界面事件由业务 UI 管理。

Web 的 `enabled`、`offlineBypass` 是兼容参数：只影响本地组件和失败展示，不会生成可信凭证。生产保持 `enabled=true`、`offlineBypass=false`，请求失败应允许用户重试。不要用这些开关跳过业务后端核验。

TypeScript 低阶请求支持 `headers`、`fetch`、`signal`、`beforeRequest`、`afterResponse` 等扩展；仅在需要自定义传输时使用。小程序支持 `request` 适配器、`sessionId`、`telemetry`。这些客户端数据不应被业务后端直接当作放行依据。

## Web / H5

```js
const captcha = RozeCaptcha.init({
  container: '#captcha',
  captchaId: '你的 Web Public Key',
  apiBase: 'https://pink.ya.wang/api/captcha',
  scene: 'login', clientType: 'web', clientId: 'web',
  language: 'zh-CN', product: 'bind', bindTrigger: '#login-button',
  appearance: { theme: 'auto', primaryColor: '#167d50', borderRadius: 8 },
  challengeMode: 'smart', hapticFeedback: true,
  requestTimeoutMs: 12000, configRetries: 2,
  onSuccess: proof => submitLoginToYourBackend(proof),
  onError: error => showError(error.message),
  onEvent: event => recordCaptchaEvent(event),
})
// 页面卸载：captcha.destroy()
```

一个页面可以挂载多个实例，各自主题互不影响；弹窗 Teleport 到 body 后仍保留自己的配置。题图宽高和拼图位置不可由业务 CSS 单独拉伸，应使用 SDK 的响应式布局。

在 `onState` 的 `ready=true` 后调用 `open()`；初始化未完成时应让业务触发按钮保持禁用。题图内容、缺口坐标与旋转中心由服务端挑战决定，不属于前端外观配置。

## Flutter Android / iOS

```dart
final proof = await PinkCaptcha.verify(context, options: PinkCaptchaOptions(
  publicKey: platformPublicKey,
  apiBase: Uri.parse('https://pink.ya.wang/api/captcha'),
  scene: 'login', language: 'zh-CN', product: 'invisible',
  appearance: PinkCaptchaAppearance(
    theme: 'auto', primaryColor: '#167d50', borderRadius: 8,
  ),
  hapticFeedback: true, configRetries: 1,
  requestTimeout: const Duration(seconds: 12),
  onEvent: (event) => recordCaptchaEvent(event),
));
if (proof != null) await submitToYourBackend(proof.toJson());
```

Flutter 包内自带交互组件，配置直接生效；不需要业务另写 WebView 页面。`product=invisible` 避免在已经打开的原生弹窗内再点一次触发按钮。原生标题/错误支持中文和英文，题图内文案使用共享组件语言。

## iOS Swift

```swift
let options = RozeCaptchaOptions(
    baseURL: URL(string: "https://pink.ya.wang")!,
    publicKey: publicKey, scene: "login", clientId: Bundle.main.bundleIdentifier!,
    language: "zh-CN", product: "popup", hapticFeedback: true,
    appearance: .init(theme: "auto", primaryColor: "#167d50", borderRadius: 8)
)
let client = try RozeCaptchaClient(options: options)
let tokens = options.appearance.viewTokens(darkMode: isSystemDark)
// 原生界面：用 tokens 中的 backgroundColor/textColor/primaryColor/borderRadius 设置自己的视图。
let webOptions = options.webOptions()
// 如已有 WebView 容器：JSONSerialization 编码 webOptions，传给共享 RozeCaptcha.init。
```

## Android Kotlin

```kotlin
val options = RozeCaptchaOptions(
    baseUrl = "https://pink.ya.wang", publicKey = publicKey,
    scene = "login", clientId = context.packageName,
    language = "zh-CN", product = "popup", hapticFeedback = true,
    appearance = RozeCaptchaAppearance("auto", "#167d50", 8),
)
val client = RozeCaptchaClient(options)
val tokens = options.appearance.viewTokens(darkMode = isSystemDark)
// 将 tokens 绑定到自绘界面；或把 options.webOptions() 序列化后交给已有 WebView。
```

Swift/Kotlin 包提供请求层、配置校验、界面颜色与圆角参数，以及共享 Web SDK 参数映射，不会自动创建原生验证码弹窗。`viewTokens()` 返回普通数据：业务 UI 需绑定这些值，主题变化时重新计算；`hapticFeedback` 为 true 时才调用系统震动 API。使用共享 Web 组件则由它应用样式和触觉反馈。

## HarmonyOS NEXT

```ts
const client = new RozeCaptchaClient({
  baseUrl: 'https://pink.ya.wang', publicKey: publicKey,
  scene: 'login', clientId: bundleName, language: 'zh-CN',
  product: 'popup', hapticFeedback: true,
  appearance: new RozeCaptchaAppearance('auto', '#167d50', 8),
})
const tokens = client.viewTokens(isSystemDark)
const webOptions = client.webOptions()
```

ArkUI 自绘界面绑定 tokens；已有 ArkWeb 容器可使用 `webOptions` 初始化共享 Web 组件。SDK 不自动创建 ArkUI 验证弹窗。

## 微信 / 支付宝 / 抖音小程序

```js
const { createWechatCaptchaClient } = require('./sdk/wechat')
const client = createWechatCaptchaClient({
  baseUrl: 'https://pink.ya.wang/api/captcha', publicKey,
  scene: 'login', clientId: appId, language: 'zh-CN', product: 'popup',
  appearance: { theme: 'auto', primaryColor: '#167d50', borderRadius: 8 },
  challengeMode: 'smart', hapticFeedback: true,
  requestTimeoutMs: 12000, configRetries: 1,
})
this.setData({ captchaPresentation: client.presentation, captchaTokens: client.viewTokens(isSystemDark) })
// 在 WXML/AXML/TTML 中绑定背景色、文字色、圆角和主按钮颜色。
// 产品模式/交互事件由本地组件实现，不能仅设置数据后就认为已渲染验证码。
```

支付宝换用 `createAlipayCaptchaClient`，抖音换用 `createDouyinCaptchaClient`，其他配置相同。传输层不会绘制题图，需使用原生界面完成交互；不能将这些包宣传为内置完整 UI。

## TypeScript 自定义界面

```ts
import { createCaptchaClient } from './sdk/client-config'
const client = createCaptchaClient({
  baseUrl: 'https://pink.ya.wang', publicKey,
  scene: 'login', clientType: 'web', clientId: 'web',
  language: 'zh-CN', product: 'popup',
  appearance: { theme: 'auto', primaryColor: '#167d50', borderRadius: 8 },
  request: { timeoutMs: 12000, maxAttempts: 2 },
})
// 自绘界面读取 client.presentation。
// 共享 Web UI：RozeCaptcha.init({ container: '#captcha', ...client.webOptions(), onSuccess })
const challenge = await client.createChallenge()
```

`captcha-public.ts` 为生成的低阶 API；新代码优先使用 `client-config.ts`，避免将展示字段放进生成的请求结构。服务端 Node SDK 没有用户界面，不接受主题参数，仍负责消费 Proof 和判断联合验证结果。

## 必须由服务端处理

前端不能设置放行结果、跳过交互后伪造成功、降低风控阈值、延长凭证寿命或允许重放。只有业务后端核验得到 `decision=allow` 且 `verification=passed` 才执行登录/注册等操作。用户取消、网络超时、配置错误都不代表通过。

原生 WebView 接入应加载可信的随包/自托管 Web SDK，使用 JSON 序列化传参，限制页面跳转和消息来源，验证完整 Proof 后再交给业务后端；不要拼接未转义的 HTML/JS 或把 Server Key 放在客户端。
