# captcha-web-sdk 验证码前端 SDK



| 条目     |                                                              |
| -------- | ------------------------------------------------------------ |
| 兼容性   | Chrome、Firefox、Safari、Opera、主流手机浏览器、iOS 及 Android上的内嵌Webview |
| 框架支持 | H5、Angular、React、Vue2、Vue3、Next.js、React Native(WebView) |


## 本地打包

1. 安装依赖

   ```bash
   npm install
   ```

2. 执行 SDK 生产打包

   ```bash
   npm run build
   ```

3. 发包前检查

   ```bash
   npm run release:check
   ```

4. 打包产物在 `dist` 目录下，核心文件如下：

   - `dist/tac/js/tac.min.js`
   - `dist/tac/css/tac.css`
   - `dist/tac/images/*`

## 作为 SDK 接入

### 方式一: 浏览器 script 直引

```html
<link rel="stylesheet" href="/tac/css/tac.css" />
<script src="/tac/js/tac.min.js"></script>

<div id="captcha-box"></div>
<script>
  const tac = new TAC({
    requestCaptchaDataUrl: "/gen",
    validCaptchaUrl: "/check",
    bindEl: "#captcha-box"
  });
  tac.init();
</script>
```

### 方式二: npm 包方式引入

```bash
npm install captcha-web-sdk
```

```js
import TAC, { CaptchaWebSdk, CaptchaConfig } from "captcha-web-sdk";
import "captcha-web-sdk/style.css";

const tac = new TAC({
  requestCaptchaDataUrl: "/gen",
  validCaptchaUrl: "/check",
  bindEl: "#captcha-box"
});

tac.init();
```

说明:

- 默认导出为 `TAC`，等价于 `CaptchaWebSdk`
- 仍然兼容浏览器全局变量 `window.TAC` 和 `window.CaptchaConfig`
- 如果你还在使用历史的 `load.min.js` 初始化方式，本仓库当前打包产物依然兼容该模式

## 配置参数表

构造函数签名:

```js
const tac = new TAC(config, style);
```

其中:

- `config` 是业务配置，负责接口地址、请求头、回调函数等
- `style` 是展示配置，负责按钮、背景、logo、文案和字号等

### 1. config 参数表

| 参数名 | 类型 | 必填 | 默认值 | 说明 | 示例 |
| ------ | ---- | ---- | ------ | ---- | ---- |
| `bindEl` | `string` | 是 | 无 | 验证码挂载容器的选择器，必须能被 `document.querySelector` 找到 | `"#captcha-box"` |
| `requestCaptchaDataUrl` | `string` | 是 | 无 | 获取验证码数据的后端接口地址，SDK 默认用 `POST + application/json` 请求 | `"/captcha/gen"` |
| `validCaptchaUrl` | `string` | 是 | 无 | 提交验证码校验结果的后端接口地址，SDK 默认用 `POST + application/json` 请求 | `"/captcha/check"` |
| `requestHeaders` | `Record<string, string>` | 否 | `{}` | 发送到两个接口的公共请求头 | `{ Authorization: "Bearer xxx" }` |
| `timeToTimestamp` | `boolean` | 否 | `true` | 是否自动把请求数据中的 `Date` 转成时间戳 | `false` |
| `i18n` | `Partial<CaptchaI18nConfig>` | 否 | 默认内置文案 | 运行时错误提示和状态文案覆盖配置 | `{ tips_error: "校验失败" }` |
| `validSuccess` | `(res, captcha, tac) => void` | 否 | 默认打印日志并销毁窗口 | 验证成功回调 | `(res, c, tac) => tac.destroyWindow()` |
| `validFail` | `(res, captcha, tac) => void` | 否 | `tac.reloadCaptcha()` | 验证失败回调 | `(res, c, tac) => tac.reloadCaptcha()` |
| `btnRefreshFun` | `(event, tac) => void` | 否 | `tac.reloadCaptcha()` | 点击刷新按钮后的回调 | `(e, tac) => tac.reloadCaptcha()` |
| `btnCloseFun` | `(event, tac) => void` | 否 | `tac.destroyWindow()` | 点击关闭按钮后的回调 | `(e, tac) => tac.destroyWindow()` |

### 2. style 参数表

