Files
huya-go/WSS_NOTES.md
T
yml2213 36aadae90a 按 10.13 抓包对齐 WSS:host 自动解析、exp 回传、pingSignal 与兑换记录字段
- 新增 launch.queryHttpDns:类型定义 + HTTPClient.QueryHttpDns + WSSClient.resolveHost(TTL 缓存、多 host 轮询、失败回退默认 host);Host 留空或等于历史默认值即视为自动解析
- WSS 握手 baseinfo 补 tag5=exp 与 tag10 空 customHeaders;UpdateUserExpsReq(cmd23) 改为回传 getConfig 的版本 map(缓存供重连复用)
- 新增 launch.pingSignal:HTTP 预热改走 pingSignal,WSS 预热在建连后探测业务通道(失败仅记日志)
- UserPrizeInfo 补 tag12 orderId/tag14 图标/tag16 prizeId/tag17 shopItemId/tag19 itemId/tag20 ext/tag21 exchangeDate,前端复盘表加订单号列
- taf 补 WriteListString/ReadListString/ReadMapStringInt64/ReadMapStringStruct
- 新增 internal/huya/launch_test.go:用抓包真实报文做字节级夹具(baseinfo 122B、queryHttpDns 请求 93B、记录 308B 等)
- CLI -wss-host 默认改为留空;GUI 设置支持留空并给出占位提示;README/WSS_NOTES 同步协议细节
2026-10-13 20:19:14 +08:00

