cdp-skill 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/SKILL.md +34 -5
- package/package.json +2 -1
- package/src/capture/console-capture.js +241 -0
- package/src/capture/debug-capture.js +144 -0
- package/src/capture/error-aggregator.js +151 -0
- package/src/capture/eval-serializer.js +320 -0
- package/src/capture/index.js +40 -0
- package/src/capture/network-capture.js +211 -0
- package/src/capture/pdf-capture.js +256 -0
- package/src/capture/screenshot-capture.js +325 -0
- package/src/cdp/browser.js +569 -0
- package/src/cdp/connection.js +369 -0
- package/src/cdp/discovery.js +138 -0
- package/src/cdp/index.js +29 -0
- package/src/cdp/target-and-session.js +439 -0
- package/src/cdp-skill.js +25 -11
- package/src/constants.js +79 -0
- package/src/dom/actionability.js +638 -0
- package/src/dom/click-executor.js +923 -0
- package/src/dom/element-handle.js +496 -0
- package/src/dom/element-locator.js +475 -0
- package/src/dom/element-validator.js +120 -0
- package/src/dom/fill-executor.js +489 -0
- package/src/dom/index.js +248 -0
- package/src/dom/input-emulator.js +406 -0
- package/src/dom/keyboard-executor.js +202 -0
- package/src/dom/quad-helpers.js +89 -0
- package/src/dom/react-filler.js +94 -0
- package/src/dom/wait-executor.js +423 -0
- package/src/index.js +6 -6
- package/src/page/cookie-manager.js +202 -0
- package/src/page/dom-stability.js +181 -0
- package/src/page/index.js +36 -0
- package/src/{page.js → page/page-controller.js} +109 -839
- package/src/page/wait-utilities.js +302 -0
- package/src/page/web-storage-manager.js +108 -0
- package/src/runner/context-helpers.js +224 -0
- package/src/runner/execute-browser.js +518 -0
- package/src/runner/execute-form.js +315 -0
- package/src/runner/execute-input.js +308 -0
- package/src/runner/execute-interaction.js +672 -0
- package/src/runner/execute-navigation.js +180 -0
- package/src/runner/execute-query.js +771 -0
- package/src/runner/index.js +51 -0
- package/src/runner/step-executors.js +421 -0
- package/src/runner/step-validator.js +641 -0
- package/src/tests/Actionability.test.js +613 -0
- package/src/tests/BrowserClient.test.js +1 -1
- package/src/tests/ChromeDiscovery.test.js +1 -1
- package/src/tests/ClickExecutor.test.js +554 -0
- package/src/tests/ConsoleCapture.test.js +1 -1
- package/src/tests/ContextHelpers.test.js +453 -0
- package/src/tests/CookieManager.test.js +450 -0
- package/src/tests/DebugCapture.test.js +307 -0
- package/src/tests/ElementHandle.test.js +1 -1
- package/src/tests/ElementLocator.test.js +1 -1
- package/src/tests/ErrorAggregator.test.js +1 -1
- package/src/tests/EvalSerializer.test.js +391 -0
- package/src/tests/FillExecutor.test.js +611 -0
- package/src/tests/InputEmulator.test.js +1 -1
- package/src/tests/KeyboardExecutor.test.js +430 -0
- package/src/tests/NetworkErrorCapture.test.js +1 -1
- package/src/tests/PageController.test.js +1 -1
- package/src/tests/PdfCapture.test.js +333 -0
- package/src/tests/ScreenshotCapture.test.js +1 -1
- package/src/tests/SessionRegistry.test.js +1 -1
- package/src/tests/StepValidator.test.js +527 -0
- package/src/tests/TargetManager.test.js +1 -1
- package/src/tests/TestRunner.test.js +1 -1
- package/src/tests/WaitStrategy.test.js +1 -1
- package/src/tests/WaitUtilities.test.js +508 -0
- package/src/tests/WebStorageManager.test.js +333 -0
- package/src/types.js +309 -0
- package/src/capture.js +0 -1400
- package/src/cdp.js +0 -1286
- package/src/dom.js +0 -4379
- package/src/runner.js +0 -3676
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP Connection Module
|
|
3
|
+
* WebSocket-based Chrome DevTools Protocol connection handling
|
|
4
|
+
*
|
|
5
|
+
* PUBLIC EXPORTS:
|
|
6
|
+
* - createConnection(wsUrl, options?) - Factory for CDP WebSocket connection
|
|
7
|
+
*
|
|
8
|
+
* @module cdp-skill/cdp/connection
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a CDP WebSocket connection
|
|
13
|
+
* @param {string} wsUrl - WebSocket URL for CDP endpoint
|
|
14
|
+
* @param {Object} [options] - Connection options
|
|
15
|
+
* @param {number} [options.maxRetries=5] - Max reconnection attempts
|
|
16
|
+
* @param {number} [options.retryDelay=1000] - Base delay between retries
|
|
17
|
+
* @param {number} [options.maxRetryDelay=30000] - Maximum retry delay cap
|
|
18
|
+
* @param {boolean} [options.autoReconnect=false] - Enable auto reconnection
|
|
19
|
+
* @returns {import('../types.js').CDPConnection} Connection interface
|
|
20
|
+
*/
|
|
21
|
+
export function createConnection(wsUrl, options = {}) {
|
|
22
|
+
const maxRetries = options.maxRetries ?? 5;
|
|
23
|
+
const retryDelay = options.retryDelay ?? 1000;
|
|
24
|
+
const maxRetryDelay = options.maxRetryDelay ?? 30000;
|
|
25
|
+
const autoReconnect = options.autoReconnect ?? false;
|
|
26
|
+
|
|
27
|
+
let ws = null;
|
|
28
|
+
let messageId = 0;
|
|
29
|
+
const pendingCommands = new Map();
|
|
30
|
+
const eventListeners = new Map();
|
|
31
|
+
let connected = false;
|
|
32
|
+
let connecting = false;
|
|
33
|
+
let onCloseCallback = null;
|
|
34
|
+
let reconnecting = false;
|
|
35
|
+
let retryAttempt = 0;
|
|
36
|
+
let intentionalClose = false;
|
|
37
|
+
|
|
38
|
+
function emit(event, data = {}) {
|
|
39
|
+
const listeners = eventListeners.get(event);
|
|
40
|
+
if (listeners) {
|
|
41
|
+
for (const callback of listeners) {
|
|
42
|
+
try {
|
|
43
|
+
callback(data);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error(`Event handler error for ${event}:`, err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function calculateBackoff(attempt) {
|
|
52
|
+
const delay = retryDelay * Math.pow(2, attempt);
|
|
53
|
+
return Math.min(delay, maxRetryDelay);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sleep(ms) {
|
|
57
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function rejectPendingCommands(reason) {
|
|
61
|
+
for (const [id, pending] of pendingCommands) {
|
|
62
|
+
clearTimeout(pending.timer);
|
|
63
|
+
pending.reject(new Error(reason));
|
|
64
|
+
}
|
|
65
|
+
pendingCommands.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function handleMessage(data) {
|
|
69
|
+
let message;
|
|
70
|
+
try {
|
|
71
|
+
message = JSON.parse(data.toString());
|
|
72
|
+
} catch {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (message.id !== undefined) {
|
|
77
|
+
const pending = pendingCommands.get(message.id);
|
|
78
|
+
if (pending) {
|
|
79
|
+
clearTimeout(pending.timer);
|
|
80
|
+
pendingCommands.delete(message.id);
|
|
81
|
+
if (message.error) {
|
|
82
|
+
pending.reject(new Error(`CDP error: ${message.error.message}`));
|
|
83
|
+
} else {
|
|
84
|
+
pending.resolve(message.result);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (message.method) {
|
|
91
|
+
if (message.sessionId) {
|
|
92
|
+
const sessionEventKey = `${message.sessionId}:${message.method}`;
|
|
93
|
+
const sessionListeners = eventListeners.get(sessionEventKey);
|
|
94
|
+
if (sessionListeners) {
|
|
95
|
+
for (const callback of sessionListeners) {
|
|
96
|
+
try {
|
|
97
|
+
callback(message.params, message.sessionId);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(`Event handler error for ${sessionEventKey}:`, err);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const globalListeners = eventListeners.get(message.method);
|
|
106
|
+
if (globalListeners) {
|
|
107
|
+
for (const callback of globalListeners) {
|
|
108
|
+
try {
|
|
109
|
+
callback(message.params, message.sessionId);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(`Event handler error for ${message.method}:`, err);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function setupWebSocketListeners() {
|
|
119
|
+
ws.addEventListener('close', () => {
|
|
120
|
+
const wasConnected = connected;
|
|
121
|
+
connected = false;
|
|
122
|
+
connecting = false;
|
|
123
|
+
rejectPendingCommands('Connection closed');
|
|
124
|
+
|
|
125
|
+
if (wasConnected && !intentionalClose && autoReconnect) {
|
|
126
|
+
attemptReconnect();
|
|
127
|
+
} else if (wasConnected && onCloseCallback && !intentionalClose) {
|
|
128
|
+
onCloseCallback('Connection closed unexpectedly');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
ws.addEventListener('message', (event) => handleMessage(event.data));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function attemptReconnect() {
|
|
136
|
+
if (reconnecting || intentionalClose) return;
|
|
137
|
+
|
|
138
|
+
reconnecting = true;
|
|
139
|
+
retryAttempt = 0;
|
|
140
|
+
|
|
141
|
+
while (retryAttempt < maxRetries && !intentionalClose) {
|
|
142
|
+
const delay = calculateBackoff(retryAttempt);
|
|
143
|
+
emit('reconnecting', { attempt: retryAttempt + 1, delay });
|
|
144
|
+
|
|
145
|
+
await sleep(delay);
|
|
146
|
+
if (intentionalClose) break;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await doReconnect();
|
|
150
|
+
reconnecting = false;
|
|
151
|
+
retryAttempt = 0;
|
|
152
|
+
emit('reconnected', {});
|
|
153
|
+
return;
|
|
154
|
+
} catch {
|
|
155
|
+
retryAttempt++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
reconnecting = false;
|
|
160
|
+
if (!intentionalClose && onCloseCallback) {
|
|
161
|
+
onCloseCallback('Connection closed unexpectedly after max retries');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function doReconnect() {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
ws = new WebSocket(wsUrl);
|
|
168
|
+
ws.addEventListener('open', () => {
|
|
169
|
+
connected = true;
|
|
170
|
+
setupWebSocketListeners();
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
173
|
+
ws.addEventListener('error', (event) => {
|
|
174
|
+
reject(new Error(`CDP reconnection error: ${event.message || 'Connection failed'}`));
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Establish WebSocket connection
|
|
181
|
+
* @returns {Promise<void>}
|
|
182
|
+
*/
|
|
183
|
+
async function connect() {
|
|
184
|
+
if (connected) return;
|
|
185
|
+
if (connecting) throw new Error('Connection already in progress');
|
|
186
|
+
|
|
187
|
+
connecting = true;
|
|
188
|
+
intentionalClose = false;
|
|
189
|
+
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
ws = new WebSocket(wsUrl);
|
|
192
|
+
|
|
193
|
+
ws.addEventListener('open', () => {
|
|
194
|
+
connected = true;
|
|
195
|
+
connecting = false;
|
|
196
|
+
resolve();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
ws.addEventListener('error', (event) => {
|
|
200
|
+
connecting = false;
|
|
201
|
+
reject(new Error(`CDP connection error: ${event.message || 'Connection failed'}`));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
ws.addEventListener('close', () => {
|
|
205
|
+
const wasConnected = connected;
|
|
206
|
+
connected = false;
|
|
207
|
+
connecting = false;
|
|
208
|
+
rejectPendingCommands('Connection closed');
|
|
209
|
+
|
|
210
|
+
if (wasConnected && !intentionalClose && autoReconnect) {
|
|
211
|
+
attemptReconnect();
|
|
212
|
+
} else if (wasConnected && onCloseCallback && !intentionalClose) {
|
|
213
|
+
onCloseCallback('Connection closed unexpectedly');
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
ws.addEventListener('message', (event) => handleMessage(event.data));
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Send CDP command
|
|
223
|
+
* @param {string} method - CDP method name
|
|
224
|
+
* @param {Object} [params={}] - Command parameters
|
|
225
|
+
* @param {number} [timeout=30000] - Command timeout in ms
|
|
226
|
+
* @returns {Promise<Object>} Command result
|
|
227
|
+
*/
|
|
228
|
+
async function send(method, params = {}, timeout = 30000) {
|
|
229
|
+
if (!connected) throw new Error('Not connected to CDP');
|
|
230
|
+
|
|
231
|
+
const id = ++messageId;
|
|
232
|
+
const message = JSON.stringify({ id, method, params });
|
|
233
|
+
|
|
234
|
+
return new Promise((resolve, reject) => {
|
|
235
|
+
const timer = setTimeout(() => {
|
|
236
|
+
pendingCommands.delete(id);
|
|
237
|
+
reject(new Error(`CDP command timeout: ${method}`));
|
|
238
|
+
}, timeout);
|
|
239
|
+
|
|
240
|
+
pendingCommands.set(id, { resolve, reject, timer });
|
|
241
|
+
ws.send(message);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Send CDP command to specific session
|
|
247
|
+
* @param {string} sessionId - Target session ID
|
|
248
|
+
* @param {string} method - CDP method name
|
|
249
|
+
* @param {Object} [params={}] - Command parameters
|
|
250
|
+
* @param {number} [timeout=30000] - Command timeout in ms
|
|
251
|
+
* @returns {Promise<Object>} Command result
|
|
252
|
+
*/
|
|
253
|
+
async function sendToSession(sessionId, method, params = {}, timeout = 30000) {
|
|
254
|
+
if (!connected) throw new Error('Not connected to CDP');
|
|
255
|
+
|
|
256
|
+
const id = ++messageId;
|
|
257
|
+
const message = JSON.stringify({ id, sessionId, method, params });
|
|
258
|
+
|
|
259
|
+
return new Promise((resolve, reject) => {
|
|
260
|
+
const timer = setTimeout(() => {
|
|
261
|
+
pendingCommands.delete(id);
|
|
262
|
+
reject(new Error(`CDP command timeout: ${method} (session: ${sessionId})`));
|
|
263
|
+
}, timeout);
|
|
264
|
+
|
|
265
|
+
pendingCommands.set(id, { resolve, reject, timer });
|
|
266
|
+
ws.send(message);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Subscribe to CDP event
|
|
272
|
+
* @param {string} event - Event name
|
|
273
|
+
* @param {function} callback - Event handler
|
|
274
|
+
*/
|
|
275
|
+
function on(event, callback) {
|
|
276
|
+
if (!eventListeners.has(event)) {
|
|
277
|
+
eventListeners.set(event, new Set());
|
|
278
|
+
}
|
|
279
|
+
eventListeners.get(event).add(callback);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Unsubscribe from CDP event
|
|
284
|
+
* @param {string} event - Event name
|
|
285
|
+
* @param {function} callback - Event handler
|
|
286
|
+
*/
|
|
287
|
+
function off(event, callback) {
|
|
288
|
+
const listeners = eventListeners.get(event);
|
|
289
|
+
if (listeners) {
|
|
290
|
+
listeners.delete(callback);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Wait for specific event
|
|
296
|
+
* @param {string} event - Event name
|
|
297
|
+
* @param {function} [predicate=()=>true] - Filter predicate
|
|
298
|
+
* @param {number} [timeout=30000] - Timeout in ms
|
|
299
|
+
* @returns {Promise<Object>} Event parameters
|
|
300
|
+
*/
|
|
301
|
+
function waitForEvent(event, predicate = () => true, timeout = 30000) {
|
|
302
|
+
return new Promise((resolve, reject) => {
|
|
303
|
+
const timer = setTimeout(() => {
|
|
304
|
+
off(event, handler);
|
|
305
|
+
reject(new Error(`Timeout waiting for event: ${event}`));
|
|
306
|
+
}, timeout);
|
|
307
|
+
|
|
308
|
+
const handler = (params) => {
|
|
309
|
+
if (predicate(params)) {
|
|
310
|
+
clearTimeout(timer);
|
|
311
|
+
off(event, handler);
|
|
312
|
+
resolve(params);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
on(event, handler);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Close the connection
|
|
322
|
+
* @returns {Promise<void>}
|
|
323
|
+
*/
|
|
324
|
+
async function close() {
|
|
325
|
+
intentionalClose = true;
|
|
326
|
+
reconnecting = false;
|
|
327
|
+
if (ws) {
|
|
328
|
+
rejectPendingCommands('Connection closed');
|
|
329
|
+
ws.close();
|
|
330
|
+
ws = null;
|
|
331
|
+
connected = false;
|
|
332
|
+
eventListeners.clear();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Remove all event listeners
|
|
338
|
+
* @param {string} [event] - Specific event, or all if omitted
|
|
339
|
+
*/
|
|
340
|
+
function removeAllListeners(event) {
|
|
341
|
+
if (event) {
|
|
342
|
+
eventListeners.delete(event);
|
|
343
|
+
} else {
|
|
344
|
+
eventListeners.clear();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Set close callback
|
|
350
|
+
* @param {function} callback - Callback for unexpected close
|
|
351
|
+
*/
|
|
352
|
+
function onClose(callback) {
|
|
353
|
+
onCloseCallback = callback;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
connect,
|
|
358
|
+
send,
|
|
359
|
+
sendToSession,
|
|
360
|
+
on,
|
|
361
|
+
off,
|
|
362
|
+
waitForEvent,
|
|
363
|
+
close,
|
|
364
|
+
removeAllListeners,
|
|
365
|
+
onClose,
|
|
366
|
+
isConnected: () => connected,
|
|
367
|
+
getWsUrl: () => wsUrl
|
|
368
|
+
};
|
|
369
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP Discovery Module
|
|
3
|
+
* HTTP-based Chrome DevTools Protocol endpoint discovery
|
|
4
|
+
*
|
|
5
|
+
* PUBLIC EXPORTS:
|
|
6
|
+
* - createDiscovery(host?, port?, timeout?) - Factory for CDP discovery
|
|
7
|
+
* - discoverChrome(host?, port?, timeout?) - Convenience function
|
|
8
|
+
*
|
|
9
|
+
* @module cdp-skill/cdp/discovery
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Discover Chrome CDP endpoints via HTTP
|
|
14
|
+
* @param {string} [host='localhost'] - Chrome debugging host
|
|
15
|
+
* @param {number} [port=9222] - Chrome debugging port
|
|
16
|
+
* @param {number} [timeout=5000] - Request timeout in ms
|
|
17
|
+
* @returns {Object} Discovery interface
|
|
18
|
+
*/
|
|
19
|
+
export function createDiscovery(host = 'localhost', port = 9222, timeout = 5000) {
|
|
20
|
+
const baseUrl = `http://${host}:${port}`;
|
|
21
|
+
|
|
22
|
+
function createTimeoutController() {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
25
|
+
return {
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
clear: () => clearTimeout(timeoutId)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get Chrome version information
|
|
33
|
+
* @returns {Promise<{browser: string, protocolVersion: string, webSocketDebuggerUrl: string}>}
|
|
34
|
+
*/
|
|
35
|
+
async function getVersion() {
|
|
36
|
+
const timeoutCtrl = createTimeoutController();
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch(`${baseUrl}/json/version`, { signal: timeoutCtrl.signal });
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Chrome not reachable at ${baseUrl}: ${response.status}`);
|
|
41
|
+
}
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
return {
|
|
44
|
+
browser: data.Browser,
|
|
45
|
+
protocolVersion: data['Protocol-Version'],
|
|
46
|
+
webSocketDebuggerUrl: data.webSocketDebuggerUrl
|
|
47
|
+
};
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err.name === 'AbortError') {
|
|
50
|
+
throw new Error(`Chrome discovery timeout at ${baseUrl}`);
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
} finally {
|
|
54
|
+
timeoutCtrl.clear();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get all browser targets
|
|
60
|
+
* @returns {Promise<Array>} Array of target info objects
|
|
61
|
+
*/
|
|
62
|
+
async function getTargets() {
|
|
63
|
+
const timeoutCtrl = createTimeoutController();
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(`${baseUrl}/json/list`, { signal: timeoutCtrl.signal });
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error(`Failed to get targets: ${response.status}`);
|
|
68
|
+
}
|
|
69
|
+
return response.json();
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err.name === 'AbortError') {
|
|
72
|
+
throw new Error('Chrome discovery timeout getting targets');
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
} finally {
|
|
76
|
+
timeoutCtrl.clear();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get page targets only
|
|
82
|
+
* @returns {Promise<Array>} Array of page target info objects
|
|
83
|
+
*/
|
|
84
|
+
async function getPages() {
|
|
85
|
+
const targets = await getTargets();
|
|
86
|
+
return targets.filter(t => t.type === 'page');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Find a page by URL pattern
|
|
91
|
+
* @param {string|RegExp} urlPattern - URL pattern to match
|
|
92
|
+
* @returns {Promise<Object|null>} Matching target or null
|
|
93
|
+
*/
|
|
94
|
+
async function findPageByUrl(urlPattern) {
|
|
95
|
+
const pages = await getPages();
|
|
96
|
+
const regex = urlPattern instanceof RegExp ? urlPattern : new RegExp(urlPattern);
|
|
97
|
+
return pages.find(p => regex.test(p.url)) || null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if Chrome is available
|
|
102
|
+
* @returns {Promise<boolean>}
|
|
103
|
+
*/
|
|
104
|
+
async function isAvailable() {
|
|
105
|
+
try {
|
|
106
|
+
await getVersion();
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
getVersion,
|
|
115
|
+
getTargets,
|
|
116
|
+
getPages,
|
|
117
|
+
findPageByUrl,
|
|
118
|
+
isAvailable
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Convenience function to discover Chrome
|
|
124
|
+
* @param {string} [host='localhost'] - Chrome debugging host
|
|
125
|
+
* @param {number} [port=9222] - Chrome debugging port
|
|
126
|
+
* @param {number} [timeout=5000] - Request timeout in ms
|
|
127
|
+
* @returns {Promise<{wsUrl: string, version: Object, targets: Array}>}
|
|
128
|
+
*/
|
|
129
|
+
export async function discoverChrome(host = 'localhost', port = 9222, timeout = 5000) {
|
|
130
|
+
const discovery = createDiscovery(host, port, timeout);
|
|
131
|
+
const version = await discovery.getVersion();
|
|
132
|
+
const targets = await discovery.getTargets();
|
|
133
|
+
return {
|
|
134
|
+
wsUrl: version.webSocketDebuggerUrl,
|
|
135
|
+
version,
|
|
136
|
+
targets
|
|
137
|
+
};
|
|
138
|
+
}
|
package/src/cdp/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP Protocol Module
|
|
3
|
+
* Re-exports all CDP-related functionality
|
|
4
|
+
*
|
|
5
|
+
* @module cdp-skill/cdp
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Connection
|
|
9
|
+
export { createConnection } from './connection.js';
|
|
10
|
+
|
|
11
|
+
// Discovery
|
|
12
|
+
export { createDiscovery, discoverChrome } from './discovery.js';
|
|
13
|
+
|
|
14
|
+
// Target and Session Management
|
|
15
|
+
export {
|
|
16
|
+
createTargetManager,
|
|
17
|
+
createSessionRegistry,
|
|
18
|
+
createPageSession
|
|
19
|
+
} from './target-and-session.js';
|
|
20
|
+
|
|
21
|
+
// Browser Client and Launcher
|
|
22
|
+
export {
|
|
23
|
+
createBrowser,
|
|
24
|
+
findChromePath,
|
|
25
|
+
launchChrome,
|
|
26
|
+
getChromeStatus,
|
|
27
|
+
isChromeProcessRunning,
|
|
28
|
+
createNewTab
|
|
29
|
+
} from './browser.js';
|