232 lines
7.4 KiB
JavaScript
232 lines
7.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
const DEFAULT_PORT = process.env.LWLT_CDP_PORT || '9223';
|
|
const DEFAULT_MARKER = 'AI-DRYRUN-NO-SUBMIT';
|
|
|
|
function parseArgs(argv) {
|
|
const args = { _: [] };
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const arg = argv[i];
|
|
if (!arg.startsWith('--')) {
|
|
args._.push(arg);
|
|
continue;
|
|
}
|
|
const key = arg.slice(2);
|
|
const next = argv[i + 1];
|
|
if (!next || next.startsWith('--')) args[key] = true;
|
|
else {
|
|
args[key] = next;
|
|
i += 1;
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function usage() {
|
|
return [
|
|
'Usage:',
|
|
' node tools/verify_order_marker.mjs --marker AI-DRYRUN-NO-SUBMIT --out reports/order-marker-verification.json',
|
|
'',
|
|
'Options:',
|
|
` --port <number> Chrome DevTools port. Default: ${DEFAULT_PORT}`,
|
|
` --marker <text> Obvious test marker to search. Default: ${DEFAULT_MARKER}`,
|
|
' --date-from <date> Departure date range start. Default: 2026-8-1.',
|
|
' --date-to <date> Departure date range end. Default: same as --date-from.',
|
|
' --out <path> Write redacted verification report.',
|
|
' --help Show this help.',
|
|
'',
|
|
'This tool searches the authorized independent-order list for an obvious test marker and stores no business row values.'
|
|
].join('\n');
|
|
}
|
|
|
|
function writeJson(path, data) {
|
|
mkdirSync(dirname(resolve(path)), { recursive: true });
|
|
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`);
|
|
}
|
|
|
|
async function getJson(url) {
|
|
const res = await fetch(url);
|
|
if (!res.ok) throw new Error(`${res.status} ${res.statusText}: ${url}`);
|
|
return res.json();
|
|
}
|
|
|
|
async function getTarget(port) {
|
|
const targets = await getJson(`http://127.0.0.1:${port}/json`);
|
|
const target = targets.find((item) => item.type === 'page' && item.url.includes('ltjt.yunzhi.run'));
|
|
if (!target) throw new Error(`No ltjt.yunzhi.run page target on port ${port}`);
|
|
return target;
|
|
}
|
|
|
|
class CDP {
|
|
constructor(wsUrl) {
|
|
this.wsUrl = wsUrl;
|
|
this.id = 0;
|
|
this.pending = new Map();
|
|
}
|
|
|
|
connect() {
|
|
return new Promise((resolve, reject) => {
|
|
this.ws = new WebSocket(this.wsUrl);
|
|
this.ws.onopen = resolve;
|
|
this.ws.onerror = (event) => reject(new Error(event.message || event.type || 'WebSocket error'));
|
|
this.ws.onmessage = (event) => {
|
|
const msg = JSON.parse(event.data);
|
|
if (!msg.id || !this.pending.has(msg.id)) return;
|
|
const { resolve: ok, reject: fail, timer } = this.pending.get(msg.id);
|
|
clearTimeout(timer);
|
|
this.pending.delete(msg.id);
|
|
msg.error ? fail(new Error(msg.error.message)) : ok(msg.result);
|
|
};
|
|
});
|
|
}
|
|
|
|
send(method, params = {}, timeoutMs = 30000) {
|
|
const id = ++this.id;
|
|
this.ws.send(JSON.stringify({ id, method, params }));
|
|
return new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => {
|
|
this.pending.delete(id);
|
|
reject(new Error(`Timeout: ${method}`));
|
|
}, timeoutMs);
|
|
this.pending.set(id, { resolve, reject, timer });
|
|
});
|
|
}
|
|
|
|
close() {
|
|
this.ws?.close();
|
|
}
|
|
}
|
|
|
|
async function evaluate(cdp, expression, timeoutMs = 30000) {
|
|
const result = await cdp.send('Runtime.evaluate', {
|
|
expression,
|
|
returnByValue: true,
|
|
awaitPromise: true
|
|
}, timeoutMs);
|
|
if (result.exceptionDetails) {
|
|
const detail = result.exceptionDetails.exception?.description
|
|
|| result.exceptionDetails.exception?.value
|
|
|| result.exceptionDetails.text
|
|
|| 'Runtime.evaluate failed';
|
|
throw new Error(detail);
|
|
}
|
|
return result.result.value;
|
|
}
|
|
|
|
function verifyExpression(marker, dateFrom, dateTo) {
|
|
return `(async () => {
|
|
const marker = ${JSON.stringify(marker)};
|
|
const dateFrom = ${JSON.stringify(dateFrom || '2026-8-1')};
|
|
const dateTo = ${JSON.stringify(dateTo || dateFrom || '2026-8-1')};
|
|
function findSameOriginWindow() {
|
|
const queue = [window];
|
|
while (queue.length) {
|
|
const win = queue.shift();
|
|
let doc;
|
|
try { doc = win.document; } catch (err) { continue; }
|
|
if (String(doc.location.href || '').includes('ltjt.yunzhi.run')) return win;
|
|
Array.from(win.frames).forEach((frame) => queue.push(frame));
|
|
}
|
|
return window;
|
|
}
|
|
|
|
const win = findSameOriginWindow();
|
|
const body = new URLSearchParams({
|
|
Act: 'JH_OrderList',
|
|
Tpage: '1',
|
|
P_Size: '50',
|
|
riqi: 'chufari',
|
|
S_chufariqi: dateFrom,
|
|
S_chufarizhi: dateTo,
|
|
S_fabudanwei: '老挝联泰',
|
|
S_tuanxuhao: marker,
|
|
S_kehuming: '',
|
|
S_chanpinming: '',
|
|
S_gendanren: '',
|
|
S_youkexinxi: '',
|
|
S_daoyou: '',
|
|
S_lingdui: '',
|
|
S_querenshu: '',
|
|
S_zhuanxianming: '',
|
|
S_zhuangtai: ''
|
|
});
|
|
const res = await win.fetch('/System/dat/orders.asp', {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
|
body: body.toString()
|
|
});
|
|
const text = await res.text();
|
|
const markerOccurrences = marker ? Math.max(0, text.split(marker).length - 1) : 0;
|
|
const loginTimeout = /登陆|登录|login/i.test(text);
|
|
const permissionText = /权限|permission/i.test(text);
|
|
const looksEmpty = /无数据|没有|暂无|empty/i.test(text) || markerOccurrences === 0;
|
|
let parsedJson = null;
|
|
try { parsedJson = JSON.parse(text); } catch (err) {}
|
|
return {
|
|
ok: res.ok,
|
|
status: markerOccurrences ? 'order_marker_found' : 'order_marker_not_found',
|
|
request: {
|
|
endpoint: '/System/dat/orders.asp',
|
|
action: 'JH_OrderList',
|
|
date_range: { from: dateFrom, to: dateTo },
|
|
marker_redacted: false,
|
|
marker
|
|
},
|
|
response: {
|
|
http_status: res.status,
|
|
byte_length: new Blob([text]).size,
|
|
marker_occurrences: markerOccurrences,
|
|
contains_login_timeout_text: loginTimeout,
|
|
contains_permission_text: permissionText,
|
|
looks_empty_or_no_match: looksEmpty,
|
|
parsed_json: Boolean(parsedJson),
|
|
json_top_level_keys: parsedJson && typeof parsedJson === 'object' ? Object.keys(parsedJson).slice(0, 30) : [],
|
|
value_redacted: true
|
|
},
|
|
submit_safety: {
|
|
live_submit_attempted: false,
|
|
verification_only: true
|
|
}
|
|
};
|
|
})()`;
|
|
}
|
|
|
|
async function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
if (args.help) {
|
|
console.log(usage());
|
|
return;
|
|
}
|
|
const marker = args.marker || DEFAULT_MARKER;
|
|
const dateFrom = args['date-from'] || '2026-8-1';
|
|
const dateTo = args['date-to'] || dateFrom;
|
|
const target = await getTarget(args.port || DEFAULT_PORT);
|
|
const cdp = new CDP(target.webSocketDebuggerUrl);
|
|
await cdp.connect();
|
|
try {
|
|
await cdp.send('Runtime.enable');
|
|
const output = await evaluate(cdp, verifyExpression(marker, dateFrom, dateTo), 30000);
|
|
output.generated_at = new Date().toISOString();
|
|
if (args.out) writeJson(args.out, output);
|
|
console.log(JSON.stringify({
|
|
status: output.status,
|
|
marker_occurrences: output.response?.marker_occurrences || 0,
|
|
http_status: output.response?.http_status,
|
|
byte_length: output.response?.byte_length,
|
|
out: args.out || ''
|
|
}, null, 2));
|
|
if (output.status !== 'order_marker_found') process.exit(2);
|
|
} finally {
|
|
cdp.close();
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error.stack || error.message);
|
|
process.exit(1);
|
|
});
|