193 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WSS 协议要点
本文件记录 2026-07-06 手动浏览器 HAR、页面 JS 和 Go 版本当前实现的复核结论。
## 抓包结论
手动浏览器链路是 HTTP + WSS 混合:
- 页面初始化、商品信息、HttpDNS 等有多条 `https://cdnws.api.huya.com/` POST。
- 浏览器同时建立了 3 条 `wss://77bc035c-ws.va.huya.com/` 连接。
- `scoreExchangePrize` 出现在 WSS 连接数据和静态 JS 中,没有出现在 `cdnws` POST 请求中。
- 当前 HTTP 版本可用,是因为 `webActUI` 的 WUP over HTTP 编码已经对齐。
- Go 版 WSS 已改为浏览器同款 `WebSocketCommand` 外层,仍需真实 Cookie 联调确认服务端返回。
## 连接
- WebSocket URL:`wss://<host>/?baseinfo=<base64 taf>`
- 手动 HAR host:`77bc035c-ws.va.huya.com`
- host 来自 `launch.queryHttpDns` 或浏览器抓包,不应长期写死。
- 手动 HAR 中 `baseinfo.cookie` 与 `traceId` 为空,cookie 通过后续 `VerifyCookie` 命令发送。
- 握手启用 `permessage-deflate`。
- 浏览器请求扩展:`permessage-deflate; client_max_window_bits`
- 服务端返回扩展:`permessage-deflate;server_no_context_takeover;client_no_context_takeover`
## 正确外层结构
浏览器 JS 不是 `1B version + 1B command + 4B sequence + body`。
页面实际构造 TAF/JCE `WebSocketCommand`:
```text
WebSocketCommand {
0: iCmdType int32
1: vData bytes
2: lRequestId int64
3: traceId string
4: iEncryptType int32
5: lTime int64
6: sMD5 string
}
```
WUP 请求发送逻辑:
```text
Wup.setServant(servant)
Wup.setFunc(func)
Wup.setRequestId(requestId)
Wup.writeStruct("tReq", req)
WebSocketCommand.iCmdType = EWSCmd_WupReq(3)
WebSocketCommand.vData = Wup.encode()
WebSocketCommand.traceId = "<trace>:<trace>:0:<enableTrace>"
ws.send(WebSocketCommand.writeTo(JceOutputStream))
```
服务端返回 `EWSCmd_WupRsp(4)`,内部 `vData` 才是 WUP 响应。
## 命令枚举
页面 JS 中可见的关键枚举:
- `3`:`EWSCmd_WupReq`
- `4`:`EWSCmd_WupRsp`
- `5`:`EWSCmdC2S_HeartBeat`
- `6`:`EWSCmdS2C_HeartBeatAck`
- `10`:`EWSCmdC2S_VerifyCookieReq`
- `11`:`EWSCmdS2C_VerifyCookieRsp`
- `16`:`EWSCmdC2S_RegisterGroupReq`
- `17`:`EWSCmdS2C_RegisterGroupRsp`
- `20`:`EWSCmdC2S_HeartBeatReq`
- `21`:`EWSCmdS2C_HeartBeatRsp`
- `23`:`EWSCmdC2S_UpdateUserExpsReq`
- `24`:`EWSCmdS2C_UpdateUserExpsRsp`
- `33`:`EWSCmdC2S_UpdateUserInfoReq`
- `34`:`EWSCmdS2C_UpdateUserInfoRsp`
手动 HAR 解出的服务端 WSS 回包包括:
- `EWSCmdS2C_HeartBeatRsp`
- `EWSCmd_WupRsp launch.wsLaunch`
- `EWSCmd_WupRsp mobileui.getConfig`
- `EWSCmd_WupRsp webActUI.getActPrizeList`
- `EWSCmd_WupRsp webActUI.getUserScore`
- `EWSCmd_WupRsp webActUI.scoreExchangePrize`
## 初始化流程
浏览器 WSS session 的关键流程:
1. 连接 `wss://<host>/?baseinfo=<base64 taf>`。
2. 发送 `EWSCmdC2S_RegisterGroupReq(16)`,默认 group 为 `live:0`。
3. 通过 WSS 调用 `launch.wsLaunch`,手动 HAR 中 requestId 为 `-1`。
4. 发送 `EWSCmdC2S_VerifyCookieReq(10)`,cookie 在这里进入 WSS 通道。
5. 通过 WSS 调用 `mobileui.getConfig`。
6. 发送 `EWSCmdC2S_UpdateUserExpsReq(23)`,请求体是 `getConfig` 下发的版本 map(实测 `{"58664":"107263"}`,Go 版已按此回传)。
7. 再发送一次 `EWSCmdC2S_RegisterGroupReq(16)`。
8. 发送 `EWSCmdC2S_HeartBeatReq(20)`,后续维持心跳。
9. `launch.queryHttpDns` 仍通过 HTTP `cdnws` 调用。
10. 后续 WUP 业务在连接可用时走 `signalSdk.sendWup`,连接不可用时可 fallback 到 `sendWupHttp`。
握手 `baseinfo`(11 个字段)实测带 `exp`(tag5),值就是 getConfig 的版本串;cookie(tag8)/traceId(tag9) 为空,tag10 是空的 `customHeaders` map。
响应应按 WUP `iRequestId` 匹配回调,不能只用 FIFO。
## 活动接口
当前 Go HTTP 版本已实现:
- `webActUI.getActPrizeList`
- `webActUI.getUserScore`
- `webActUI.getUserPrizeRecords`
- `webActUI.scoreExchangePrize`
WSS 版本仍是实验状态,但外层 `WebSocketCommand`、初始化帧顺序和 requestId 匹配已按手动 HAR 修正。
抢购兑换请求结构:
```text
tReq.user
tReq.sid
tReq.pid
tReq.ip = ""
tReq.clientEnv = {}
tReq.source = ""
tReq.isRole = 1
```
浏览器手动抢购小黄鸭成功回包确认:
- `webActUI.scoreExchangePrize`
- `sid=2203`
- `pid=12889`
- `status=200`
- `msg=请求成功`
- `exchangeInfo.pid=12889`
- `exchangeInfo.exchangeScore=988`
- `exchangeInfo.desc=兑换奖品:套装-潮萌小黄鸭`
## launch.queryHttpDns(已实现)
2026-10-13「精英宝典」抓包确认:`m-shop.huya.com` 页面用 HTTP `cdnws` 调它换取 WSS host,`iRequestId` 固定 `-1`。
请求 `tReq`:
```text
tReq.0 uid int64
tReq.1 ua string # webh5&0.0.1&websocket&&h5_/points.html
tReq.2 domains list<string> # ["cdnws.api.huya.com"]
tReq.3 appSrc string # HUYA&ZH&2052
tReq.4 "" string
tReq.5 0 int32
tReq.6 1 int32
```
响应 `tRsp`:
```text
tRsp.0 map<domain, {0: list<string> hosts, 1: int32 ttl秒, 2: list<string>}> # 实测 ttl=1296000
tRsp.1 int32
```
Go 版:`HTTPClient.QueryHttpDns` + `WSSClient.resolveHost`(TTL 缓存、多 host 轮询、失败回退默认 host)。
## launch.pingSignal(已实现)
页面在 HTTP `cdnws` 上调用它做链路探测,`iRequestId=0`:
```text
tReq.0 "launch" tReq.1 "pingSignal" tReq.2 map<string,string> 空
tRsp.0 "launch" tRsp.1 "pingSignal" tRsp.2 map<string,int64>{signal_ap_start, signal_ap_end}
```
Go 版:`HTTPClient.PingSignal`(HTTP 预热)与 `WSSClient.PingSignal`(WSS 预热后探测业务通道),预热结果只记日志、不影响抢购。
## 商城链路(仅记录,未实现)
同一抓包中「开通精英宝典 / 购买幸运币」属于商城付费链路,全部走 WSS 上的 `shopMiddleUI.*`:
```text
getGoodsInfoV5 → getSupplierInfoV5 → listPayChannelV5 → checkHyProtocolV5 → acceptHyProtocolV5
→ calcOrderPromotion → checkUserBuyAuth → createOrderV5 → payOrderSubmitV5(支付宝二维码) → orderDetailV5(轮询)
另有 createOrderV6(另一条下单通道)与 revenueWebUI.queryUserOrderList
```
注意函数名带版本后缀(V5/V6)。`webActUI` 在 WSS 上同样可用(实测 `checkUserBindGameAccount`、`getUserPrizeRecords`)。
## 待完善
- 使用真实 Cookie 联调 `list/score/exchange`,确认服务端不再超时。
- WSS 侧对 `launch.pingSignal` 的实际可用性需真号验证(失败已降级为日志)。
- 补更多基于真实抓包摘要的 WSS 编解码单测。