258 lines
13 KiB
JavaScript
258 lines
13 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
|
|
const DEFAULT_CDP = '/Users/inmanx/.agents/skills/chrome-cdp/scripts/cdp.mjs';
|
|
const DEFAULT_PLATFORM_TARGET = 'DA600CC0';
|
|
const EXTENSION_ID = 'kafggjjlhebccdgkechmbgflkaifkccf';
|
|
|
|
function parseArgs(argv) {
|
|
const args = {
|
|
expectedRunId: '',
|
|
identifier: '',
|
|
ddid: '',
|
|
closeArrangementTid: '',
|
|
platformTarget: DEFAULT_PLATFORM_TARGET,
|
|
cdp: process.env.CDP_CLI || DEFAULT_CDP,
|
|
timeoutMs: 30_000
|
|
};
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const value = argv[index];
|
|
if (value === '--expected-run-id') args.expectedRunId = argv[++index] || '';
|
|
else if (value === '--identifier') args.identifier = argv[++index] || '';
|
|
else if (value === '--ddid') args.ddid = argv[++index] || '';
|
|
else if (value === '--close-arrangement-tid') args.closeArrangementTid = argv[++index] || '';
|
|
else if (value === '--platform-target') args.platformTarget = argv[++index] || '';
|
|
else if (value === '--cdp') args.cdp = argv[++index] || '';
|
|
else if (value === '--timeout-ms') args.timeoutMs = Number(argv[++index]);
|
|
else throw new Error(`unknown argument: ${value}`);
|
|
}
|
|
if (!args.expectedRunId) throw new Error('--expected-run-id is required');
|
|
if (!args.identifier.includes('TEST-202609')) throw new Error('--identifier must be a TEST-202609 object');
|
|
if (args.ddid && !/^\d+$/.test(args.ddid)) throw new Error('--ddid must be numeric');
|
|
if (args.closeArrangementTid && !/^\d+$/.test(args.closeArrangementTid)) throw new Error('--close-arrangement-tid must be numeric');
|
|
if (!Number.isFinite(args.timeoutMs) || args.timeoutMs < 5_000) throw new Error('--timeout-ms must be at least 5000');
|
|
return args;
|
|
}
|
|
|
|
function cdp(args, command, target, ...rest) {
|
|
return execFileSync(process.execPath, [args.cdp, command, target, ...rest], {
|
|
encoding: 'utf8',
|
|
maxBuffer: 32 * 1024 * 1024
|
|
}).trim();
|
|
}
|
|
|
|
function parseJson(value, label) {
|
|
try {
|
|
return JSON.parse(String(value || '').trim());
|
|
} catch {
|
|
throw new Error(`${label} returned non-JSON output: ${String(value || '').slice(0, 500)}`);
|
|
}
|
|
}
|
|
|
|
function wait(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
const args = parseArgs(process.argv.slice(2));
|
|
parseJson(cdp(args, 'eval', args.platformTarget, `(async()=>JSON.stringify(await sendToExtension('PING',{},3000)))()`), 'extension ping');
|
|
const targetsResult = parseJson(cdp(args, 'evalraw', args.platformTarget, 'Target.getTargets', '{}'), 'target list');
|
|
const serviceWorker = targetsResult.targetInfos?.find((target) => (
|
|
target.type === 'service_worker' && target.url === `chrome-extension://${EXTENSION_ID}/background.js`
|
|
));
|
|
if (!serviceWorker?.targetId) throw new Error('extension_service_worker_missing');
|
|
const attached = parseJson(cdp(args, 'evalraw', args.platformTarget, 'Target.attachToTarget', JSON.stringify({
|
|
targetId: serviceWorker.targetId,
|
|
flatten: false
|
|
})), 'service worker attach');
|
|
if (!attached.sessionId) throw new Error('extension_service_worker_attach_failed');
|
|
|
|
const probeKey = `READONLY-BUSINESS-FRAME-${args.expectedRunId}-${Date.now()}`;
|
|
const expression = `(async()=>{
|
|
const tabs=await chrome.tabs.query({url:"https://lwlt.hisy.cc/*"});
|
|
const tab=tabs.find((candidate)=>/\\/System\\/Mainlt\\.asp/i.test(candidate.url||""))||tabs[0];
|
|
if(!tab?.id)throw new Error("erp_tab_missing");
|
|
const injected=await chrome.scripting.executeScript({
|
|
target:{tabId:tab.id,allFrames:true},world:"MAIN",
|
|
func:async(identifier,expectedDdid,closeArrangementTid)=>{
|
|
const pathname=String(location.pathname||"");
|
|
if(!/^\\/system\\/business\\//i.test(pathname))return null;
|
|
const documentText=String(document.body?.innerText||document.body?.textContent||"");
|
|
const forms=[...document.forms].map((form)=>({
|
|
id:String(form.id||""),name:String(form.name||""),method:String(form.method||""),
|
|
action_path:(()=>{try{return new URL(form.action||location.href,location.href).pathname}catch{return ""}})(),
|
|
element_count:form.elements.length,
|
|
field_names:[...new Set([...form.elements].map((element)=>String(element.name||"")).filter(Boolean))].sort()
|
|
}));
|
|
const functionNames=["SubmitInfoForm","CheckData","DoInfo","SubmitForm"];
|
|
const functions={};
|
|
for(const name of functionNames){
|
|
try{
|
|
const source=typeof window[name]==="function"?String(window[name]):"";
|
|
if(source)functions[name]=source.slice(0,20000);
|
|
}catch{}
|
|
}
|
|
try{
|
|
const source=typeof window.Vidy?.CheckMastInput==="function"?String(window.Vidy.CheckMastInput):"";
|
|
if(source)functions["Vidy.CheckMastInput"]=source.slice(0,20000);
|
|
}catch{}
|
|
try{
|
|
const source=typeof window.Vidy?.ClearInput==="function"?String(window.Vidy.ClearInput):"";
|
|
if(source)functions["Vidy.ClearInput"]=source.slice(0,20000);
|
|
}catch{}
|
|
const relevantScripts=[...document.scripts].map((script)=>String(script.textContent||""))
|
|
.filter((source)=>/SubmitInfoForm|DoInfoJH|xiadanbeizhu/.test(source))
|
|
.map((source)=>source.slice(0,30000));
|
|
const trackedControls={};
|
|
for(const name of ["xiadanbeizhu","zhusuanpai","da","xiao","ddid","tdid"]){
|
|
const control=document.querySelector('[name="'+name+'"],#'+name);
|
|
if(!control)continue;
|
|
trackedControls[name]={
|
|
tag:String(control.tagName||""),type:String(control.type||""),
|
|
value_length:String(control.value||"").length,max_length:Number(control.maxLength),
|
|
disabled:Boolean(control.disabled),read_only:Boolean(control.readOnly)
|
|
};
|
|
}
|
|
let validationCapture=null;
|
|
const listForm=document.querySelector('#ListForm,form[name="ListForm"]');
|
|
const actualDdid=String(listForm?.querySelector('[name="ddid"]')?.value||"");
|
|
const noteControl=listForm?.querySelector('[name="xiadanbeizhu"]');
|
|
const receivableSlots=[];
|
|
const activeReceivables=[];
|
|
const generatedZeroReceivables=[];
|
|
const receivableInspectionScope={
|
|
ddid_matched:Boolean(expectedDdid&&actualDdid===expectedDdid),
|
|
marker_in_note:String(noteControl?.value||"").includes("TEST-202609"),
|
|
value_redacted:true
|
|
};
|
|
if(expectedDdid&&actualDdid===expectedDdid&&String(noteControl?.value||"").includes("TEST-202609")){
|
|
const rowIds=[...listForm.elements].filter((control)=>/^ys_id\\d+$/.test(String(control.name||"")));
|
|
for(const rowIdControl of rowIds){
|
|
const match=String(rowIdControl.name||"").match(/^ys_id(\\d+)$/);
|
|
if(!match)continue;
|
|
const index=match[1];
|
|
const value=(name)=>String(listForm.elements.namedItem(name)?.value||"");
|
|
const name=value("ys_xiangmu"+index);
|
|
const amount=Number(value("ys_jine"+index)||0);
|
|
const row={
|
|
row_id:String(rowIdControl.value),name,
|
|
quantity:Number(value("ys_shuliang"+index)||0),
|
|
unit_price:Number(value("ys_danjia"+index)||0),
|
|
amount,
|
|
currency:value("ys_bizhong"+index)||"CNY",
|
|
paid:Number(value("ys_yishoufu"+index)||0)!==0,
|
|
settled:Boolean(value("ys_shenheren"+index)),
|
|
value_redacted:true
|
|
};
|
|
receivableSlots.push(row);
|
|
if(!/^\\d+$/.test(String(rowIdControl.value||""))||String(rowIdControl.value||"")==="0")continue;
|
|
activeReceivables.push(row);
|
|
if(["成人团费","单人房差"].includes(name)&&amount===0)generatedZeroReceivables.push(row);
|
|
}
|
|
}
|
|
if(expectedDdid&&actualDdid===expectedDdid&&String(noteControl?.value||"").includes("TEST-202609")){
|
|
const jq=window.jQuery||window.$;
|
|
const sha256=async(value)=>{
|
|
const digest=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(value));
|
|
return [...new Uint8Array(digest)].map((byte)=>byte.toString(16).padStart(2,"0")).join("");
|
|
};
|
|
const beforeBody="Act=DoInfoJH&"+jq(listForm).serialize();
|
|
const alerts=[];
|
|
const originalAlert=window.alert;
|
|
const originalDialogAlert=jq?.dialog?.alert;
|
|
window.alert=(message)=>{alerts.push(String(message||"").length)};
|
|
if(jq?.dialog&&typeof originalDialogAlert==="function")jq.dialog.alert=(message)=>{alerts.push(String(message||"").length)};
|
|
let validationResult=false;
|
|
let thrown="";
|
|
try{validationResult=window.Vidy?.CheckMastInput?.("ListForm")===true}catch(error){thrown=String(error?.message||error)}
|
|
window.alert=originalAlert;
|
|
if(jq?.dialog&&typeof originalDialogAlert==="function")jq.dialog.alert=originalDialogAlert;
|
|
const afterBody="Act=DoInfoJH&"+jq(listForm).serialize();
|
|
validationCapture={
|
|
ddid_matched:true,marker_in_note:true,validation_result:validationResult,
|
|
alert_count:alerts.length,thrown:Boolean(thrown),
|
|
before_sha256:await sha256(beforeBody),after_sha256:await sha256(afterBody),
|
|
body_changed:beforeBody!==afterBody,
|
|
before_field_count:[...new URLSearchParams(beforeBody.slice("Act=DoInfoJH&".length)).keys()].length,
|
|
after_field_count:[...new URLSearchParams(afterBody.slice("Act=DoInfoJH&".length)).keys()].length,
|
|
note_length_after_validation:String(noteControl?.value||"").length,
|
|
no_erp_write:true
|
|
};
|
|
}
|
|
let uiCleanup=null;
|
|
if(closeArrangementTid&&/\\/system\\/business\\/teams_(daoyou|cheliang|jiudian|piao|qita)\\.asp$/i.test(pathname)){
|
|
const arrangementForm=document.querySelector('#InfoForm1,form[name="InfoForm1"]');
|
|
const arrangementTid=String(arrangementForm?.elements?.namedItem?.('tdID')?.value||"");
|
|
const arrangementNote=String(arrangementForm?.elements?.namedItem?.('beizhu0')?.value||"");
|
|
const dialogApi=window.frameElement?.api;
|
|
const exact=arrangementTid===closeArrangementTid&&arrangementNote.includes("TEST-202609");
|
|
if(exact&&typeof dialogApi?.close==="function"){
|
|
window.setTimeout(()=>dialogApi.close(),0);
|
|
uiCleanup={scheduled:true,tid_matched:true,marker_matched:true,no_erp_write:true};
|
|
}else{
|
|
uiCleanup={scheduled:false,tid_matched:arrangementTid===closeArrangementTid,
|
|
marker_matched:arrangementNote.includes("TEST-202609"),close_available:typeof dialogApi?.close==="function",no_erp_write:true};
|
|
}
|
|
}
|
|
return {
|
|
pathname,title:document.title,identifier_present:documentText.includes(identifier),
|
|
marker_present:documentText.includes("TEST-202609"),account_present:documentText.includes("测试ai员工账号"),
|
|
frame_api:{present:Boolean(window.frameElement?.api),close:typeof window.frameElement?.api?.close==="function"},
|
|
forms,functions,relevant_scripts:relevantScripts,tracked_controls:trackedControls,
|
|
validation_capture:validationCapture,receivable_inspection_scope:receivableInspectionScope,
|
|
receivable_slots:receivableSlots,
|
|
active_receivables:activeReceivables,
|
|
generated_zero_receivables:generatedZeroReceivables,
|
|
ui_cleanup:uiCleanup,no_erp_write:true
|
|
};
|
|
},args:[${JSON.stringify(args.identifier)},${JSON.stringify(args.ddid)},${JSON.stringify(args.closeArrangementTid)}]
|
|
});
|
|
if(${JSON.stringify(Boolean(args.closeArrangementTid))})await new Promise((resolve)=>setTimeout(resolve,500));
|
|
const frames=injected.map((item)=>item.result).filter(Boolean);
|
|
const saved=await chrome.storage.local.get("businessTaskResults");
|
|
const resultMap=saved.businessTaskResults||{};
|
|
resultMap[${JSON.stringify(probeKey)}]={
|
|
status:"completed",stage:"read_only_business_frame_inspection",
|
|
message:"business frame source inspection completed",write_attempted:false,no_erp_write:true,
|
|
report:{frames}
|
|
};
|
|
await chrome.storage.local.set({businessTaskResults:resultMap});
|
|
return true;
|
|
})()`;
|
|
const message = JSON.stringify({
|
|
id: 1,
|
|
method: 'Runtime.evaluate',
|
|
params: { expression, awaitPromise: true, returnByValue: true }
|
|
});
|
|
cdp(args, 'evalraw', args.platformTarget, 'Target.sendMessageToTarget', JSON.stringify({
|
|
sessionId: attached.sessionId,
|
|
message
|
|
}));
|
|
|
|
const startedAt = Date.now();
|
|
let result = null;
|
|
while (Date.now() - startedAt < args.timeoutMs) {
|
|
try {
|
|
const response = parseJson(cdp(
|
|
args,
|
|
'eval',
|
|
args.platformTarget,
|
|
`(async()=>JSON.stringify(await sendToExtension('GET_TASK_RESULT',{task_id:${JSON.stringify(probeKey)}},3000)))()`
|
|
), 'probe result');
|
|
result = response?.result || null;
|
|
if (result?.status === 'completed') break;
|
|
} catch {
|
|
// The bounded read-only inspection is still running.
|
|
}
|
|
await wait(500);
|
|
}
|
|
|
|
try {
|
|
cdp(args, 'evalraw', args.platformTarget, 'Target.detachFromTarget', JSON.stringify({ sessionId: attached.sessionId }));
|
|
} catch {
|
|
// Detachment is cleanup only.
|
|
}
|
|
|
|
if (!result) throw new Error(`read-only business frame inspection timed out after ${args.timeoutMs}ms`);
|
|
console.log(JSON.stringify({ probe_key: probeKey, result }, null, 2));
|