Files
platform/src/servers/api/browser/cdp.ts
T
2026-02-27 08:27:43 +00:00

100 lines
3.1 KiB
TypeScript

type CdpCommandOptions = {
relayPort: number;
userToken: string;
targetId?: string;
};
async function sendCdpCommand(opts: CdpCommandOptions, method: string, params?: unknown): Promise<unknown> {
const { relayPort, userToken, targetId } = opts;
const url = `ws://127.0.0.1:${relayPort}/cdp?token=${encodeURIComponent(userToken)}`;
return await new Promise<unknown>((resolve, reject) => {
const ws = new WebSocket(url);
let settled = false;
const timeout = setTimeout(() => {
if (settled) return;
settled = true;
ws.close();
reject(new Error(`CDP command timeout: ${method}`));
}, 30_000);
ws.addEventListener('open', () => {
const cmd: Record<string, unknown> = { id: 1, method };
if (params) cmd.params = params;
if (targetId) cmd.sessionId = targetId;
ws.send(JSON.stringify(cmd));
});
ws.addEventListener('message', (event) => {
if (settled) return;
try {
const msg = JSON.parse(String(event.data)) as { id?: number; result?: unknown; error?: { message: string } };
if (msg.id === 1) {
settled = true;
clearTimeout(timeout);
ws.close();
if (msg.error) reject(new Error(msg.error.message));
else resolve(msg.result);
}
} catch {
// ignore parse errors, wait for correct message
}
});
ws.addEventListener('error', () => {
if (settled) return;
settled = true;
clearTimeout(timeout);
reject(new Error('CDP WebSocket connection failed'));
});
ws.addEventListener('close', () => {
if (settled) return;
settled = true;
clearTimeout(timeout);
reject(new Error('CDP WebSocket closed before response'));
});
});
}
export async function captureScreenshot(
opts: CdpCommandOptions,
format: 'png' | 'jpeg' = 'png',
quality?: number,
): Promise<string> {
const params: Record<string, unknown> = { format };
if (quality !== undefined) params.quality = quality;
const result = (await sendCdpCommand(opts, 'Page.captureScreenshot', params)) as { data: string };
return result.data;
}
export async function evaluateJS(opts: CdpCommandOptions, expression: string): Promise<unknown> {
const result = (await sendCdpCommand(opts, 'Runtime.evaluate', {
expression,
returnByValue: true,
awaitPromise: true,
})) as { result?: { value?: unknown; description?: string }; exceptionDetails?: { text?: string } };
if (result.exceptionDetails) {
throw new Error(result.exceptionDetails.text ?? 'Evaluation failed');
}
return result.result?.value;
}
export async function navigateTo(opts: CdpCommandOptions, url: string): Promise<void> {
await sendCdpCommand(opts, 'Page.navigate', { url });
}
export async function getPageInfo(opts: CdpCommandOptions): Promise<{ title: string; url: string }> {
const result = (await sendCdpCommand(opts, 'Runtime.evaluate', {
expression: 'JSON.stringify({ title: document.title, url: location.href })',
returnByValue: true,
})) as { result?: { value?: string } };
try {
return JSON.parse(result.result?.value ?? '{}');
} catch {
return { title: '', url: '' };
}
}