| 参数名 | 类型 | 必填 | 默认值 | 说明 | 示例 |
| ------ | ---- | ---- | ------ | ---- | ---- |
| `btnUrl` | `string \| null` | 否 | 内置 base64 按钮图 | 滑块按钮图片地址 | `"https://cdn.example.com/btn.png"` |
| `bgUrl` | `string \| null` | 否 | 无 | 验证码弹窗背景图地址 | `"https://cdn.example.com/bg.jpg"` |
| `logoUrl` | `string \| null` | 否 | 内置 logo | 底部 logo 地址，传 `null` 可隐藏 logo | `null` |
| `moveTrackMaskBgColor` | `string` | 否 | `#89d2ff` | 滑动轨道填充背景色 | `"#f7b645"` |
| `moveTrackMaskBorderColor` | `string` | 否 | `#0298f8` | 滑动轨道边框颜色 | `"#ef9c0d"` |
| `i18n` | `Partial<CaptchaI18nConfig>` | 否 | 默认内置文案 | UI 文案和字号覆盖配置 | `{ slider_title: "请拖动滑块" }` |

### 3. i18n 参数表

支持 `%s` 占位符的参数:

- `unknown_captcha_type`
- `tips_success`

#### 3.1 通用提示文案

| 参数名 | 默认值 | 用途 |
| ------ | ------ | ---- |
| `required_bind_el` | `[TAC] 必须配置 [bindEl] 用于将验证码绑定到该元素上` | 缺少 `bindEl` 时抛出的错误文案 |
| `required_request_captcha_data_url` | `[TAC] 必须配置 [requestCaptchaDataUrl] 请求验证码接口` | 缺少生成接口地址时抛出的错误文案 |
| `required_valid_captcha_url` | `[TAC] 必须配置 [validCaptchaUrl] 验证验证码接口` | 缺少校验接口地址时抛出的错误文案 |
| `invalid_captcha_data` | `[TAC] 后台验证码接口数据错误` | 获取验证码接口返回结构异常时的错误文案 |
| `unknown_captcha_type` | `[TAC] 未知的验证码类型[%s]` | 后端返回未知验证码类型时的错误文案 |
| `default_valid_success_log` | `验证码校验成功，请重写 [config.validSuccess] 方法，用于自定义逻辑处理` | 未自定义成功回调时的默认日志文案 |
| `tips_success` | `验证成功,耗时%s秒` | 验证成功的提示文案 |
| `tips_error` | `验证失败，请重新尝试!` | 普通失败提示文案 |
| `tips_4001` | `验证码已失效，请刷新后重试!` | 验证码失效提示文案 |
| `tips_network_error` | `网络异常，请稍后重试!` | 网络异常提示文案 |

#### 3.2 UI 文案与字号

| 参数名 | 默认值 | 用途 |
| ------ | ------ | ---- |
| `modal_title` | `请完成安全验证` | 弹窗头部主标题 |
| `modal_subtitle` | `验证通过后即可继续操作` | 弹窗头部副标题 |
| `close_button_aria_label` | `关闭验证` | 关闭按钮无障碍文案 |
| `slider_title` | `拖动滑块完成拼图` | 历史滑块任务说明文案，当前默认 UI 不再展示，保留兼容 |
| `slider_action_hint` | `按住滑块，拖动完成上方拼图` | 滑块验证码轨道提示文案 |
| `concat_title` | `拖动滑块完成拼图` | 历史拼接任务说明文案，当前默认 UI 不再展示，保留兼容 |
| `concat_action_hint` | `按住滑块，拖动完成上方拼图` | 拼接验证码轨道提示文案 |
| `image_click_title` | `请依次点击:` | 点选验证码标题 |
| `image_click_confirm_text` | `确定` | 点选验证码确认按钮文案 |
| `rotate_title` | `拖动滑块完成拼图` | 历史旋转任务说明文案，当前默认 UI 不再展示，保留兼容 |
| `rotate_action_hint` | `按住滑块，旋转至正确角度` | 旋转验证码轨道提示文案 |
| `disable_title` | `当前验证码不可用` | 禁用态标题 |
| `disable_default_message` | `接口异常` | 禁用态默认提示 |
| `slider_title_size` | `15px` | 滑块验证码标题字号 |
| `concat_title_size` | `15px` | 拼接验证码标题字号 |
| `image_click_title_size` | `20px` | 点选验证码标题字号 |
| `rotate_title_size` | `15px` | 旋转验证码标题字号 |
| `disable_title_size` | `15px` | 禁用态标题字号 |

#### 3.3 已废弃但兼容保留的 i18n 字段

| 参数名 | 当前状态 | 说明 |
| ------ | -------- | ---- |
| `close_text` | 已废弃，但仍兼容 | 会自动映射到 `close_button_aria_label`，建议改用新字段 |
| `refresh_text` | 已废弃，当前忽略 | 刷新按钮已改为纯 icon 交互，不再显示该文案 |

