Files
auto-pickup-v2/token_bridge/server.js
T

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