# @16x/webphone-sdk 中文文档

规范地址：https://npmcn.com/package/%4016x/webphone-sdk/

版本：3\.3\.2
npm 元数据更新时间：2026-10-26T02:26:42.791Z
中文译文更新时间：2026-11-06T08:18:56.545Z
软件包许可：UNLICENSED

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%4016x/webphone-sdk
原文对照：https://npmcn.com/package/%4016x/webphone-sdk/?lang=original
项目主页：https://www.npmjs.com/package/@16x/webphone-sdk

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：2.5 MB，93 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：1 个
- 首次发布：2026-09-20T15:46:18.138Z
- 最新版本发布：2026-10-26T02:26:27.675Z
- 近 30 天下载量：3915（2026-10-05 至 2026-11-03，npm 官方下载统计）
- 已发布版本数：46（近 12 个月 46 个）

## 中文简介

`@16x/webphone-sdk` 是浏览器端 WebPhone SDK。客户只需提供服务端取票接口，并在前端配置 `createTokenSessionProvider`。SDK 会在内部获取坐席账号、创建会话、登录 SIP、刷新凭证并请求坐席状态。

## 安装

```sh
npm install @16x/webphone-sdk
```

也可以使用：`pnpm add @16x/webphone-sdk`、`yarn add @16x/webphone-sdk`、`bun add @16x/webphone-sdk`

## README

# CCBar WebPhone SDK

`@16x/webphone-sdk` 是浏览器端 WebPhone SDK。客户只需提供服务端取票接口，并在前端配置 `createTokenSessionProvider`。SDK 会在内部获取坐席账号、创建会话、登录 SIP、刷新凭证并请求坐席状态。

## 安装

```bash
npm install @16x/webphone-sdk
```

