fix(framework): decodeFrame 改单层(服务器协议非双层)

真机联调发现:服务器实发单层 {app,route,rpc,data};旧客户端的'外层 data'是浏览器
MessageEvent(00_minhttp.js:266 ws.onmessage=config.onmessage),非协议层。框架传输层
已取 ev.data,decodeFrame 不应再剥一层。修正后 kick_server 等业务包能正确解析。
- decodeFrame 单层化 + 测试改单层帧
- 更正 docs/protocol/01 §3.2(双层→单层,附 MessageEvent 依据)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 20:25:36 +08:00
co-authored by Claude Opus 4.8
parent 5132fddc4e
commit 96c6c7c914
5 changed files with 65 additions and 63 deletions
@@ -13,31 +13,26 @@ export type DecodeResult =
| { kind: 'message'; message: InboundMessage } // 正常业务内层
| { kind: 'ignore' }; // 无法解析,安全忽略
function parseMaybe(v: unknown): unknown {
return typeof v === 'string' ? JSON.parse(v) : v;
}
/**
* 解一帧原始字符串为分类结果。docs/protocol/01 §3.2 步骤 3–8。
* 解一帧为分类结果。**服务器协议是单层 `{route, rpc, data}`**(可能带 `app`)。
* 入参 frame 已是服务器实发帧——传输层已取 `MessageEvent.data`(旧 12_Logic.js 里被当成
* "外层 data" 的其实是浏览器 WebSocket MessageEvent 这个壳,非协议层)。故此处不再剥 `.data`。
* 不做 TcpID/isSendLoginState/submit_error 过滤(那是连接级,见 net-client)。
*/
export function decodeFrame(frame: string): DecodeResult {
let outer: any;
try { outer = JSON.parse(frame); } catch { return { kind: 'ignore' }; }
let data = outer?.data;
// 特殊串按原始帧判定(与旧 12_Logic.js 对 MessageEvent.data 的判断一致)。
if (frame === 'webserve-服务器未工作') return { kind: 'serverDown' };
if (frame.substr(0, 9) === '@toconcon') return { kind: 'handshake' };
if (data === 'webserve-服务器未工作') return { kind: 'serverDown' };
let msg: any;
try { msg = JSON.parse(frame); } catch { return { kind: 'ignore' }; }
// 兼容服务器偶发"再包一层字符串"(旧源码对每层都做 typeof=='string' 再 parse)。
if (typeof msg === 'string') {
try { msg = JSON.parse(msg); } catch { return { kind: 'ignore' }; }
}
if (!msg || typeof msg !== 'object') return { kind: 'ignore' };
if (typeof data === 'string') {
if (data.substr(0, 9) === '@toconcon') return { kind: 'handshake' };
try { data = JSON.parse(data); } catch { return { kind: 'ignore' }; }
}
if (data && typeof data === 'object' && (data as any).com === '@serverheartbeat') {
return { kind: 'heartbeat' };
}
const inner = parseMaybe(data) as InboundMessage | undefined;
if (inner && typeof inner === 'object' && 'route' in inner && 'rpc' in inner) {
return { kind: 'message', message: inner };
}
if ((msg as any).com === '@serverheartbeat') return { kind: 'heartbeat' };
if ('route' in msg && 'rpc' in msg) return { kind: 'message', message: msg as InboundMessage };
return { kind: 'ignore' };
}