### 4. 高级能力: 请求链 requestChain

如果你要在请求前后统一加签名、改请求体、打印日志，可以使用 `CaptchaConfig` 实例上的请求链能力。

| 方法名 | 参数 | 说明 |
| ------ | ---- | ---- |
| `addRequestChain(chain)` | `{ preRequest?, postRequest? }` | 在链尾追加一个处理器 |
| `insertRequestChain(index, chain)` | `index, chain` | 在指定位置插入处理器 |
| `removeRequestChain(index)` | `index` | 删除指定位置的处理器 |

请求链回调签名:

| 回调 | 签名 | 说明 |
| ---- | ---- | ---- |
| `preRequest` | `(type, requestParam, config, captcha, tac) => boolean \| void` | 请求发送前执行，可修改 `requestParam` |
| `postRequest` | `(type, requestParam, response, config, captcha, tac) => boolean \| void` | 请求完成后执行，可统一处理返回结果 |

`type` 可能值:

- `requestCaptchaData`
- `validCaptcha`

`requestParam` 结构:

```js
{
  url: "/captcha/gen",
  method: "POST",
  headers: {
    "Content-Type": "application/json;charset=UTF-8"
  },
  data: {}
}
```

### 5. 最小可用配置

```js
import TAC from "captcha-web-sdk";
import "captcha-web-sdk/style.css";

const tac = new TAC({
  bindEl: "#captcha-box",
  requestCaptchaDataUrl: "/captcha/gen",
  validCaptchaUrl: "/captcha/check"
});

tac.init();
```

### 6. 完整配置示例

```js
import TAC from "captcha-web-sdk";
import "captcha-web-sdk/style.css";

const tac = new TAC(
  {
    bindEl: "#captcha-box",
    requestCaptchaDataUrl: "/captcha/gen",
    validCaptchaUrl: "/captcha/check",
    requestHeaders: {
      Authorization: "Bearer your-token"
    },
    timeToTimestamp: true,
    i18n: {
      tips_error: "验证失败，请重新再试一次",
      tips_network_error: "网络开小差了，请稍后重试"
    },
    validSuccess: (res, captcha, tacInstance) => {
      tacInstance.destroyWindow();
      console.log("校验成功:", res);
    },
    validFail: (res, captcha, tacInstance) => {
      tacInstance.reloadCaptcha();
    },
    btnRefreshFun: (event, tacInstance) => {
      tacInstance.reloadCaptcha();
    },
    btnCloseFun: (event, tacInstance) => {
      tacInstance.destroyWindow();
    }
  },
  {
    logoUrl: null,
    btnUrl: "https://cdn.example.com/captcha/btn.png",
    bgUrl: "https://cdn.example.com/captcha/bg.jpg",
    moveTrackMaskBgColor: "#f7b645",
    moveTrackMaskBorderColor: "#ef9c0d",
    i18n: {
      slider_title: "拖动滑块完成验证",
      slider_action_hint: "向右拖动完成验证",
      modal_title: "请完成下列验证后继续",
      modal_subtitle: "验证成功后会自动返回当前流程",
      close_button_aria_label: "关闭验证码弹窗",
      image_click_confirm_text: "确认"
    }
  }
);

tac.init();
```

## 国际化配置

现在支持两种国际化覆盖方式：

- `config.i18n`: 适合接口提示、运行时错误等文案
- `style.i18n`: 适合按钮、标题、禁用态等 UI 文案

两者都会和默认文案自动合并，`style.i18n` 优先级最高。

```js
import TAC from "captcha-web-sdk";
import "captcha-web-sdk/style.css";

const tac = new TAC(
  {
    requestCaptchaDataUrl: "/gen",
    validCaptchaUrl: "/check",
    bindEl: "#captcha-box",
    i18n: {
      required_bind_el: "[TAC] bindEl is required",
      required_request_captcha_data_url: "[TAC] requestCaptchaDataUrl is required",
      required_valid_captcha_url: "[TAC] validCaptchaUrl is required",
      invalid_captcha_data: "[TAC] Invalid captcha payload",
      unknown_captcha_type: "[TAC] Unknown captcha type[%s]",
      tips_success: "Verified in %s seconds",
      tips_error: "Verification failed, please try again",
      tips_4001: "Captcha expired, please refresh and retry",
      tips_network_error: "Network error, please try again later"
    },
    validSuccess: (res, c, tacInstance) => {
      tacInstance.destroyWindow();
      console.log("captcha ok", res);
    }
  },
  {
    i18n: {
      modal_title: "Please complete the verification",
      modal_subtitle: "Continue after the verification passes",
      close_button_aria_label: "Close verification",
      slider_title: "Drag the slider to complete the puzzle",
      slider_action_hint: "Hold the handle and drag to complete the puzzle",
      concat_title: "Drag the slider to complete the puzzle",
      concat_action_hint: "Hold the handle and drag to complete the puzzle",
      image_click_title: "Click in order:",
      image_click_confirm_text: "Confirm",
      rotate_title: "Drag the slider to rotate",
      rotate_action_hint: "Hold the handle and rotate to the correct angle",
      disable_title: "Captcha unavailable",
      disable_default_message: "Request failed"
    }
  }
);

tac.init();
```

