133 lines
5.3 KiB
JavaScript
133 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { readFile } from 'node:fs/promises';
|
|
|
|
const DEFAULT_PORT_FILE = '/Users/inmanx/Library/Application Support/Google/Chrome/DevToolsActivePort';
|
|
const EXTENSION_ID = 'kafggjjlhebccdgkechmbgflkaifkccf';
|
|
|
|
function argument(name, fallback = '') {
|
|
const index = process.argv.indexOf(name);
|
|
return index >= 0 && process.argv[index + 1] ? process.argv[index + 1] : fallback;
|
|
}
|
|
|
|
function connect(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const socket = new WebSocket(url);
|
|
const timer = setTimeout(() => reject(new Error('browser websocket connection timed out')), 15000);
|
|
socket.addEventListener('open', () => {
|
|
clearTimeout(timer);
|
|
resolve(socket);
|
|
}, { once: true });
|
|
socket.addEventListener('error', () => {
|
|
clearTimeout(timer);
|
|
reject(new Error('browser websocket connection failed'));
|
|
}, { once: true });
|
|
});
|
|
}
|
|
|
|
function messenger(socket) {
|
|
let sequence = 0;
|
|
const pending = new Map();
|
|
socket.addEventListener('message', (event) => {
|
|
const message = JSON.parse(String(event.data || '{}'));
|
|
const handler = pending.get(message.id);
|
|
if (!handler) return;
|
|
pending.delete(message.id);
|
|
if (message.error) handler.reject(new Error(message.error.message || JSON.stringify(message.error)));
|
|
else handler.resolve(message.result || {});
|
|
});
|
|
return (method, params = {}, sessionId = '', timeoutMs = 8000) => new Promise((resolve, reject) => {
|
|
const id = ++sequence;
|
|
const timer = setTimeout(() => {
|
|
pending.delete(id);
|
|
reject(new Error(`${method} timed out`));
|
|
}, timeoutMs);
|
|
pending.set(id, {
|
|
resolve: (value) => {
|
|
clearTimeout(timer);
|
|
resolve(value);
|
|
},
|
|
reject: (error) => {
|
|
clearTimeout(timer);
|
|
reject(error);
|
|
}
|
|
});
|
|
socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }));
|
|
});
|
|
}
|
|
|
|
const taskId = argument('--task');
|
|
const resumePrewrite = process.argv.includes('--resume-prewrite');
|
|
const reloadExtension = process.argv.includes('--reload-extension');
|
|
const cleanupRuntime = process.argv.includes('--cleanup-runtime');
|
|
const portFile = argument('--port-file', process.env.CDP_PORT_FILE || DEFAULT_PORT_FILE);
|
|
const [port, browserId] = (await readFile(portFile, 'utf8')).trim().split(/\r?\n/);
|
|
if (!port || !browserId) throw new Error(`invalid DevToolsActivePort file: ${portFile}`);
|
|
|
|
const browserPath = browserId.startsWith('/') ? browserId : `/devtools/browser/${browserId}`;
|
|
const socket = await connect(`ws://127.0.0.1:${port}${browserPath}`);
|
|
const send = messenger(socket);
|
|
try {
|
|
const targets = await send('Target.getTargets');
|
|
const worker = (targets.targetInfos || []).find((target) => (
|
|
target.type === 'service_worker'
|
|
&& target.url === `chrome-extension://${EXTENSION_ID}/background.js`
|
|
));
|
|
if (!worker) throw new Error(`extension service worker not found: ${EXTENSION_ID}`);
|
|
const attached = await send('Target.attachToTarget', { targetId: worker.targetId, flatten: true });
|
|
const taskLiteral = JSON.stringify(taskId);
|
|
const inspectionExpression = `Promise.all([
|
|
chrome.storage.local.get(null),
|
|
Promise.resolve(typeof runningTasks === 'undefined' ? [] : [...runningTasks])
|
|
]).then(([saved, running]) => {
|
|
const taskId = ${taskLiteral};
|
|
const executions = saved.businessTaskExecutions || {};
|
|
const results = saved.businessTaskResults || {};
|
|
const result = results[taskId] || null;
|
|
const task = saved.currentBusinessTask || null;
|
|
const recent = Object.fromEntries(Object.entries(saved).filter(([key]) => /^last/i.test(key)));
|
|
return {
|
|
worker: { target_id: ${JSON.stringify(worker.targetId)}, url: ${JSON.stringify(worker.url)} },
|
|
running_tasks: running,
|
|
execution: executions[taskId] || null,
|
|
current_task: task ? {
|
|
task_id: task.task_id,
|
|
status: task.status,
|
|
execution_id: task.execution_id,
|
|
received_at: task.received_at,
|
|
action: task.operation?.action || ''
|
|
} : null,
|
|
result: result ? {
|
|
status: result.status,
|
|
stage: result.stage,
|
|
message: result.message,
|
|
execution_id: result.execution_id,
|
|
write_attempted: result.write_attempted,
|
|
erp_write_started: result.erp_write_started,
|
|
blockers: result.blockers || [],
|
|
report_status: result.report?.status || '',
|
|
erp_receipt: result.erp_receipt || result.report?.erp_receipt || null,
|
|
verification: result.verification || result.report?.verification || null
|
|
} : null,
|
|
recent_storage: recent
|
|
};
|
|
})`;
|
|
const expression = cleanupRuntime
|
|
? 'cleanupStaleRuntimeState()'
|
|
: reloadExtension
|
|
? `(() => { const version = chrome.runtime.getManifest().version; chrome.runtime.reload(); return { reload_requested: true, version }; })()`
|
|
: resumePrewrite
|
|
? `resumePrewriteTask(${taskLiteral})`
|
|
: inspectionExpression;
|
|
const evaluated = await send('Runtime.evaluate', {
|
|
expression,
|
|
awaitPromise: true,
|
|
returnByValue: true
|
|
}, attached.sessionId, resumePrewrite ? 240_000 : 8_000);
|
|
if (evaluated.exceptionDetails) throw new Error(evaluated.exceptionDetails.text || 'worker evaluation failed');
|
|
process.stdout.write(`${JSON.stringify(evaluated.result?.value || null, null, 2)}\n`);
|
|
await send('Target.detachFromTarget', { sessionId: attached.sessionId }).catch(() => {});
|
|
} finally {
|
|
socket.close();
|
|
}
|