/** * 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'); });