118 lines
4.0 KiB
JavaScript
118 lines
4.0 KiB
JavaScript
/**
|
|
* Token Bridge — HTTP 服务器模式。
|
|
* 在 jsdom 中运行 $_ts 安全框架,计算 c93l5Fya token。
|
|
*
|
|
* 接口:
|
|
* GET /ping → {"ok":true}
|
|
* POST /init → body: {html, scripts, baseUrl} → {"ok":true, "nsd":...}
|
|
* POST /token → body: {method, path} → {"token":"..."}
|
|
*/
|
|
|
|
const { JSDOM } = require('jsdom');
|
|
const http = require('http');
|
|
|
|
let dom = null;
|
|
let window = null;
|
|
let _cdLen = 0;
|
|
let _nsd = null;
|
|
|
|
function initialize(html, scripts, baseUrl) {
|
|
dom = new JSDOM(html, {
|
|
url: baseUrl,
|
|
runScripts: 'dangerously',
|
|
pretendToBeVisual: true,
|
|
});
|
|
window = dom.window;
|
|
|
|
_cdLen = (window.$_ts && window.$_ts.cd) ? window.$_ts.cd.length : 0;
|
|
_nsd = (window.$_ts && window.$_ts.nsd) ? window.$_ts.nsd : null;
|
|
|
|
// 拦截 XMLHttpRequest.prototype.open —— security JS 会包装它
|
|
const OriginalXHR = window.XMLHttpRequest;
|
|
OriginalXHR.prototype.open = function (method, url) {
|
|
this.__ts_method = method;
|
|
this.__ts_url = url;
|
|
};
|
|
|
|
// 注入 security JS
|
|
for (const script of scripts) {
|
|
try {
|
|
const el = window.document.createElement('script');
|
|
el.textContent = script.content;
|
|
window.document.head.appendChild(el);
|
|
} catch (_) {}
|
|
}
|
|
|
|
// 验证
|
|
const testXhr = new OriginalXHR();
|
|
testXhr.open('GET', '/_ts_test_');
|
|
const captured = testXhr.__ts_url || '';
|
|
|
|
if (!captured.includes('c93l5Fya=')) {
|
|
throw new Error(
|
|
`$_ts init failed: cd_len=${_cdLen}, nsd=${_nsd}, captured=${captured.substring(0, 80)}`
|
|
);
|
|
}
|
|
|
|
return { ok: true, nsd: _nsd };
|
|
}
|
|
|
|
function computeToken(method, path) {
|
|
if (!window) throw new Error('not initialized');
|
|
// process.stderr.write(JSON.stringify({trace: 'computeToken', method, path}) + '\n');
|
|
const OriginalXHR = window.XMLHttpRequest;
|
|
// process.stderr.write(JSON.stringify({trace: 'XHR class', hasXHR: !!OriginalXHR}) + '\n');
|
|
const xhr = new OriginalXHR();
|
|
// process.stderr.write(JSON.stringify({trace: 'xhr created'}) + '\n');
|
|
xhr.open(method || 'GET', path);
|
|
// process.stderr.write(JSON.stringify({trace: 'open called', url: (xhr.__ts_url||'').substring(0,100)}) + '\n');
|
|
const url = xhr.__ts_url || '';
|
|
const idx = url.indexOf('c93l5Fya=');
|
|
if (idx < 0) throw new Error(`Token not found for ${path}, url=${url.substring(0,80)}`);
|
|
return url.substring(idx + 'c93l5Fya='.length);
|
|
}
|
|
|
|
// ─── HTTP Server ────────────────────────────────────────────
|
|
|
|
function readBody(req) {
|
|
return new Promise((resolve) => {
|
|
let data = '';
|
|
req.on('data', c => data += c);
|
|
req.on('end', () => resolve(data));
|
|
});
|
|
}
|
|
|
|
function json(res, data, status = 200) {
|
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(data));
|
|
}
|
|
|
|
const server = http.createServer(async (req, res) => {
|
|
try {
|
|
if (req.method === 'GET' && req.url === '/ping') {
|
|
json(res, { ok: true });
|
|
} else if (req.method === 'POST' && req.url === '/init') {
|
|
const body = await readBody(req);
|
|
const { html, scripts, baseUrl } = JSON.parse(body);
|
|
const result = initialize(html, scripts, baseUrl);
|
|
json(res, result);
|
|
} else if (req.method === 'POST' && req.url === '/token') {
|
|
const body = await readBody(req);
|
|
const { method, path } = JSON.parse(body);
|
|
const token = computeToken(method, path);
|
|
json(res, { token });
|
|
} else {
|
|
json(res, { error: 'not found' }, 404);
|
|
}
|
|
} catch (e) {
|
|
process.stderr.write(JSON.stringify({ error: e.message }) + '\n');
|
|
json(res, { error: e.message }, 500);
|
|
}
|
|
});
|
|
|
|
const port = process.argv[2] || 0;
|
|
server.listen(port, '127.0.0.1', () => {
|
|
const addr = server.address();
|
|
process.stderr.write(JSON.stringify({ ready: true, port: addr.port }) + '\n');
|
|
});
|