100 lines
3.1 KiB
TypeScript
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: '' };
|
|
}
|
|
}
|