@16x/webphone-sdk 3.1.11 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,215 +1,181 @@
1
- # Changelog
2
-
3
- ## 3.1.11 - 2026-09-30
4
-
5
- - **破坏性(legacy 会话来源)**:`createLegacySessionProvider` 的 `sipWsUrl` 改为**必填**,软电话地址
6
- 由接入方传完整地址(`wss://` / `ws://`)。SDK 不再按坐席账号的 `domain` / `wssPort`(缺省 7443)
7
- 与固定路径 `/api/fs/sip-ws` 拼装,`sipWsPath` 选项删除;缺 `sipWsUrl`、传相对路径或 `http(s)://`
8
- 地址时,建会话(`createSession` / `refreshSession`)抛 `CONFIG_INVALID`(**构造本身不抛**)。
9
- - 地址上已有的查询参数保留,SDK 仍把 fs token 挂成 `?token=`(`attachTokenToWss: false` 可关掉)。
10
- - 只影响 `@16x/webphone-sdk/legacy` 的调用方;服务端下发完整 `transport.wssUrl` 的那条链不受影响。
11
-
12
- ## 3.1.10 - 2026-09-24
13
-
14
- - **会话入口自动认密文**:接入方把老平台 `seat/account/get` 返回的 `password` 密文**直接**塞进
15
- `session.sip.registerTicket` 时,SDK 现在会在会话进入的那一处(`createSessionProviderApi`)
16
- 先用老平台内置 key/iv 解一次,解不出来就原样放行 —— 服务端因此可以只做转发,
17
- 不必再自己调 `decryptSipPassword`。
18
- - 判定条件收得很紧,避免把明文密码当密文解坏:合法 Base64、长度是 4 的倍数、解码后是 16 字节的整数倍、
19
- AES 解出的文本没有替换符与控制字符。固定种子采样(字母数字 8~40 位、32 位十六进制、
20
- 以及「刚好是 16 字节 Base64 带 `=` 填充」这种最危险的形状)实测没有误判。
21
- - 只认老平台内置的 key/iv(即 `decryptSipPassword` 的默认值):环境不同、key/iv 被改过时,
22
- 自动判定会失败并退回原值,这种环境仍需接入方自己用 `decryptSipPassword(cipher, { key, iv })`
23
- 解好再放进会话。
24
- - 非破坏性:已经自己解好的接入方(含 `createLegacySessionProvider`)拿到的会话对象原样不变;
25
- `decryptSipPassword` 的实现搬到 `src/crypto/legacySipTicket.ts`,公开导出与行为保持不变。
26
-
27
- ## 3.1.9 - 2026-09-24
28
-
29
- > 同日先发过 `3.8.1`(内容与本文完全相同),按项目版本线改成 3.1.9(3.1.8 的补丁位)重新发布;3.8.1 不再维护。
30
-
31
-
32
- - **坐席状态接口直接用会话里的那张票**:老平台把 fs token 拼在软电话地址上(`wss://…/api/fs/sip-ws?token=…`),
33
- 同一个 token 也用于 HTTP 接口的 `Authorization`。`client.setAgentStatus()` 现在把这个地址带给会话来源
34
- (`SessionProvider.setAgentStatus(request, context?)`,新增可选参数 `AgentStatusContext.wssUrl`),
35
- 订阅来源可以直接拿它去打 `seat/account/get` 与 `seats/set-status`,**不再单独换一次票**。
36
- - `createLegacySessionProvider` 的 `setAgentStatus` 按这个顺序取票:
37
- 会话地址里的 token → 最近见过的那个(退签时会话已经断开)→ `getToken`/`tokenUrl` 重新换票(与旧行为一致)。
38
- 它自己在 `createSession` 里拼过 WSS 地址时也会记住那张票;`invalidateToken()` 会把它一起清掉。
39
- - 非破坏性:`setAgentStatus` 的第二个参数是可选的,旧接入方(不传 context、只用 `getToken`/`tokenUrl`)行为不变。
40
-
41
- ## 4.1.0 - 2026-09-24
42
-
43
- - 新增 `createLegacySessionProvider`(只在 `@16x/webphone-sdk/legacy` 子入口导出):
44
- 把老平台「取 token → `POST {host}/openapi/token/v1/seat/account/get` → AES-128-CBC/Pkcs7 解 SIP 密码
45
- → 拼 WebPhoneSession」以及坐席状态 `POST {host}/openapi/token/v1/seats/set-status` 这一整段交给 SDK,
46
- 页面只需提供换票口子:`getToken()` 函数或 `tokenUrl` 地址(**两者都不会让 SDK 接触 API SECRET**)。
47
- AES key/iv 默认用老平台固定值(与旧版 `ccbar.js` 一致),可用 `aesKey`/`aesIv` 覆盖;
48
- 平台返回的 `iceServers` 原样透传(含 TURN),缺失时才按 `turnIp` 拼 STUN;
49
- `refreshSession` 等于整条链路重跑(换一次 SIP 密码)。
50
- - 新增 `decryptSipPassword(cipherText, { key?, iv? })`(同样只在 `/legacy` 导出):
51
- 解 `seat/account/get` 返回的 `password`/`registerPassword`(AES-128-CBC/Pkcs7、Base64 密文)。
52
- 服务端(Node 18+)与页面都能用;默认平台内置 key/iv,可覆盖;失败原因在 `error.cause`。
53
- `createLegacySessionProvider` 内部复用同一个实现。
54
- - 新增错误码 `LEGACY_PLATFORM_UNREACHABLE`(网络/超时/非 JSON)与 `LEGACY_PLATFORM_REJECTED`
55
- (平台业务错误,平台的 code 在 `error.serverCode` 上)。
56
- - 非破坏性:`sessionProvider` 契约与服务端拼会话的用法完全不变。
57
-
58
- ## 4.0.0 - 2026-09-24
59
-
60
- **破坏性变更:移除「SDK 自己换会话」这条能力,会话只能由接入方服务端下发。**
61
-
62
- - 移除 `tokenProvider` 选项与 `baseUrl` 上的会话职责:SDK 不再请求平台会话接口
63
- (`POST /webphone/v1/sessions`、`/sessions/{id}/refresh`、`DELETE /sessions/{id}`、
64
- `/capabilities`、`/agents/me/status`)。
65
- - 删除 `WebPhoneApi`、`TokenManager` 及其公开导出(`TokenProvider`、`TokenRequest`、
66
- `TokenProviderResult`、`WebPhoneApiOptions`、`RefreshSessionRequest`、`CapabilitiesResponse`)。
67
- - `sessionProvider` 成为**唯一**会话来源;未提供时构造抛 `CONFIG_INVALID`。
68
- - `connect({ extension })` 的 `extension` 参数删除(它只用于向平台换取 token):
69
- 改成 `connect()`,坐席身份由接入方服务端按登录态决定。
70
- - `sessionProvider.invalidateToken()` 保留:接入方可以在终态失败时清掉自己的服务端缓存。
71
- - **迁移**:原来用 `tokenProvider` 的接入方,改成在自己服务端拼好 `WebPhoneSession`
72
- (拿坐席账号 → 解出 SIP 密码 → 拼 WSS 地址),再用 `sessionProvider.createSession` 交给 SDK。
73
- 参考实现:客户 demo 仓库的 `server/get-session.js`(fs token → 坐席账号 → AES-128-CBC 解密 → 拼 WSS)。
74
- `docs/api/client.md` 与 `docs-site` 的「会话来源」章节已按新契约重写。
75
- - 仓库结构:`examples/` 下不再提供前端 demo 与 Node/Go 签发服务(它们只服务于旧流程),
76
- 只保留 `examples/network-check`(网络预检页);客户包相应缩减为
77
- `ccbar-sdk-network-check` + `ccbar-sdk-technical-integration-package`。
78
-
79
- ## 3.1.8 - 2026-09-23
80
-
81
- - **修 3.1.7 引入的回归:来电会被立刻拒掉(对端收到 480)。**
82
- 3.1.7 新加的 `mediaConnected` 订阅在「会话还没有 PeerConnection」时崩了:JsSIP 的
83
- `session.connection` 在 PC 建好之前返回的是 **`null`,不是 `undefined`**(来电要等 `answer()`
84
- 才建 PC),而那处只判了 `undefined` → `null.addEventListener` 抛 TypeError →
85
- `CCBarCall` 构造函数的 catch 里 `terminate()` → 页面收到一个包着 `CALL_OPERATION_NOT_ALLOWED`
86
- 的错误,对端收到 `480 Temporarily Unavailable`(SIP 原文里是 `newRTCSession` 后 1ms 就 `terminate`)。
87
- 现在按 `null` 处理:PC 还没建就只挂 `peerconnection` 监听,等它出现再挂 `connectionstatechange`。
88
- - 同一处一并修了 `getMediaRegistration()`:PC 还没建时不再拿 `null` 去建媒体注册对象
89
- (那个对象之后一被用就会抛;来电在 answer 之后的状态变化里会重新注册,不受影响)。
90
- - 类型跟着改了:`jsSipTypes.ts` 里 `connection` 声明为 `RTCPeerConnection | null` ——
91
- 这个 bug 正是被「只写了 undefined」的类型漏过去的,改完之后 `tsc` 会把同类问题拦住。
92
- - 补用例:会话 `connection` 为 `null` 时订阅 `mediaConnected` 不能抛;测试里的假 JsSIP 会话
93
- 现在默认就是 `null`(与真实 JsSIP 一致)。
94
-
95
-
96
- ## 3.1.7 - 2026-09-23
97
-
98
- - **`active` 不再只认 SIP 的 ACK**:新增会话事件 `mediaConnected` —— 媒体真的通了(ICE + DTLS 完成)时,
99
- 只要通话已经进入 `connecting` 就推进到 `active`。
100
- 背景:`active` 原来只由 JsSIP 的 `confirmed` 驱动,而它要等对端回 ACK。有些平台(SBC)不回 ACK:
101
- 200 OK 之后音频已经通了、双方都在通话,`confirmed` 却永远不来,页面就一直停在「接通中」
102
- (3.1.6 之前停在「振铃中」,那是同一现象的另一个表现,见 3.1.6 第一条)。
103
- - 只从 `connecting` 推进:`ringing` / `dialing` 阶段的连接可能只是早期媒体(回铃),不算接通。
104
- - `CallSessionEvent` 增加 `'mediaConnected'`(新增事件,对已有集成无影响);JsSIP 适配器在
105
- PeerConnection 的 `connectionstatechange` 变成 `connected` 时发一次,通话结束时撤掉会话上的订阅。
106
- - 仍未解决、且**在平台侧**的一点:如果平台始终不回 ACK,JsSIP 自己的 ACK 定时器会在 32 秒后以
107
- `NO_ACK` 结束这通电话。SDK 不能替对端回 ACK,只能让状态先跟上事实;平台侧需补齐 ACK。
108
-
109
-
110
- ## 3.1.6 - 2026-09-23
111
-
112
- - **接听后立刻进入 `connecting`**:原来要等 JsSIP 的 `accepted`(也就是 200 OK 真发出去之后)才动状态。
113
- 背景:`answer()` 只让 JsSIP 同步进入 ANSWERED,200 OK 还要等本地媒体/SDP 准备好(首次通话还要等
114
- 麦克风授权),这十几秒里状态一直停在 `ringing` —— 页面上就是「点了接听没反应、状态还是振铃中」,
115
- 用户会以为没接上。现在点完接听立刻走 `ringing → connecting`,随后的 `accepted` / `confirmed`
116
- 照旧推到 `active`(公开状态链 `ringing→connecting→active` 不变,已有用例覆盖)。
117
- - 修 `hangup()`:terminate 之后若通话已被推成终态(JsSIP 对未确认的来电会**同步**发 `failed`),
118
- 不再多做一次非法的 `failed → ended` 转换 —— 那一帧会多抛一个 `CALL_OPERATION_NOT_ALLOWED`,
119
- 页面红字行/日志里就多一条看不懂的报错。
120
- - 修媒体收尾的噪音:通话结束解绑发送通道时,若连接已经关闭(WebRTC 规定此时 `replaceTrack` 抛
121
- `InvalidStateError`),按「没有东西可解绑」处理,不再报 `SDK_INTERNAL_ERROR`
122
- (典型现象:挂断瞬间刷一条 `replaceTrack ... The peer connection is closed`)。
123
- - 接听 / 拒接失败时**先把原始原因报出来**再 terminate 清理:terminate 会同步触发 JsSIP 的 `failed`
124
- (cause 固定是 `REJECTED`),原来的原因会被这个终态盖掉 —— 之前只能看到 `REJECTED`,
125
- 看不到「为什么接听会失败」。现在先经 `error` 事件把原始 cause 带出去。
126
-
127
-
128
- ## 3.1.5 - 2026-09-23
129
-
130
- - 新增 `dial({ destination, userdata })`:自定义参数原样写进 INVITE 的 `X-User-Data` 头,由平台/服务端从 SIP
131
- 报文里读 —— 就是旧版脚本 SDK 的 `userdata`,头名与含义都没变,平台侧不用改。未传、空串、纯空白=不带头
132
- (与旧行为逐字一致);`dial({ destination, userdata: '' })` 也不会多出任何头。
133
- - 只接受可见 ASCII:换行/制表能伪造出新的 SIP 头,中文等非 ASCII 不合规(中间设备可能吞掉或改写),
134
- 两者都抛 `CALL_INVALID_USERDATA`(`category: 'call'`、`retryable: false`,处置建议放在 `cause` 里)。
135
- 要传中文或 JSON 请调用方先 `encodeURIComponent` / base64,再在平台侧解回来。
136
- - 校验在建通话之前完成:非法值不占并发额度、也不留 pending —— 否则同号紧接着再拨会撞 `CALL_ALREADY_EXISTS`。
137
- - 注意:SDK 不暴露 SIP 头,页面侧读不到这个值(`call.incoming` 只给 `callId` / `from`);它是对平台/服务端说的,
138
- 页面上要排障只能像演示页那样打开 JsSIP 的 debug 抓 console 原文。
139
-
140
-
141
- ## 3.1.4 - 2026-09-22
142
-
143
- - **移动端形态(`platform: 'mobile-web'`)可以接来电了**:能不能接听改由会话策略 `policy.mobileIncomingEnabled` 决定
144
- (这个字段契约里一直有、之前只声明没实现)。打开时保留 `inbound` 能力,来电正常进 `call.incoming`、可以 `answer()`;
145
- `false` 或缺省时行为与旧版完全一致(来电直接拒掉,`CAPABILITY_NOT_SUPPORTED`)。保持 / 转接仍然不在移动端支持
146
- (移动 UI 上没有这两个操作),并发数仍是 1。
147
- - 背景:`mobile-web` 之前无条件删掉 `inbound`,导致手机浏览器「能打出去、别人打不进来」——被叫把 INVITE 直接结束,
148
- 主叫那边只会看到平台转出来的 `480 Temporarily Unavailable`(`Q.850 cause=16 NORMAL_CLEARING`),页面连来电浮层都不弹。
149
- 注意 `platform` 不传时 `detectPlatform()` 命中 `iPhone|iPad|Android` 会自动选 `mobile-web`,所以这个坑对手机端接入方是默认生效的。
150
- - `CallManager.answer` / `CCBarCall.answer` 的「按平台一刀切」改成按能力判断(会话说了算):
151
- 平台若给移动端授予 `inbound`,同样能正常接听。
152
-
153
-
154
- ## 3.1.3 - 2026-09-22
155
-
156
- - 新增可选构造参数 `agentTerminal`:设了就在 INVITE / REGISTER 上带 `X-Agent-Terminal` 并作为 UA 的
157
- User-Agent。**默认不发**(旧版脚本 SDK 的两种做法都能外呼:xcall fork 带 `'ccbar坐席'`,另一个参考页完全不带)。
158
- - 会话定时器默认关闭(`session_timers: false`,与旧版脚本 SDK 一致),呼叫参数用 `sessionTimersExpires: 120`:
159
- 开着时 INVITE 会多出 `Session-Expires` / `Supported: timer`。可用 `sipSessionTimers: true` 打开。
160
- - 注册成功后先建一个带 `iceCandidatePoolSize` 的空连接预热候选(`warmIceCandidatePool`):
161
- JsSIP 要等 ICE 收集结束才发 INVITE,机器上有虚拟网卡/不通的 STUN 时收集会拖到几十秒,
162
- 预热后第一次外呼也能立刻出局。
163
-
164
-
165
- ## 3.1.2 - 2026-09-21
166
-
167
- - 拨号更快出局:`pcConfig` 带上 `iceCandidatePoolSize: 2`(与旧版 SDK 的 login config 一致)。
168
- 背景:JsSIP 会等 ICE 收集完成才发 INVITE,不预收集候选时,STUN 的往返会直接变成「点了外呼要等一会儿才拨出去」。
169
-
170
-
171
- ## 3.1.1 - 2026-09-21
172
-
173
- - 新增 SIP 保活:已注册时每 `sipKeepaliveSeconds`(默认 25 秒)重发一次 REGISTER,撑住 nginx/NAT 上的长连接。
174
- 起因:浏览器发不了 WebSocket ping,中间设备会静默掐掉空闲的 WSS —— 页面还显示已注册,下一次 INVITE 直接
175
- 失败(`CALL_OPERATION_NOT_ALLOWED`,JsSIP 的 `NotConnected`)。注册有效期本身比间隔短时不再叠这一层。
176
- 可用构造参数 `sipKeepaliveSeconds` 调整,`0` 关闭。
177
-
178
-
179
- ## 3.1.0 - 2026-09-21
180
-
181
- - 新增可选构造参数 `sessionProvider`:由接入方提供会话(`createSession` / 可选 `refreshSession`
182
- / `setAgentStatus`),SDK 不再要求必须存在 `/webphone/v1/sessions`。用于把 SDK 接到自建或旧平台
183
- (例如 `token/fs` + `seat/account/get` 那一套)上,`tokenProvider` 与 `sessionProvider` 二选一。
184
- - `sessionProvider` 缺省 `refreshSession` 时用 `createSession` 重建;缺省 `setAgentStatus` 时抛
185
- `CAPABILITY_NOT_SUPPORTED`,不静默失败;没有提供任何来源时构造即报 `CONFIG_INVALID`。
186
- - `transport.ticket` 为空时不再声明 `xcall-ticket.` 子协议,只发 `sip`:旧平台把凭据放在
187
- WSS URL 的 `?token=` 上,多余子协议会让握手带上一个平台不认识的协议名。
188
- - REGISTER 的 `register_expires` 改为按 `session.sip.registerExpires` 走(原先前端写死 300 秒),
189
- 会话没给或给了非正数时才回退 300。平台侧要按自己的注册策略下发这个值。
190
- - 打包脚本在缺少 `zip` 命令的环境(如未装 zip 的 Windows)回退到系统自带 `tar`,交付包仍为 `.zip`。
191
-
192
- ## 3.0.2 - 2026-09-21
193
-
194
- - 修复 disconnect 后仍挂着的 `online`/`offline` 监听,以及 `deletedSessions` 墓碑无限增长。
195
- - `CallManager` 在通话结束通知完成后删除 terminal 记账;`dispose` 仍会清空剩余 maps。
196
- - `WebPhoneApi` 的 fetch 使用 `credentials: 'omit'`;SIP `wssUrl` 仅允许 `wss:` 或 loopback `ws:`。
197
- - React / Vue 在应用卸载时 `dispose()`;Vanilla default-ui 与 Mobile H5 通过 `ownsClient` 在组件卸载时释放。
198
- - 传入 `sharedWorker` 时 `CCBarClient` 会 attach 同源 Worker,并在 `dispose()` 时先 detach 再关闭端口;SIP 仍是单标签页。
199
- - 诊断报告中的 SDK 版本与 `@16x/webphone-sdk@3.0.2` 对齐。
200
-
201
- ## 3.0.1 - 2026-09-19
202
-
203
- - 以 `@16x/webphone-sdk` 发布到 npmjs:https://www.npmjs.com/package/@16x/webphone-sdk
204
- - 发布 WebPhone SDK 3.0.1,包含 WSS ticket 通过 `Sec-WebSocket-Protocol` 传递及 API 主机相对路径解析修复。
205
-
206
- ## 0.1.0 - 2026-09-18
207
-
208
- - 完成 Headless Core 生命周期、SIP/通话控制、媒体设备、重连与脱敏诊断能力。
209
- - 提供 ESM、CJS、UMD、类型声明及根入口、`/ui`、`/legacy`、`/diagnostics`、`/styles.css` 导出。
210
- - 新增旧 `CCBarSDK` 兼容层,按设计映射连接、拨号、当前通话控制和坐席状态方法。
211
- - `insideCall(number)` 在当前 `DialOptions` 不支持 `type` 时保留原目标号码,不添加隐式前缀。
212
- - `setBu()` 改为抛出明确的 `DeprecatedError`,不允许浏览器伪造忙碌状态。
213
- - Legacy 弃用警告仅在开发构建中按方法输出一次;生产构建保持安静。
214
- - 显式导入 `/legacy` 时才在浏览器附加 `window.CCBarSDK`,并保持 SSR 导入安全。
215
- - 增加 50 次初始化、连接、拨号、结束和销毁生命周期资源回收测试。
1
+ # Changelog
2
+
3
+ ## 3.2.0 - 2026-10-03
4
+
5
+ - 新增 `createTokenSessionProvider` 根入口,客户可通过 `getToken` 提供短期凭证;SDK 内部完成坐席账号获取、SIP 会话创建、注册登录、凭证刷新和坐席状态请求。
6
+ - 重复调用 `client.connect()` 时复用进行中的连接流程,已连接时直接返回,避免重复获取凭证和重复初始化连接。
7
+ - 完善接入文档,说明连接、坐席、通话状态,事件与错误处理,以及 `userdata` 到 SIP header 和话单字段的映射方式。
8
+ - 补充 9196、9199 实时验收及凭证错误、网络中断、STUN 异常和资源清理场景。
9
+
10
+ ## 3.1.11 - 2026-09-30
11
+
12
+ - **破坏性变更(会话来源)**:会话来源工厂的 `sipWsUrl` 改为**必填**,软电话地址
13
+ 由接入方传完整地址(`wss://` / `ws://`)。SDK 不再按坐席账号的 `domain` / `wssPort`(缺省 7443)
14
+ 与固定路径 `/api/fs/sip-ws` 拼装,`sipWsPath` 选项删除;缺 `sipWsUrl`、传相对路径或 `http(s)://`
15
+ 地址时,建会话(`createSession` / `refreshSession`)抛 `CONFIG_INVALID`(**构造本身不抛**)。
16
+ - 地址上已有的查询参数保留,SDK 仍把 fs token 挂成 `?token=`(`attachTokenToWss: false` 可关掉)。
17
+ - 只影响使用兼容会话入口的调用方;服务端下发完整 `transport.wssUrl` 的接入方式不受影响。
18
+
19
+ ## 3.1.10 - 2026-09-24
20
+
21
+ - **会话入口自动识别密文**:接入方把 `seat/account/get` 返回的 `password` 密文**直接**塞进
22
+ `session.sip.registerTicket` 时,SDK 现在会在会话进入的那一处(`createSessionProviderApi`)
23
+ 先用平台内置 key/iv 解一次,解不出来就原样放行 —— 服务端因此可以只做转发,
24
+ 不必再自己调 `decryptSipPassword`。
25
+ - 判定条件收得很紧,避免把明文密码当密文解坏:合法 Base64、长度是 4 的倍数、解码后是 16 字节的整数倍、
26
+ AES 解出的文本没有替换符与控制字符。固定种子采样(字母数字 8~40 位、32 位十六进制、
27
+ 以及「刚好是 16 字节 Base64 带 `=` 填充」这种最危险的形状)实测没有误判。
28
+ - 只认平台内置的 key/iv(即 `decryptSipPassword` 的默认值):环境不同、key/iv 被改过时,
29
+ 自动判定会失败并退回原值,这种环境仍需接入方自己用 `decryptSipPassword(cipher, { key, iv })`
30
+ 解好再放进会话。
31
+ - 非破坏性:已经自行解密的接入方拿到的会话对象原样不变;`decryptSipPassword` 的实现移入独立加密模块,
32
+ 公开导出与行为保持不变。
33
+
34
+ ## 3.1.9 - 2026-09-24
35
+
36
+ - **坐席状态接口直接用会话里的那张票**:平台把 fs token 拼在软电话地址上(`wss://…/api/fs/sip-ws?token=…`),
37
+ 同一个 token 也用于 HTTP 接口的 `Authorization`。`client.setAgentStatus()` 现在把这个地址带给会话来源
38
+ (`SessionProvider.setAgentStatus(request, context?)`,新增可选参数 `AgentStatusContext.wssUrl`),
39
+ 订阅来源可以直接拿它去打 `seat/account/get` 与 `seats/set-status`,**不再单独换一次票**。
40
+ - 会话来源的 `setAgentStatus` 按这个顺序取票:
41
+ 会话地址里的 token → 最近见过的那个(退签时会话已经断开)→ `getToken`/`tokenUrl` 重新换票(与旧行为一致)。
42
+ 它自己在 `createSession` 里拼过 WSS 地址时也会记住那张票;`invalidateToken()` 会把它一起清掉。
43
+ - 非破坏性:`setAgentStatus` 的第二个参数是可选的,旧接入方(不传 context、只用 `getToken`/`tokenUrl`)行为不变。
44
+
45
+ ## 3.1.8 - 2026-09-23
46
+
47
+ - **修 3.1.7 引入的回归:来电会被立刻拒掉(对端收到 480)。**
48
+ 3.1.7 新加的 `mediaConnected` 订阅在「会话还没有 PeerConnection」时崩了:JsSIP 的
49
+ `session.connection` 在 PC 建好之前返回的是 **`null`,不是 `undefined`**(来电要等 `answer()`
50
+ 才建 PC),而那处只判了 `undefined` → `null.addEventListener` 抛 TypeError →
51
+ `CCBarCall` 构造函数的 catch 里 `terminate()` → 页面收到一个包着 `CALL_OPERATION_NOT_ALLOWED`
52
+ 的错误,对端收到 `480 Temporarily Unavailable`(SIP 原文里是 `newRTCSession` 后 1ms 就 `terminate`)。
53
+ 现在按 `null` 处理:PC 还没建就只挂 `peerconnection` 监听,等它出现再挂 `connectionstatechange`。
54
+ - 同一处一并修了 `getMediaRegistration()`:PC 还没建时不再拿 `null` 去建媒体注册对象
55
+ (那个对象之后一被用就会抛;来电在 answer 之后的状态变化里会重新注册,不受影响)。
56
+ - 类型跟着改了:`jsSipTypes.ts` 里 `connection` 声明为 `RTCPeerConnection | null` ——
57
+ 这个 bug 正是被「只写了 undefined」的类型漏过去的,改完之后 `tsc` 会把同类问题拦住。
58
+ - 补用例:会话 `connection` 为 `null` 时订阅 `mediaConnected` 不能抛;测试里的假 JsSIP 会话
59
+ 现在默认就是 `null`(与真实 JsSIP 一致)。
60
+
61
+
62
+ ## 3.1.7 - 2026-09-23
63
+
64
+ - **`active` 不再只认 SIP 的 ACK**:新增会话事件 `mediaConnected` —— 媒体真的通了(ICE + DTLS 完成)时,
65
+ 只要通话已经进入 `connecting` 就推进到 `active`。
66
+ 背景:`active` 原来只由 JsSIP 的 `confirmed` 驱动,而它要等对端回 ACK。有些平台(SBC)不回 ACK:
67
+ 200 OK 之后音频已经通了、双方都在通话,`confirmed` 却永远不来,页面就一直停在「接通中」
68
+ (3.1.6 之前停在「振铃中」,那是同一现象的另一个表现,见 3.1.6 第一条)。
69
+ - 只从 `connecting` 推进:`ringing` / `dialing` 阶段的连接可能只是早期媒体(回铃),不算接通。
70
+ - `CallSessionEvent` 增加 `'mediaConnected'`(新增事件,对已有集成无影响);JsSIP 适配器在
71
+ PeerConnection 的 `connectionstatechange` 变成 `connected` 时发一次,通话结束时撤掉会话上的订阅。
72
+ - 仍未解决、且**在平台侧**的一点:如果平台始终不回 ACK,JsSIP 自己的 ACK 定时器会在 32 秒后以
73
+ `NO_ACK` 结束这通电话。SDK 不能替对端回 ACK,只能让状态先跟上事实;平台侧需补齐 ACK。
74
+
75
+
76
+ ## 3.1.6 - 2026-09-23
77
+
78
+ - **接听后立刻进入 `connecting`**:原来要等 JsSIP 的 `accepted`(也就是 200 OK 真发出去之后)才动状态。
79
+ 背景:`answer()` 只让 JsSIP 同步进入 ANSWERED,200 OK 还要等本地媒体/SDP 准备好(首次通话还要等
80
+ 麦克风授权),这十几秒里状态一直停在 `ringing` —— 页面上就是「点了接听没反应、状态还是振铃中」,
81
+ 用户会以为没接上。现在点完接听立刻走 `ringing → connecting`,随后的 `accepted` / `confirmed`
82
+ 照旧推到 `active`(公开状态链 `ringing→connecting→active` 不变,已有用例覆盖)。
83
+ - 修 `hangup()`:terminate 之后若通话已被推成终态(JsSIP 对未确认的来电会**同步**发 `failed`),
84
+ 不再多做一次非法的 `failed → ended` 转换 —— 那一帧会多抛一个 `CALL_OPERATION_NOT_ALLOWED`,
85
+ 页面红字行/日志里就多一条看不懂的报错。
86
+ - 修媒体收尾的噪音:通话结束解绑发送通道时,若连接已经关闭(WebRTC 规定此时 `replaceTrack` 抛
87
+ `InvalidStateError`),按「没有东西可解绑」处理,不再报 `SDK_INTERNAL_ERROR`
88
+ (典型现象:挂断瞬间刷一条 `replaceTrack ... The peer connection is closed`)。
89
+ - 接听 / 拒接失败时**先把原始原因报出来**再 terminate 清理:terminate 会同步触发 JsSIP 的 `failed`
90
+ (cause 固定是 `REJECTED`),原来的原因会被这个终态盖掉 —— 之前只能看到 `REJECTED`,
91
+ 看不到「为什么接听会失败」。现在先经 `error` 事件把原始 cause 带出去。
92
+
93
+
94
+ ## 3.1.5 - 2026-09-23
95
+
96
+ - 新增 `dial({ destination, userdata })`:自定义参数原样写进 INVITE 的 `X-User-Data` 头,由平台/服务端从 SIP
97
+ 报文里读 —— 就是旧版脚本 SDK 的 `userdata`,头名与含义都没变,平台侧不用改。未传、空串、纯空白=不带头
98
+ (与旧行为逐字一致);`dial({ destination, userdata: '' })` 也不会多出任何头。
99
+ - 只接受可见 ASCII:换行/制表能伪造出新的 SIP 头,中文等非 ASCII 不合规(中间设备可能吞掉或改写),
100
+ 两者都抛 `CALL_INVALID_USERDATA`(`category: 'call'`、`retryable: false`,处置建议放在 `cause` 里)。
101
+ 要传中文或 JSON 请调用方先 `encodeURIComponent` / base64,再在平台侧解回来。
102
+ - 校验在建通话之前完成:非法值不占并发额度、也不留 pending —— 否则同号紧接着再拨会撞 `CALL_ALREADY_EXISTS`。
103
+ - 注意:SDK 不暴露 SIP 头,页面侧读不到这个值(`call.incoming` 只给 `callId` / `from`);它是对平台/服务端说的,
104
+ 页面上要排障只能像演示页那样打开 JsSIP 的 debug 抓 console 原文。
105
+
106
+
107
+ ## 3.1.4 - 2026-09-22
108
+
109
+ - **移动端形态(`platform: 'mobile-web'`)可以接来电了**:能不能接听改由会话策略 `policy.mobileIncomingEnabled` 决定
110
+ (这个字段契约里一直有、之前只声明没实现)。打开时保留 `inbound` 能力,来电正常进 `call.incoming`、可以 `answer()`;
111
+ `false` 或缺省时行为与旧版完全一致(来电直接拒掉,`CAPABILITY_NOT_SUPPORTED`)。保持 / 转接仍然不在移动端支持
112
+ (移动 UI 上没有这两个操作),并发数仍是 1。
113
+ - 背景:`mobile-web` 之前无条件删掉 `inbound`,导致手机浏览器「能打出去、别人打不进来」——被叫把 INVITE 直接结束,
114
+ 主叫那边只会看到平台转出来的 `480 Temporarily Unavailable`(`Q.850 cause=16 NORMAL_CLEARING`),页面连来电浮层都不弹。
115
+ 注意 `platform` 不传时 `detectPlatform()` 命中 `iPhone|iPad|Android` 会自动选 `mobile-web`,所以这个坑对手机端接入方是默认生效的。
116
+ - `CallManager.answer` / `CCBarCall.answer` 的「按平台一刀切」改成按能力判断(会话说了算):
117
+ 平台若给移动端授予 `inbound`,同样能正常接听。
118
+
119
+
120
+ ## 3.1.3 - 2026-09-22
121
+
122
+ - 新增可选构造参数 `agentTerminal`:设了就在 INVITE / REGISTER 上带 `X-Agent-Terminal` 并作为 UA 的
123
+ User-Agent。**默认不发**(旧版脚本 SDK 的两种做法都能外呼:xcall fork 带 `'ccbar坐席'`,另一个参考页完全不带)。
124
+ - 会话定时器默认关闭(`session_timers: false`,与旧版脚本 SDK 一致),呼叫参数用 `sessionTimersExpires: 120`:
125
+ 开着时 INVITE 会多出 `Session-Expires` / `Supported: timer`。可用 `sipSessionTimers: true` 打开。
126
+ - 注册成功后先建一个带 `iceCandidatePoolSize` 的空连接预热候选(`warmIceCandidatePool`):
127
+ JsSIP 要等 ICE 收集结束才发 INVITE,机器上有虚拟网卡/不通的 STUN 时收集会拖到几十秒,
128
+ 预热后第一次外呼也能立刻出局。
129
+
130
+
131
+ ## 3.1.2 - 2026-09-21
132
+
133
+ - 拨号更快出局:`pcConfig` 带上 `iceCandidatePoolSize: 2`(与旧版 SDK 的 login config 一致)。
134
+ 背景:JsSIP 会等 ICE 收集完成才发 INVITE,不预收集候选时,STUN 的往返会直接变成「点了外呼要等一会儿才拨出去」。
135
+
136
+
137
+ ## 3.1.1 - 2026-09-21
138
+
139
+ - 新增 SIP 保活:已注册时每 `sipKeepaliveSeconds`(默认 25 秒)重发一次 REGISTER,撑住 nginx/NAT 上的长连接。
140
+ 起因:浏览器发不了 WebSocket ping,中间设备会静默掐掉空闲的 WSS —— 页面还显示已注册,下一次 INVITE 直接
141
+ 失败(`CALL_OPERATION_NOT_ALLOWED`,JsSIP 的 `NotConnected`)。注册有效期本身比间隔短时不再叠这一层。
142
+ 可用构造参数 `sipKeepaliveSeconds` 调整,`0` 关闭。
143
+
144
+
145
+ ## 3.1.0 - 2026-09-21
146
+
147
+ - 新增可选构造参数 `sessionProvider`:由接入方提供会话(`createSession` / 可选 `refreshSession`
148
+ / `setAgentStatus`),SDK 不再要求必须存在 `/webphone/v1/sessions`。用于把 SDK 接到自建服务或既有平台接口
149
+ (例如 `token/fs` + `seat/account/get` 那一套)上,`tokenProvider` 与 `sessionProvider` 二选一。
150
+ - `sessionProvider` 缺省 `refreshSession` 时用 `createSession` 重建;缺省 `setAgentStatus` 时抛
151
+ `CAPABILITY_NOT_SUPPORTED`,不静默失败;没有提供任何来源时构造即报 `CONFIG_INVALID`。
152
+ - `transport.ticket` 为空时不再声明 `xcall-ticket.` 子协议,只发 `sip`:平台把凭据放在
153
+ WSS URL 的 `?token=` 上,多余子协议会让握手带上一个平台不认识的协议名。
154
+ - REGISTER 的 `register_expires` 改为按 `session.sip.registerExpires` 走(原先前端写死 300 秒),
155
+ 会话没给或给了非正数时才回退 300。平台侧要按自己的注册策略下发这个值。
156
+ - 打包脚本在缺少 `zip` 命令的环境(如未装 zip 的 Windows)回退到系统自带 `tar`,交付包仍为 `.zip`。
157
+
158
+ ## 3.0.2 - 2026-09-21
159
+
160
+ - 修复 disconnect 后仍挂着的 `online`/`offline` 监听,以及 `deletedSessions` 墓碑无限增长。
161
+ - `CallManager` 在通话结束通知完成后删除 terminal 记账;`dispose` 仍会清空剩余 maps。
162
+ - `WebPhoneApi` 的 fetch 使用 `credentials: 'omit'`;SIP `wssUrl` 仅允许 `wss:` 或 loopback `ws:`。
163
+ - React / Vue 在应用卸载时 `dispose()`;Vanilla default-ui 与 Mobile H5 通过 `ownsClient` 在组件卸载时释放。
164
+ - 传入 `sharedWorker` 时 `CCBarClient` 会 attach 同源 Worker,并在 `dispose()` 时先 detach 再关闭端口;SIP 仍是单标签页。
165
+ - 诊断报告中的 SDK 版本与 `@16x/webphone-sdk@3.0.2` 对齐。
166
+
167
+ ## 3.0.1 - 2026-09-19
168
+
169
+ - 以 `@16x/webphone-sdk` 发布到 npmjs:https://www.npmjs.com/package/@16x/webphone-sdk
170
+ - 发布 WebPhone SDK 3.0.1,包含 WSS ticket 通过 `Sec-WebSocket-Protocol` 传递及 API 主机相对路径解析修复。
171
+
172
+ ## 0.1.0 - 2026-09-18
173
+
174
+ - 完成 Headless Core 生命周期、SIP/通话控制、媒体设备、重连与脱敏诊断能力。
175
+ - 提供 ESM、CJS、UMD、类型声明及根入口、UI、兼容、诊断与样式入口。
176
+ - 新增 `CCBarSDK` 兼容层,按设计映射连接、拨号、当前通话控制和坐席状态方法。
177
+ - `insideCall(number)` 在当前 `DialOptions` 不支持 `type` 时保留原目标号码,不添加隐式前缀。
178
+ - `setBu()` 改为抛出明确的 `DeprecatedError`,不允许浏览器伪造忙碌状态。
179
+ - 兼容 API 弃用提示仅在开发构建中按方法输出一次;生产构建保持安静。
180
+ - 显式加载兼容入口模块时才在浏览器附加 `window.CCBarSDK`,并保持 SSR 导入安全。
181
+ - 增加 50 次初始化、连接、拨号、结束和销毁生命周期资源回收测试。
package/LICENSE CHANGED
@@ -1 +1 @@
1
- Copyright 16X. All rights reserved.
1
+ Copyright 16X. All rights reserved.