公开包地址：[npmjs](https://www.npmjs.com/package/@16x/webphone-sdk)。

## 最简接入

### 1\. 服务端实现取票接口

浏览器通过客户自己的 `POST /get-token` 接口取短期 fs token。服务端从已登录用户的会话中取得分机号，使用服务端环境变量中的 APP Key 和 security（APP Secret）签名调用 `POST /openapi/v1/token/fs`，然后返回上游的 token 和 expires。不要接受浏览器提交的 APP Key、security 或任意分机号。

取票请求与签名示例见[会话服务指南](https://npmcn.com/package/%4016x/webphone-sdk/docs/guides/session-service.md)。

### 2\. 创建客户端并连接

```ts
import { CCBarClient, createTokenSessionProvider } from '@16x/webphone-sdk';

const client = new CCBarClient({
  platform: 'web',
  sessionProvider: createTokenSessionProvider({
    // 请向商务经理或对接人获取 HTTP API 网关地址
    host: 'https://api.example.com',
    // 请向商务经理或对接人获取完整的 SIP WebSocket 地址
    sipWsUrl: 'wss://sip.example.com/api/fs/sip-ws',
    // 这是客户需要实现的唯一服务端方法
    getToken: async () => {
      const response = await fetch('/get-token', {
        method: 'POST',
        credentials: 'include',
      });
      if (!response.ok) throw new Error('获取 token 失败');
      return response.json();
    },
  }),
});

await client.initialize();
await client.connect();
```

`host` 用于 SDK 内部请求坐席账号和状态接口；`sipWsUrl` 是完整的 SIP WebSocket 地址。`getToken` 可返回 `{ token, expires }`，其中 `expires` 是秒数；也可返回 `{ token, expiresAt }`（Unix 秒）或 `{ token, expiresAtMs }`（Unix 毫秒）。SDK 按字段名解释单位，不会根据数值大小猜测秒或毫秒。

坐席账号响应中的 `expiresIn` / `expires_in` 是账号数据刷新间隔（秒），不代表当前 SIP WebSocket 必须断开重建。刷新时 SDK 会更新 SIP 账号密码及 JsSIP 自动重连使用的最新 token；当前 WSS 保持连接，后续底层重连会使用新凭证。

请向商务经理或对接人获取 HTTP API 网关地址 `host` 和完整的 SIP WebSocket 地址 `sipWsUrl`。`host` 用于坐席账号/状态接口；`sipWsUrl` 应包含 `wss://` 协议和服务端提供的路径。另请获取 APP Key、security（仅供客户服务端实现 `getToken`，不要放进浏览器代码）以及话单查询/推送接口资料；话单由客户业务系统另行集成，SDK 不提供查询或推送能力。

### 3\. 拨打、控制和挂断

```ts
const call = await client.dial({ destination: '9196' });
await call.mute();
await call.unmute();
await call.hold();
await call.resume();
await call.hangup();
```

### 4\. 传递 userData 并关联话单

拨打时可以通过 `userdata` 传递业务关联信息。SDK 会把它放进 SIP `INVITE` 请求的 `X-User-Data` header，供平台侧读取：

```ts
const userdata = encodeURIComponent(JSON.stringify({
  orderId: 'ORDER-20261003-001',
  source: 'crm',
}));

const call = await client.dial({
  destination: '9196',
  userdata,
});
```

`userdata` 必须是字符串；未传、空字符串或纯空白不会发送该 header。值只支持可见 ASCII，不能包含换行、制表符或未编码的中文；传入不合规内容会以 `CALL_INVALID_USERDATA` 拒绝拨打。像上例一样先对 JSON 做 `encodeURIComponent()`，平台侧收到 `X-User-Data` 后再 `decodeURIComponent()` 和解析即可。不要放入密码、token 等敏感信息，因为 SIP header 可能经过或记录在平台网络设备中。

SDK 负责把 `userdata` 透传到 SIP 请求。若要让它出现在话单推送或话单查询结果中，平台服务端还需要读取 `X-User-Data`，将其解析并保存到话单字段；请与平台侧确认字段映射和推送、查询接口是否都返回该字段。SDK 不会直接写入或查询话单。

### 5\. 查看连接状态、坐席状态和拨打结果

通过 `client.on()` 监听连接状态。连接注册成功和连接状态是不同事件：

```ts
const offConnection = client.on('connection.stateChanged', ({ state }) => {
  // offline | connecting | connected | reconnecting | failed
  console.log('SIP connection:', state);
});

const offRegistered = client.on('connection.registered', () => {
  console.log('SIP registered');
});
```

调用 `setAgentStatus()` 修改坐席状态；`agent.statusChanged` 在状态接口成功后发出：

```ts
const offAgentStatus = client.on('agent.statusChanged', ({ status }) => {
  console.log('Agent status:', status); // available | break | offline
});

await client.setAgentStatus('available');
```

坐席状态表示坐席在业务平台上的工作状态，不表示 SIP WebSocket 是否已连接，也不表示当前通话状态：

| 坐席状态 | 含义 |
| --- | --- |
| `available` | 坐席空闲/就绪；是否能接听新呼叫仍由平台的队列和路由策略决定。 |
| `break` | 坐席暂时休息或暂停接听，平台侧对应 `On Break`。 |
| `offline` | 坐席在业务平台登出，平台侧对应 `Logged Out`。它本身不关闭 SIP 连接；如需断开连接，还要调用 `client.disconnect()`。 |

因此，`client.setAgentStatus('offline')` 和 `client.disconnect()` 是两个独立操作：前者更新平台坐席状态，后者断开 SDK 的 SIP 连接。反过来，连接掉线也不一定会自动把坐席状态改为 `offline`。

`client.dial()` 成功返回 Call 对象不代表通话已接通。监听 `call.stateChanged` 并检查 `to` 是否为 `active`；结束或失败分别通过 `call.ended`、`call.failed` 通知：

```ts
const offCallState = client.on('call.stateChanged', ({ callId, from, to }) => {
  console.log(`Call ${callId}: ${from} -> ${to}`);
  // new | dialing | ringing | connecting | active | held | ended | failed
});

const offCallEnded = client.on('call.ended', ({ callId, reason }) => {
  console.log('Call ended:', callId, reason);
});

const offCallFailed = client.on('call.failed', ({ callId, error }) => {
  showCallError(error.code);
});
```

拨号发起阶段的错误会让 `dial()` 拒绝；接通后的通话错误通过 `call.failed` 事件报告。npm SDK 与 CCBar Script 使用相同的字符串 `code` 和可读 `message`。若后端返回数字错误码，SDK 将其保留在可选的 `platformCode` 中：

```ts
import { CCBarError } from '@16x/webphone-sdk';

try {
  await client.dial({ destination: '9196' });
} catch (error) {
  if (error instanceof CCBarError) {
    showCallError(error.message);
    reportSafeError({ code: error.code, errno: error.errno, source: error.source, platformCode: error.platformCode });
  } else {
    showCallError('拨号失败，请稍后重试。');
  }
}
```

客户代码应使用 `error.code` 做程序判断、将 `error.message` 显示给用户；`platformCode` 仅用于排查后端响应，不应作为 SDK 业务分支的依据。

每个错误还带数字编号 `errno` 和归属 `source`，用来判断该找谁排查。`errno` 首位即归属：

| 首位 | `source` | 含义 |
| --- | --- | --- |
| 1 | `client` | 本机或调用方（参数、麦克风、浏览器能力等） |
| 2 | `platform` | 平台接口与注册；有平台业务码时为 `2000 + 业务码`，否则有 HTTP 错误状态时为 `2000 + 状态码` |
| 3 | `network` | 网络（断网、超时、信令连接中断） |
| 4 | `line` | 线路（`4000 + SIP 码`，如 503 → `4503`） |
| 5 | `callee` | 被叫（`5000 + SIP 码`，如忙线 486 → `5486`、拒接 603 → `5603`） |
| 6 | `platform` | 平台拦截了呼叫（欠费、黑名单、频次、线路未分配等），编号与话单结束码一致（如 `6033`） |

呼叫失败时还可能带 `sipCode`（SIP 响应码）、`q850`（失败响应中的 Q.850 原因值）和 `platformFailCode`（平台拦截结束码原值）。平台拦截的 `code` 为 `PLATFORM_REJECTED`（3.2.13 新增，此前报为 `CALL_REJECTED`），`message` 会给出具体原因，例如「平台拦截了本次呼叫：欠费（编号 6033）」。其余 `code` 取值不变。错误码清单和处理建议见 [错误码说明](https://npmcn.com/package/%4016x/webphone-sdk/docs/customer/webphone/05-error-codes.md)。

通话状态含义如下。`ended` 和 `failed` 都是终态；一个 Call 到达终态后不会再恢复到其他状态。

| 状态 | 含义 |
| --- | --- |
| `new` | SDK 已创建 Call 对象，呼叫尚未开始处理；通常是很短暂的初始状态。 |
| `dialing` | SDK 已发起呼叫，正在等待对端响应。 |
| `ringing` | 对端正在振铃，或呼叫流程已进入振铃/早期进展阶段。 |
| `connecting` | 呼叫已应答，正在完成媒体协商或建立音频连接；此时还不能当作通话已接通。 |
| `active` | 通话已接通，可以进行通话控制。 |
| `held` | 通话已保持；调用 `call.resume()` 可恢复到 `active`。 |
| `ended` | 通话已正常结束、被挂断或被拒接。 |
| `failed` | 呼叫建立或通话过程中发生失败；查看 `call.failed` 事件中的错误码。 |

`client.on()` 返回取消监听函数。页面或组件卸载时调用对应的 `off...()`，或在客户端销毁时统一清理监听器。

## 网络和安全要求

-   页面须运行在 HTTPS 或 localhost，并取得浏览器麦克风权限。
-   网关须允许页面的 Origin 和客户出口 IP 调用坐席账号与状态接口；这两项与客户自己的 get-token 接口是分开的，请在上线前确认 CORS 和 IP 白名单。
-   APP Key 和 security 只保存在服务端。浏览器运行时会持有短期 fs token 和 SIP 凭证；不要记录含 token 的 WSS URL、Authorization 请求头或 SIP 密码。
-   按客户系统的登录态调用 get-token，避免浏览器自行指定分机号。

## 连接生命周期

建议在系统账号登录成功或进入拨打列表页时调用 `client.connect()`。退出账号或离开拨打列表页时，按需先调用 `client.setAgentStatus('offline')`，再调用 `client.disconnect()`。客户端实例不再使用时调用 `client.dispose()` 释放监听器、定时器、连接和媒体资源。重复调用正在连接或已连接的 `connect()` 会复用当前连接。

## 常用 API

-   `client.initialize()`、`client.connect()`、`client.disconnect()`、`client.dispose()`
-   `client.dial({ destination, userdata? })`
-   `client.setAgentStatus('available' | 'break' | 'offline')`
-   `call.mute()`、`call.unmute()`、`call.hold()`、`call.resume()`、`call.hangup()`
-   `client.on(event, handler)`、`client.getDiagnostics()`

详细 API 和完整事件说明见[客户端 API 文档](https://npmcn.com/package/%4016x/webphone-sdk/docs/api/client.md)；服务端取票签名示例见[会话服务指南](https://npmcn.com/package/%4016x/webphone-sdk/docs/guides/session-service.md)。

## 开发与验证

```bash
npm run lint
npm run typecheck
npm test
npm run pack:check
```

## License

Copyright 16X. All rights reserved.

Private customer distribution only. No redistribution or sublicensing without written permission.

## Live acceptance (maintainers)

Real-call acceptance is opt-in and runs outside `npm test`. Copy `scripts/live-call-acceptance.env.example` to a local `.env` file, fill it with a dedicated test account, and start a Chromium-based browser with remote debugging enabled at `CCBAR_LIVE_CDP_URL`. The runner requests microphone permission through CDP and uses only routes 9196 and 9199.

```sh
npm run test:live
```

The runner verifies token signing, account lookup, SIP registration, seat status, repeated `connect()`, disconnect/reconnect, call controls, credential failures, delayed/lost HTTP requests, browser network loss, and malformed/unreachable STUN behavior. Repeated-connect assertions compare SDK token, account, session, connect, and registration-event counts; they do not claim server-side REGISTER packet counts. It measures dial-to-active time against `CC_DIAL_ACTIVE_SLO_MS`; each sample must meet the threshold. JSON output contains scenario, stage, status codes, elapsed times, and SDK resource counts; it omits credentials, tokens, raw errors, and WebSocket URLs. Live tests require the gateway Origin/IP allowlists, browser microphone permission, reachable WSS, and both test routes.

## 常见问题

### @16x/webphone-sdk 是什么？

@16x/webphone-sdk 是浏览器端 WebPhone SDK。客户只需提供服务端取票接口，并在前端配置 createTokenSessionProvider。SDK 会在内部获取坐席账号、创建会话、登录 SIP、刷新凭证并请求坐席状态。

### 如何安装 @16x/webphone-sdk？

在项目目录运行 npm install @16x/webphone-sdk，或使用 pnpm add / yarn add / bun add。

### @16x/webphone-sdk 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @16x/webphone-sdk 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @16x/webphone-sdk 可以商用吗？

@16x/webphone-sdk 使用 UNLICENSED 许可。作者声明为 UNLICENSED，即不授予公开使用许可（与公共领域的 Unlicense 不同）。使用前需获得作者授权。以上为概括，不构成法律意见，请以许可原文为准。

### @16x/webphone-sdk 的下载量有多少？

根据 npm 官方下载统计，@16x/webphone-sdk 在 2026/10/05 – 2026/11/03 这 30 天内被下载 3,915 次，日均约 131 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @16x/webphone-sdk 还在维护吗？

最新版本 3\.3\.2 发布于 2026/10/26（12 天前）；至今共发布 46 个版本，其中过去 12 个月发布了 46 个；首次发布于 2026/09/20。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

