111 lines
4.2 KiB
JavaScript
111 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { readFile } from 'node:fs/promises';
|
|
import net from 'node:net';
|
|
|
|
const PAGES_FILE = '/Users/inmanx/.cache/cdp/pages.json';
|
|
const SOCKET_ROOT = '/Users/inmanx/.cache/cdp';
|
|
const ORIGIN = 'http://127.0.0.1:8786';
|
|
|
|
function argument(name, fallback = '') {
|
|
const index = process.argv.indexOf(name);
|
|
return index >= 0 && process.argv[index + 1] ? process.argv[index + 1] : fallback;
|
|
}
|
|
|
|
function daemonRequest(socketPath, cmd, args = []) {
|
|
return new Promise((resolve, reject) => {
|
|
const socket = net.createConnection(socketPath);
|
|
let buffer = '';
|
|
const timer = setTimeout(() => {
|
|
socket.destroy();
|
|
reject(new Error(`CDP daemon ${cmd} timed out`));
|
|
}, 15_000);
|
|
socket.on('connect', () => {
|
|
socket.write(`${JSON.stringify({ id: 1, cmd, args })}\n`);
|
|
});
|
|
socket.on('data', (chunk) => {
|
|
buffer += chunk.toString();
|
|
const newline = buffer.indexOf('\n');
|
|
if (newline < 0) return;
|
|
try {
|
|
const response = JSON.parse(buffer.slice(0, newline));
|
|
clearTimeout(timer);
|
|
socket.end();
|
|
if (!response.ok) reject(new Error(response.error || `CDP daemon ${cmd} failed`));
|
|
else resolve(response.result || '');
|
|
} catch (error) {
|
|
clearTimeout(timer);
|
|
socket.destroy();
|
|
reject(error);
|
|
}
|
|
});
|
|
socket.on('error', (error) => {
|
|
clearTimeout(timer);
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
const taskId = argument('--task-id');
|
|
const storageKey = argument('--storage-key');
|
|
const targetPrefix = argument('--target', 'DA600CC0');
|
|
if (!/^TASK-[A-Za-z0-9_-]+$/.test(taskId)) throw new Error('--task-id is required');
|
|
if (!/^ltjt_[A-Za-z0-9_-]+$/.test(storageKey)) throw new Error('--storage-key is required');
|
|
|
|
const pages = JSON.parse(await readFile(PAGES_FILE, 'utf8'));
|
|
const page = pages.find((target) => target.targetId.startsWith(targetPrefix));
|
|
if (!page) throw new Error(`page target not found: ${targetPrefix}`);
|
|
const socketPath = `${SOCKET_ROOT}/cdp-${page.targetId}.sock`;
|
|
const evaluated = JSON.parse(await daemonRequest(socketPath, 'evalraw', [
|
|
'Runtime.evaluate',
|
|
JSON.stringify({
|
|
expression: `sessionStorage.getItem(${JSON.stringify(storageKey)})`,
|
|
returnByValue: true
|
|
})
|
|
]));
|
|
const rawResult = evaluated.result?.value;
|
|
if (!rawResult) throw new Error('stored extension result is missing');
|
|
const result = JSON.parse(rawResult);
|
|
if (!result.execution_id || result.status !== 'completed') throw new Error('stored extension result is not a completed durable result');
|
|
|
|
const cookieResult = JSON.parse(await daemonRequest(socketPath, 'evalraw', [
|
|
'Network.getCookies',
|
|
JSON.stringify({ urls: [`${ORIGIN}/`] })
|
|
]));
|
|
const cookies = (cookieResult.cookies || []).filter((cookie) => cookie.domain === '127.0.0.1' || cookie.domain === 'localhost');
|
|
if (!cookies.length) throw new Error('authenticated browser session cookie is missing');
|
|
const cookieHeader = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');
|
|
|
|
const csrfResponse = await fetch(`${ORIGIN}/api/auth/csrf`, {
|
|
headers: { Cookie: cookieHeader },
|
|
cache: 'no-store'
|
|
});
|
|
const csrfBody = await csrfResponse.json();
|
|
if (!csrfResponse.ok || !csrfBody.csrf_token) throw new Error(`CSRF request failed: HTTP ${csrfResponse.status}`);
|
|
|
|
const response = await fetch(`${ORIGIN}/api/tasks/${encodeURIComponent(taskId)}/result`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Cookie: cookieHeader,
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': csrfBody.csrf_token
|
|
},
|
|
body: JSON.stringify({
|
|
connection_id: `task-owner-browser:${ORIGIN}`,
|
|
execution_id: result.execution_id,
|
|
result
|
|
})
|
|
});
|
|
const body = await response.json();
|
|
if (!response.ok) throw new Error(body.message || body.error || `result persistence failed: HTTP ${response.status}`);
|
|
process.stdout.write(`${JSON.stringify({
|
|
status: 'extension_result_persisted',
|
|
http_status: response.status,
|
|
task_id: body.task?.task_id || taskId,
|
|
task_status: body.task?.status || '',
|
|
task_stage: body.task?.stage || '',
|
|
execution_id: result.execution_id,
|
|
report_status: result.report?.status || '',
|
|
requery_matched: result.report?.requery?.matched === true
|
|
}, null, 2)}\n`);
|