## 最近变更

本次 SDK UI 与国际化做了以下调整：

- 统一滑块类验证码的信息层级：头部 `modal_title / modal_subtitle` 作为主标题区，内部 `slider_title / concat_title / rotate_title` 调整为图片区上方的任务说明
- 重做 `SLIDER / CONCAT / ROTATE` 三类滑块轨道，统一为更接近系统控件的浅底轨道、实体把手和完整蓝色进度底块
- 滑块类验证码已移除图片区上方的额外任务说明行，避免与头部主标题重复；`slider_title / concat_title / rotate_title` 当前仅兼容保留
- 新增弹窗头部文案国际化：`modal_title`、`modal_subtitle`
- 新增关闭按钮无障碍文案国际化：`close_button_aria_label`
- 新增滑块轨道提示国际化：`slider_action_hint`、`concat_action_hint`、`rotate_action_hint`
- 刷新按钮调整为纯 icon 形式，`refresh_text` 不再展示
- 刷新按钮点击时增加旋转动画反馈
- 关闭按钮与刷新按钮都改为纯 icon 交互样式
- 弹窗整体圆角和字号已收敛，视觉更贴近用户端业务场景

兼容性说明：

- 旧字段 `close_text` 仍会自动兼容到 `close_button_aria_label`
- 旧字段 `refresh_text` 不会报错，但当前 UI 已不再使用该文案

## 多框架示例

完整示例见 [examples/frameworks.md](./examples/frameworks.md)

- Next.js: 仅在 Client Component 中初始化，CSS 放到 `app/layout.tsx` 或 `pages/_app.tsx`
- React: 在 `useEffect` 中初始化，在清理函数里销毁
- Vue3: 在 `onMounted` 中初始化，在 `onBeforeUnmount` 中销毁
- Vue2: 在 `mounted` 中初始化，在 `beforeDestroy` 中销毁
- React Native: 通过 `react-native-webview` 加载 H5 页面或内联 HTML，不支持直接在原生运行时中挂载 DOM 版 SDK

## 发布到 npm

### 首次发布前

1. 登录 npm

   ```bash
   npm login
   ```

2. 确认包名还未被占用

   ```bash
   npm view captcha-web-sdk version
   ```

   如果返回 `404 Not Found`，说明当前包名还没有被注册。

3. 执行发包前检查

   ```bash
   npm run release:check
   ```

### 手动发布

```bash
npm publish
```

### CI 自动发布

仓库内已提供 GitHub Actions 工作流：

- 推送 `v*` 标签时自动发布
- 也支持手动触发 `workflow_dispatch`

如果你的源码仓库托管在 Gitee，`.github/workflows` 不会自动执行，需要将同样的步骤迁移到 Gitee Go 或其他 CI 平台。

使用前需要在仓库 Secrets 中配置：

- `NPM_TOKEN`

示例发布命令：

```bash
git tag v1.0.0
git push origin v1.0.0
```


## 旧版 `load.min.js` 兼容接入

如果你还在使用历史初始化方式，也可以继续通过 `window.initTAC` 接入。推荐优先迁移到上文的 npm / `new TAC(...)` 方式，便于类型提示、打包优化和长期维护。

```html
<div id="captcha-box"></div>
<script src="/tac/load.min.js"></script>
<script>
  const config = {
    requestCaptchaDataUrl: "/gen",
    validCaptchaUrl: "/check",
    bindEl: "#captcha-box",
    i18n: {
      tips_error: "验证失败，请重试"
    }
  };

  const style = {
    logoUrl: null,
    i18n: {
      modal_title: "请完成安全验证",
      modal_subtitle: "验证通过后即可继续操作",
      close_button_aria_label: "关闭验证",
      slider_action_hint: "按住滑块，拖动完成上方拼图"
    }
  };

  window.initTAC("/tac", config, style).then((tac) => {
    tac.init();
  });
</script>
```
