Files
TankIC/sockets/ws_hook.js
T

100 lines
4.4 KiB
JavaScript
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.
// ============================================================
// WebSocket 流量捕获(页面内 hook,传输仍由浏览器发起)
// ============================================================
// 目的:在不改变「服务端看到的连接」的前提下,把浏览器真实的 ws 帧抓下来,
// 用于与「中继转存」逐帧对比,定位「直连能进、中继不能进」的差异。
//
// 挂载:serve.js 提供 /ws_hook.js;web/index.html 在主脚本之前引入(先于游戏创建 WebSocket)。
// 开关:**默认不启用**;URL hash 加 #hook 才抓(#nohook 可强制关)。
// 产物:
// window.__ws_capture__ —— 内存里的抓包(connections[].messages[] = {d,t,n,b},
// d=c2s/s2c,n=字节数,b=base64 原始字节,顺序即收发顺序)
// window.__ws_save__() —— 手动保存;离开页面时自动 sendBeacon 再存一份
// 落盘位置:serve.js 的 POST /__dump__/<name> → 3DTank/names/<name>
// ============================================================
(function () {
if (window.__ws_hooked__) return;
if (!/#hook/.test(location.hash) || /#nohook/.test(location.hash)) {
console.log('[WS Hook] 未启用(默认关闭;URL 加 #hook 开启抓包)');
// 兜底:即使没启用也定义,调用时提示怎么开,避免"__ws_save__ 不存在"
window.__ws_save__ = function () {
console.warn('[WS Hook] 未启用:请在 URL 末尾加 #hook(如 http://localhost:8080/#hook)后整页刷新,再调用 __ws_save__()');
return Promise.resolve(null);
};
return;
}
window.__ws_hooked__ = true;
var Capture = { startedAt: Date.now(), connections: [] };
window.__ws_capture__ = Capture;
var Original = window.WebSocket;
function ToBytes(Data) {
if (Data instanceof ArrayBuffer) return new Uint8Array(Data);
if (ArrayBuffer.isView(Data)) return new Uint8Array(Data.buffer, Data.byteOffset, Data.byteLength);
if (typeof Data === 'string') return new TextEncoder().encode(Data);
return new Uint8Array(0);
}
function ToBase64(Bytes) {
var S = '';
var Chunk = 0x8000; // 分块,避免超长字符串参数
for (var I = 0; I < Bytes.length; I += Chunk) {
S += String.fromCharCode.apply(null, Bytes.subarray(I, I + Chunk));
}
return btoa(S);
}
function Hooked(Url, Protocols) {
var Ws = Protocols ? new Original(Url, Protocols) : new Original(Url);
var Info = { id: Capture.connections.length, url: String(Url), openTime: null, closeCode: null, messages: [] };
Capture.connections.push(Info);
console.log('[WS Hook] #' + Info.id + ' 连接 ' + Info.url);
function Record(Dir, Data) {
var Bytes = ToBytes(Data);
Info.messages.push({ d: Dir, t: Date.now(), n: Bytes.length, b: ToBase64(Bytes) });
}
var OrigSend = Ws.send;
Ws.send = function (Data) {
Record('c2s', Data);
return OrigSend.apply(Ws, arguments);
};
Ws.addEventListener('message', function (Ev) { Record('s2c', Ev.data); });
Ws.addEventListener('open', function () { Info.openTime = Date.now(); });
Ws.addEventListener('close', function (Ev) { Info.closeCode = Ev.code; });
return Ws;
}
Hooked.CONNECTING = Original.CONNECTING;
Hooked.OPEN = Original.OPEN;
Hooked.CLOSING = Original.CLOSING;
Hooked.CLOSED = Original.CLOSED;
Hooked.prototype = Original.prototype;
window.WebSocket = Hooked;
function Stamp() {
return new Date().toISOString().replace(/[:.]/g, '-');
}
function Save(Prefix) {
var Json = JSON.stringify(Capture);
var Url = '/__dump__/' + (Prefix || 'ws_capture_') + Stamp() + '.json';
return fetch(Url, { method: 'POST', body: Json })
.then(function (R) {
console.log('[WS Hook] 已保存 ' + Url + '(' + Json.length + ' 字节,' + Capture.connections.length + ' 个连接)');
return R;
})
.catch(function (E) { console.warn('[WS Hook] 保存失败: ' + (E && E.message)); });
}
window.__ws_save__ = function () { return Save('ws_capture_'); };
window.addEventListener('beforeunload', function () {
try {
var Blob = new Blob([JSON.stringify(Capture)], { type: 'application/json' });
navigator.sendBeacon('/__dump__/ws_capture_auto_' + Date.now() + '.json', Blob);
} catch (E) {}
});
console.log('[WS Hook] 已挂载:window.__ws_capture__(内存)/ __ws_save__()(存到 3DTank/names/)');
})();