@dev-blinq/cucumber_client 1.0.1596-dev → 1.0.1597-dev
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/bin/client/recorderv3/services.js +65 -560
- package/package.json +1 -1
|
@@ -214,9 +214,8 @@ export class PublishService {
|
|
|
214
214
|
export class RemoteBrowserService extends EventEmitter {
|
|
215
215
|
CDP_CONNECT_URL;
|
|
216
216
|
context;
|
|
217
|
-
// stableTabId, Page Info
|
|
218
217
|
pages = new Map();
|
|
219
|
-
_selectedPageId = null;
|
|
218
|
+
_selectedPageId = null;
|
|
220
219
|
constructor({ CDP_CONNECT_URL, context }) {
|
|
221
220
|
super();
|
|
222
221
|
this.CDP_CONNECT_URL = CDP_CONNECT_URL;
|
|
@@ -228,15 +227,46 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
228
227
|
const timestamp = new Date().toISOString();
|
|
229
228
|
console.log(`[${timestamp}] [RemoteBrowserService] ${message}`, data ? JSON.stringify(data, null, 2) : "");
|
|
230
229
|
}
|
|
230
|
+
// Simplified: Get the ID directly from the page
|
|
231
|
+
async getCdpTargetId(page) {
|
|
232
|
+
try {
|
|
233
|
+
const cdpSession = await page.context().newCDPSession(page);
|
|
234
|
+
const { targetInfo } = await cdpSession.send("Target.getTargetInfo");
|
|
235
|
+
await cdpSession.detach();
|
|
236
|
+
if (targetInfo && targetInfo.targetId) {
|
|
237
|
+
this.log("✅ Found CDP ID by session", { id: targetInfo.targetId, url: page.url() });
|
|
238
|
+
return targetInfo.targetId;
|
|
239
|
+
}
|
|
240
|
+
throw new Error("Target.getTargetInfo did not return a targetId");
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
this.log("❌ Error getting CDP ID by session", { url: page.url(), error });
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async getDebugURLs() {
|
|
248
|
+
const url = `${this.CDP_CONNECT_URL}/json?t=${Date.now()}`; // Cache-busting
|
|
249
|
+
try {
|
|
250
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
251
|
+
if (!response.ok) {
|
|
252
|
+
this.log("❌ Failed to fetch debug URLs", { status: response.status });
|
|
253
|
+
throw new Error("Error while fetching debug URL");
|
|
254
|
+
}
|
|
255
|
+
return await response.json();
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
this.log("❌ Exception while fetching debug URLs", { error });
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
231
262
|
async initializeListeners() {
|
|
232
263
|
this.log("📡 Initializing listeners");
|
|
233
264
|
this.context.on("page", async (page) => {
|
|
234
265
|
const stableTabId = uuidv4();
|
|
235
266
|
this.log("🆕 New page event triggered", { stableTabId, url: page.url() });
|
|
236
|
-
this.
|
|
237
|
-
|
|
267
|
+
const cdpTargetId = await this.getCdpTargetId(page);
|
|
268
|
+
this.pages.set(stableTabId, { page, cdpTargetId });
|
|
238
269
|
if (cdpTargetId) {
|
|
239
|
-
this.pages.get(stableTabId).cdpTargetId = cdpTargetId;
|
|
240
270
|
this.log("✅ Page mapped to CDP ID", { stableTabId, cdpTargetId });
|
|
241
271
|
}
|
|
242
272
|
else {
|
|
@@ -246,115 +276,37 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
246
276
|
this._selectedPageId = stableTabId;
|
|
247
277
|
}
|
|
248
278
|
await this.syncState();
|
|
249
|
-
page.on("load",
|
|
250
|
-
|
|
251
|
-
const newCdpId = await this.findCdpTargetId(page);
|
|
252
|
-
const pageInfo = this.pages.get(stableTabId);
|
|
253
|
-
if (pageInfo && newCdpId && pageInfo.cdpTargetId !== newCdpId) {
|
|
254
|
-
this.log("🔄 CDP Target ID changed on navigation", {
|
|
255
|
-
stableTabId,
|
|
256
|
-
old: pageInfo.cdpTargetId,
|
|
257
|
-
new: newCdpId,
|
|
258
|
-
});
|
|
259
|
-
pageInfo.cdpTargetId = newCdpId;
|
|
260
|
-
}
|
|
261
|
-
await this.syncState();
|
|
262
|
-
});
|
|
279
|
+
page.on("load", () => this.syncState());
|
|
280
|
+
page.on("framenavigated", () => this.syncState());
|
|
263
281
|
page.on("close", async () => {
|
|
264
|
-
this.log("🗑️ Page close event", { stableTabId
|
|
282
|
+
this.log("🗑️ Page close event", { stableTabId });
|
|
265
283
|
this.pages.delete(stableTabId);
|
|
266
|
-
this.log("✅ Page removed from internal map", {
|
|
267
|
-
stableTabId,
|
|
268
|
-
remaining: this.pages.size,
|
|
269
|
-
});
|
|
270
284
|
if (this._selectedPageId === stableTabId) {
|
|
271
|
-
|
|
272
|
-
this.
|
|
273
|
-
this.log("🔄 Selected page changed after close", {
|
|
274
|
-
oldSelectedId: stableTabId,
|
|
275
|
-
newSelectedId: this._selectedPageId,
|
|
276
|
-
});
|
|
285
|
+
this._selectedPageId = this.pages.size > 0 ? Array.from(this.pages.keys())[0] : null;
|
|
286
|
+
this.log("🔄 Selected page changed after close", { newSelectedId: this._selectedPageId });
|
|
277
287
|
}
|
|
278
288
|
await this.syncState();
|
|
279
289
|
});
|
|
280
|
-
page.on("framenavigated", async (frame) => {
|
|
281
|
-
this.log("🔄 Frame navigated event", { stableTabId, url: frame.url() });
|
|
282
|
-
await this.syncState();
|
|
283
|
-
});
|
|
284
290
|
});
|
|
291
|
+
// Initialize with existing pages
|
|
285
292
|
const existingPages = this.context.pages();
|
|
286
293
|
this.log("📄 Found existing pages", { count: existingPages.length });
|
|
287
294
|
for (const page of existingPages) {
|
|
288
295
|
const stableTabId = uuidv4();
|
|
289
|
-
const cdpTargetId = await this.
|
|
296
|
+
const cdpTargetId = await this.getCdpTargetId(page);
|
|
290
297
|
this.pages.set(stableTabId, { page, cdpTargetId });
|
|
291
|
-
this.log("✅ Existing page added to map", {
|
|
292
|
-
stableTabId,
|
|
293
|
-
cdpTargetId,
|
|
294
|
-
url: page.url(),
|
|
295
|
-
});
|
|
298
|
+
this.log("✅ Existing page added to map", { stableTabId, cdpTargetId, url: page.url() });
|
|
296
299
|
}
|
|
297
300
|
if (this.pages.size > 0 && !this._selectedPageId) {
|
|
298
301
|
this._selectedPageId = Array.from(this.pages.keys())[0];
|
|
299
|
-
this.log("🎯 Initial selected page set", { selectedPageId: this._selectedPageId });
|
|
300
302
|
}
|
|
301
303
|
await this.syncState();
|
|
302
304
|
}
|
|
303
|
-
async findCdpTargetId(page) {
|
|
304
|
-
try {
|
|
305
|
-
// This is the most direct and reliable way to get the ID
|
|
306
|
-
const cdpSession = await page.context().newCDPSession(page);
|
|
307
|
-
const { targetInfo } = await cdpSession.send("Target.getTargetInfo");
|
|
308
|
-
await cdpSession.detach();
|
|
309
|
-
if (targetInfo && targetInfo.targetId) {
|
|
310
|
-
this.log("🎭 Target info", targetInfo);
|
|
311
|
-
this.log("✅ Found CDP ID by session", { id: targetInfo.targetId, url: page.url() });
|
|
312
|
-
return targetInfo.targetId;
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
throw new Error("Target.getTargetInfo did not return a targetId");
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch (cdpError) {
|
|
319
|
-
// If the session fails, fall back to a simple URL match
|
|
320
|
-
this.log("⚠️ CDP session failed, trying URL match as fallback...", { url: page.url(), cdpError });
|
|
321
|
-
try {
|
|
322
|
-
const debugData = await this.getDebugURLs(); // This now has the cache-busting fix
|
|
323
|
-
const match = debugData.find((p) => p.type === "page" && p.url === page.url());
|
|
324
|
-
if (match) {
|
|
325
|
-
this.log("✅ Found CDP ID by exact URL as fallback", { id: match.id, url: page.url() });
|
|
326
|
-
return match.id;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
catch (fetchError) {
|
|
330
|
-
this.log("❌ Error in fallback getDebugURLs", { fetchError });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
this.log("❌ No match found for page", { url: page.url() });
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
async getDebugURLs() {
|
|
337
|
-
const url = `${this.CDP_CONNECT_URL}/json?_=${Date.now()}`; // Cache-busting query param
|
|
338
|
-
try {
|
|
339
|
-
const response = await fetch(url, { cache: "no-store" });
|
|
340
|
-
if (!response.ok) {
|
|
341
|
-
throw new Error("Error while fetching debug URL");
|
|
342
|
-
}
|
|
343
|
-
return await response.json();
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
this.log("❌ Exception while fetching debug URLs", { error });
|
|
347
|
-
return [];
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
305
|
async syncState() {
|
|
351
306
|
try {
|
|
352
307
|
this.log("🔄 Starting state sync");
|
|
353
308
|
const state = await this.getState();
|
|
354
|
-
this.log("✅ State sync complete", {
|
|
355
|
-
pagesCount: state.pages.length,
|
|
356
|
-
selectedPageId: state.selectedPageId,
|
|
357
|
-
});
|
|
309
|
+
this.log("✅ State sync complete", { pagesCount: state.pages.length, selectedPageId: state.selectedPageId });
|
|
358
310
|
this.emit("BrowserService.stateSync", state);
|
|
359
311
|
}
|
|
360
312
|
catch (error) {
|
|
@@ -363,38 +315,36 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
363
315
|
}
|
|
364
316
|
async getState() {
|
|
365
317
|
this.log("📊 Getting current state");
|
|
318
|
+
// Get the *live* list of debuggable pages
|
|
366
319
|
const debugData = await this.getDebugURLs();
|
|
367
|
-
|
|
320
|
+
const debugMap = new Map(debugData.map((d) => [d.id, d]));
|
|
368
321
|
const pagesData = [];
|
|
369
322
|
for (const [stableTabId, pageInfo] of this.pages.entries()) {
|
|
370
|
-
let currentCdpId = pageInfo.cdpTargetId;
|
|
371
|
-
let debugInfo = debugData.find((d) => d.id === currentCdpId);
|
|
372
|
-
this.log("🔍 debugInfo ", debugInfo);
|
|
373
|
-
if (!debugInfo) {
|
|
374
|
-
this.log("⚠️ Re-finding CDP ID for", { stableTabId });
|
|
375
|
-
const newCdpId = await this.findCdpTargetId(pageInfo.page);
|
|
376
|
-
if (newCdpId) {
|
|
377
|
-
pageInfo.cdpTargetId = newCdpId;
|
|
378
|
-
debugInfo = debugData.find((d) => d.id === newCdpId);
|
|
379
|
-
this.log("🔍 Updated debugInfo after re-finding CDP ID", debugInfo);
|
|
380
|
-
this.log("✅ Re-found CDP ID", { stableTabId, newCdpId });
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
323
|
try {
|
|
324
|
+
// Re-verify the CDP ID in case it changed
|
|
325
|
+
const currentCdpId = await this.getCdpTargetId(pageInfo.page);
|
|
326
|
+
if (currentCdpId && pageInfo.cdpTargetId !== currentCdpId) {
|
|
327
|
+
this.log("🔄 CDP ID changed", { stableTabId, old: pageInfo.cdpTargetId, new: currentCdpId });
|
|
328
|
+
pageInfo.cdpTargetId = currentCdpId;
|
|
329
|
+
}
|
|
330
|
+
// Get the debug info from the live map
|
|
331
|
+
const debugInfo = currentCdpId ? debugMap.get(currentCdpId) : undefined;
|
|
384
332
|
pagesData.push({
|
|
385
|
-
id: stableTabId,
|
|
333
|
+
id: stableTabId,
|
|
386
334
|
title: await pageInfo.page.title(),
|
|
387
335
|
url: pageInfo.page.url(),
|
|
388
|
-
wsDebuggerUrl: debugInfo?.webSocketDebuggerUrl || "",
|
|
336
|
+
wsDebuggerUrl: debugInfo?.webSocketDebuggerUrl || "",
|
|
389
337
|
});
|
|
390
338
|
}
|
|
391
339
|
catch (error) {
|
|
340
|
+
// This likely means the page was closed during the loop
|
|
392
341
|
this.log("❌ Error getting page data (page might be closed)", { stableTabId });
|
|
342
|
+
this.pages.delete(stableTabId); // Clean up dead page
|
|
393
343
|
}
|
|
394
344
|
}
|
|
345
|
+
// Ensure selectedPageId is valid
|
|
395
346
|
if (this._selectedPageId && !this.pages.has(this._selectedPageId)) {
|
|
396
|
-
this._selectedPageId =
|
|
397
|
-
this.log("🔄 Corrected selectedPageId", { new: this._selectedPageId });
|
|
347
|
+
this._selectedPageId = pagesData.length > 0 ? pagesData[0].id : null;
|
|
398
348
|
}
|
|
399
349
|
const state = {
|
|
400
350
|
pages: pagesData,
|
|
@@ -406,10 +356,11 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
406
356
|
async createTab(url = "about:blank") {
|
|
407
357
|
try {
|
|
408
358
|
this.log("🆕 Creating new tab", { url });
|
|
409
|
-
const page = await this.context.newPage(); //
|
|
359
|
+
const page = await this.context.newPage(); // Triggers 'page' event
|
|
410
360
|
if (url !== "about:blank") {
|
|
411
361
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
412
362
|
}
|
|
363
|
+
// Find the stableTabId that was just created by the 'page' event
|
|
413
364
|
for (const [stableTabId, pageInfo] of this.pages.entries()) {
|
|
414
365
|
if (pageInfo.page === page) {
|
|
415
366
|
this._selectedPageId = stableTabId;
|
|
@@ -428,7 +379,7 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
428
379
|
this.log("🗑️ Closing tab", { stableTabId });
|
|
429
380
|
const pageInfo = this.pages.get(stableTabId);
|
|
430
381
|
if (pageInfo) {
|
|
431
|
-
await pageInfo.page.close(); //
|
|
382
|
+
await pageInfo.page.close(); // Triggers 'close' event
|
|
432
383
|
}
|
|
433
384
|
else {
|
|
434
385
|
this.log("⚠️ Page not found for closing", { stableTabId });
|
|
@@ -458,462 +409,16 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
458
409
|
}
|
|
459
410
|
getSelectedPage() {
|
|
460
411
|
const pageInfo = this._selectedPageId ? this.pages.get(this._selectedPageId) : null;
|
|
461
|
-
this.log("🔍 Getting selected page", {
|
|
462
|
-
selectedPageId: this._selectedPageId,
|
|
463
|
-
found: !!pageInfo,
|
|
464
|
-
url: pageInfo?.page.url(),
|
|
465
|
-
});
|
|
466
412
|
return pageInfo?.page || null;
|
|
467
413
|
}
|
|
468
414
|
destroy() {
|
|
469
415
|
this.log("💥 Destroying RemoteBrowserService");
|
|
470
|
-
// Remove all listeners *this* instance has registered
|
|
471
|
-
// This stops it from listening to context/page events
|
|
472
416
|
this.context.removeAllListeners("page");
|
|
473
|
-
for (const [
|
|
474
|
-
pageInfo.page.removeAllListeners(
|
|
475
|
-
pageInfo.page.removeAllListeners("close");
|
|
476
|
-
pageInfo.page.removeAllListeners("framenavigated");
|
|
417
|
+
for (const [, pageInfo] of this.pages.entries()) {
|
|
418
|
+
pageInfo.page.removeAllListeners();
|
|
477
419
|
}
|
|
478
|
-
// Clear internal maps
|
|
479
420
|
this.pages.clear();
|
|
480
421
|
this._selectedPageId = null;
|
|
481
|
-
// Remove all listeners *on this* emitter
|
|
482
422
|
this.removeAllListeners();
|
|
483
423
|
}
|
|
484
424
|
}
|
|
485
|
-
// export class RemoteBrowserService extends EventEmitter {
|
|
486
|
-
// private CDP_CONNECT_URL: string;
|
|
487
|
-
// private context: BrowserContext;
|
|
488
|
-
// private pages: Map<string, Page> = new Map();
|
|
489
|
-
// private _selectedPageId: string | null = null;
|
|
490
|
-
// constructor({ CDP_CONNECT_URL, context }: { CDP_CONNECT_URL: string; context: BrowserContext }) {
|
|
491
|
-
// super();
|
|
492
|
-
// this.CDP_CONNECT_URL = CDP_CONNECT_URL;
|
|
493
|
-
// this.context = context;
|
|
494
|
-
// this.log("🚀 RemoteBrowserService initialized", { CDP_CONNECT_URL });
|
|
495
|
-
// this.initializeListeners();
|
|
496
|
-
// }
|
|
497
|
-
// private log(message: string, data?: any) {
|
|
498
|
-
// const timestamp = new Date().toISOString();
|
|
499
|
-
// console.log(`[${timestamp}] [RemoteBrowserService] ${message}`, data ? JSON.stringify(data, null, 2) : "");
|
|
500
|
-
// }
|
|
501
|
-
// private async initializeListeners() {
|
|
502
|
-
// this.log("📡 Initializing listeners");
|
|
503
|
-
// // Listen for new pages
|
|
504
|
-
// this.context.on("page", async (page) => {
|
|
505
|
-
// this.log("🆕 New page event triggered", { url: page.url() });
|
|
506
|
-
// const id = await this.getPageId(page);
|
|
507
|
-
// this.log("🔍 Got page ID from CDP", { id, url: page.url() });
|
|
508
|
-
// if (id) {
|
|
509
|
-
// this.pages.set(id, page);
|
|
510
|
-
// this.log("✅ Page added to internal map", {
|
|
511
|
-
// id,
|
|
512
|
-
// url: page.url(),
|
|
513
|
-
// totalPages: this.pages.size,
|
|
514
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
515
|
-
// });
|
|
516
|
-
// await this.syncState();
|
|
517
|
-
// } else {
|
|
518
|
-
// this.log("❌ Failed to get page ID, page not added to map", { url: page.url() });
|
|
519
|
-
// }
|
|
520
|
-
// // Listen for page updates
|
|
521
|
-
// page.on("load", async () => {
|
|
522
|
-
// this.log("🔄 Page load event", { id, url: page.url() });
|
|
523
|
-
// await this.syncState();
|
|
524
|
-
// });
|
|
525
|
-
// page.on("close", async () => {
|
|
526
|
-
// this.log("🗑️ Page close event", { id, url: page.url() });
|
|
527
|
-
// if (id) {
|
|
528
|
-
// this.pages.delete(id);
|
|
529
|
-
// this.log("✅ Page removed from internal map", {
|
|
530
|
-
// id,
|
|
531
|
-
// remainingPages: this.pages.size,
|
|
532
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
533
|
-
// });
|
|
534
|
-
// if (this._selectedPageId === id) {
|
|
535
|
-
// const firstPage = Array.from(this.pages.keys())[0];
|
|
536
|
-
// this._selectedPageId = firstPage || null;
|
|
537
|
-
// this.log("🔄 Selected page changed after close", {
|
|
538
|
-
// oldSelectedId: id,
|
|
539
|
-
// newSelectedId: this._selectedPageId,
|
|
540
|
-
// });
|
|
541
|
-
// }
|
|
542
|
-
// await this.syncState();
|
|
543
|
-
// }
|
|
544
|
-
// });
|
|
545
|
-
// });
|
|
546
|
-
// // Initialize with existing pages
|
|
547
|
-
// const existingPages = this.context.pages();
|
|
548
|
-
// this.log("📄 Found existing pages", { count: existingPages.length });
|
|
549
|
-
// for (const page of existingPages) {
|
|
550
|
-
// const id = await this.getPageId(page);
|
|
551
|
-
// this.log("🔍 Processing existing page", { id, url: page.url() });
|
|
552
|
-
// if (id) {
|
|
553
|
-
// this.pages.set(id, page);
|
|
554
|
-
// this.log("✅ Existing page added to map", {
|
|
555
|
-
// id,
|
|
556
|
-
// url: page.url(),
|
|
557
|
-
// totalPages: this.pages.size,
|
|
558
|
-
// });
|
|
559
|
-
// }
|
|
560
|
-
// }
|
|
561
|
-
// // Set initial selected page
|
|
562
|
-
// if (this.pages.size > 0 && !this._selectedPageId) {
|
|
563
|
-
// this._selectedPageId = Array.from(this.pages.keys())[0];
|
|
564
|
-
// this.log("🎯 Initial selected page set", { selectedPageId: this._selectedPageId });
|
|
565
|
-
// }
|
|
566
|
-
// this.log("✅ Initialization complete", {
|
|
567
|
-
// totalPages: this.pages.size,
|
|
568
|
-
// selectedPageId: this._selectedPageId,
|
|
569
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
570
|
-
// });
|
|
571
|
-
// await this.syncState();
|
|
572
|
-
// }
|
|
573
|
-
// private async getPageId(page: Page): Promise<string | null> {
|
|
574
|
-
// try {
|
|
575
|
-
// const pageUrl = page.url();
|
|
576
|
-
// this.log("🔍 Getting page ID", { pageUrl });
|
|
577
|
-
// // Fetch debug data from /json endpoint (more reliable for matching)
|
|
578
|
-
// const debugData = await this.getDebugURLs();
|
|
579
|
-
// this.log("📊 CDP debug data received", {
|
|
580
|
-
// totalPages: debugData.length,
|
|
581
|
-
// pages: debugData.map((p) => ({ id: p.id, type: p.type, url: p.url })),
|
|
582
|
-
// });
|
|
583
|
-
// // Exact URL match
|
|
584
|
-
// for (const pageData of debugData) {
|
|
585
|
-
// if (pageData.type === "page" && pageData.url === pageUrl) {
|
|
586
|
-
// this.log("✅ Found exact URL match in /json", {
|
|
587
|
-
// id: pageData.id,
|
|
588
|
-
// url: pageUrl,
|
|
589
|
-
// });
|
|
590
|
-
// return pageData.id;
|
|
591
|
-
// }
|
|
592
|
-
// }
|
|
593
|
-
// this.log("⚠️ No exact match found, trying normalized URLs", { pageUrl });
|
|
594
|
-
// // Normalized URL match
|
|
595
|
-
// const normalizeUrl = (url: string) => {
|
|
596
|
-
// try {
|
|
597
|
-
// const u = new URL(url);
|
|
598
|
-
// const normalized = u.hostname.replace(/^www\./, "") + u.pathname + u.search;
|
|
599
|
-
// return normalized;
|
|
600
|
-
// } catch {
|
|
601
|
-
// return url;
|
|
602
|
-
// }
|
|
603
|
-
// };
|
|
604
|
-
// const normalizedPageUrl = normalizeUrl(pageUrl);
|
|
605
|
-
// for (const pageData of debugData) {
|
|
606
|
-
// if (pageData.type === "page") {
|
|
607
|
-
// const normalizedDebugUrl = normalizeUrl(pageData.url);
|
|
608
|
-
// if (normalizedDebugUrl === normalizedPageUrl) {
|
|
609
|
-
// this.log("✅ Found normalized URL match in /json", {
|
|
610
|
-
// id: pageData.id,
|
|
611
|
-
// pageUrl: normalizedPageUrl,
|
|
612
|
-
// debugUrl: normalizedDebugUrl,
|
|
613
|
-
// });
|
|
614
|
-
// return pageData.id;
|
|
615
|
-
// }
|
|
616
|
-
// }
|
|
617
|
-
// }
|
|
618
|
-
// // If still not found, try CDP session as fallback
|
|
619
|
-
// this.log("⚠️ Not found in /json, trying CDP Target.getTargetInfo", { pageUrl });
|
|
620
|
-
// try {
|
|
621
|
-
// const cdpSession = await page.context().newCDPSession(page);
|
|
622
|
-
// const { targetInfo } = await cdpSession.send("Target.getTargetInfo");
|
|
623
|
-
// await cdpSession.detach();
|
|
624
|
-
// if (targetInfo && targetInfo.targetId) {
|
|
625
|
-
// // Verify this target ID exists in debug data
|
|
626
|
-
// const targetExists = debugData.some((d) => d.id === targetInfo.targetId);
|
|
627
|
-
// if (targetExists) {
|
|
628
|
-
// this.log("✅ Got target ID from CDP session and verified in /json", {
|
|
629
|
-
// targetId: targetInfo.targetId,
|
|
630
|
-
// url: pageUrl,
|
|
631
|
-
// });
|
|
632
|
-
// return targetInfo.targetId;
|
|
633
|
-
// } else {
|
|
634
|
-
// this.log("⚠️ Target ID from CDP session not found in /json (ID mismatch)", {
|
|
635
|
-
// cdpTargetId: targetInfo.targetId,
|
|
636
|
-
// availableIds: debugData.map((d) => d.id),
|
|
637
|
-
// });
|
|
638
|
-
// }
|
|
639
|
-
// }
|
|
640
|
-
// } catch (cdpError) {
|
|
641
|
-
// this.log("⚠️ Failed to get target ID from CDP session", {
|
|
642
|
-
// error: cdpError instanceof Error ? cdpError.message : String(cdpError),
|
|
643
|
-
// });
|
|
644
|
-
// }
|
|
645
|
-
// this.log("❌ No match found for page", {
|
|
646
|
-
// pageUrl,
|
|
647
|
-
// normalizedPageUrl,
|
|
648
|
-
// availablePages: debugData.filter((p) => p.type === "page").map((p) => p.url),
|
|
649
|
-
// });
|
|
650
|
-
// return null;
|
|
651
|
-
// } catch (error) {
|
|
652
|
-
// this.log("❌ Error getting page ID", {
|
|
653
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
654
|
-
// stack: error instanceof Error ? error.stack : undefined,
|
|
655
|
-
// });
|
|
656
|
-
// return null;
|
|
657
|
-
// }
|
|
658
|
-
// }
|
|
659
|
-
// private async getDebugURLs(): Promise<DebugPageInfo[]> {
|
|
660
|
-
// const url = `${this.CDP_CONNECT_URL}/json`;
|
|
661
|
-
// this.log("📡 Fetching debug URLs", { url });
|
|
662
|
-
// try {
|
|
663
|
-
// const response = await fetch(url);
|
|
664
|
-
// if (!response.ok) {
|
|
665
|
-
// this.log("❌ Failed to fetch debug URLs", {
|
|
666
|
-
// status: response.status,
|
|
667
|
-
// statusText: response.statusText,
|
|
668
|
-
// });
|
|
669
|
-
// throw new Error("Error while fetching debug URL");
|
|
670
|
-
// }
|
|
671
|
-
// const data = await response.json();
|
|
672
|
-
// this.log("✅ Debug URLs fetched successfully", {
|
|
673
|
-
// count: data.length,
|
|
674
|
-
// pages: data.map((p: any) => ({ id: p.id, type: p.type, url: p.url })),
|
|
675
|
-
// });
|
|
676
|
-
// return data;
|
|
677
|
-
// } catch (error) {
|
|
678
|
-
// this.log("❌ Exception while fetching debug URLs", {
|
|
679
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
680
|
-
// });
|
|
681
|
-
// throw error;
|
|
682
|
-
// }
|
|
683
|
-
// }
|
|
684
|
-
// private async syncState() {
|
|
685
|
-
// try {
|
|
686
|
-
// this.log("🔄 Starting state sync");
|
|
687
|
-
// const state = await this.getState();
|
|
688
|
-
// this.log("✅ State sync complete", {
|
|
689
|
-
// pagesCount: state.pages.length,
|
|
690
|
-
// selectedPageId: state.selectedPageId,
|
|
691
|
-
// pages: state.pages.map((p) => ({ id: p.id, title: p.title, url: p.url })),
|
|
692
|
-
// });
|
|
693
|
-
// this.emit("BrowserService.stateSync", state);
|
|
694
|
-
// } catch (error) {
|
|
695
|
-
// this.log("❌ Error syncing state", {
|
|
696
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
697
|
-
// stack: error instanceof Error ? error.stack : undefined,
|
|
698
|
-
// });
|
|
699
|
-
// }
|
|
700
|
-
// }
|
|
701
|
-
// async getState(): Promise<BrowserState> {
|
|
702
|
-
// this.log("📊 Getting current state", {
|
|
703
|
-
// internalPagesCount: this.pages.size,
|
|
704
|
-
// internalPageIds: Array.from(this.pages.keys()),
|
|
705
|
-
// selectedPageId: this._selectedPageId,
|
|
706
|
-
// });
|
|
707
|
-
// const debugData = await this.getDebugURLs();
|
|
708
|
-
// this.log("📊 Debug data for state", {
|
|
709
|
-
// debugPagesCount: debugData.length,
|
|
710
|
-
// debugPages: debugData.map((p) => ({ id: p.id, type: p.type, url: p.url })),
|
|
711
|
-
// });
|
|
712
|
-
// const pagesData: PageData[] = [];
|
|
713
|
-
// const updatedPages = new Map<string, Page>();
|
|
714
|
-
// const matchedDebugIds = new Set<string>(); // Track which CDP IDs we've already matched
|
|
715
|
-
// let updatedSelectedPageId = this._selectedPageId;
|
|
716
|
-
// for (const [oldId, page] of this.pages.entries()) {
|
|
717
|
-
// this.log("🔍 Processing page from internal map", {
|
|
718
|
-
// oldId,
|
|
719
|
-
// url: page.url(),
|
|
720
|
-
// });
|
|
721
|
-
// // Try to find by old ID first (most common case - ID hasn't changed)
|
|
722
|
-
// let debugInfo = debugData.find((d) => d.id === oldId && !matchedDebugIds.has(d.id));
|
|
723
|
-
// // Fallback: Try to find by URL (page ID may have changed)
|
|
724
|
-
// if (!debugInfo) {
|
|
725
|
-
// this.log("⚠️ Page ID not found in CDP, attempting to match by URL", {
|
|
726
|
-
// oldId,
|
|
727
|
-
// pageUrl: page.url(),
|
|
728
|
-
// });
|
|
729
|
-
// debugInfo = debugData.find((d) => d.type === "page" && d.url === page.url() && !matchedDebugIds.has(d.id));
|
|
730
|
-
// if (debugInfo) {
|
|
731
|
-
// this.log("✅ Found page by URL match, updating ID", {
|
|
732
|
-
// oldId,
|
|
733
|
-
// newId: debugInfo.id,
|
|
734
|
-
// url: page.url(),
|
|
735
|
-
// });
|
|
736
|
-
// // Update selected page ID if this was the selected page and ID changed
|
|
737
|
-
// if (oldId === this._selectedPageId) {
|
|
738
|
-
// updatedSelectedPageId = debugInfo.id;
|
|
739
|
-
// this.log("🔄 Updated selected page ID", {
|
|
740
|
-
// oldId,
|
|
741
|
-
// newId: debugInfo.id,
|
|
742
|
-
// });
|
|
743
|
-
// }
|
|
744
|
-
// }
|
|
745
|
-
// } else {
|
|
746
|
-
// this.log("✅ Found matching debug info by ID", {
|
|
747
|
-
// id: debugInfo.id,
|
|
748
|
-
// debugUrl: debugInfo.url,
|
|
749
|
-
// pageUrl: page.url(),
|
|
750
|
-
// });
|
|
751
|
-
// }
|
|
752
|
-
// if (debugInfo) {
|
|
753
|
-
// // Mark this CDP ID as matched to avoid duplicate matches
|
|
754
|
-
// matchedDebugIds.add(debugInfo.id);
|
|
755
|
-
// try {
|
|
756
|
-
// const pageData = {
|
|
757
|
-
// id: debugInfo.id,
|
|
758
|
-
// title: await page.title(),
|
|
759
|
-
// url: page.url(),
|
|
760
|
-
// wsDebuggerUrl: debugInfo.webSocketDebuggerUrl || "",
|
|
761
|
-
// };
|
|
762
|
-
// pagesData.push(pageData);
|
|
763
|
-
// updatedPages.set(debugInfo.id, page);
|
|
764
|
-
// this.log("✅ Page added to state", pageData);
|
|
765
|
-
// } catch (error) {
|
|
766
|
-
// this.log("❌ Error getting page data", {
|
|
767
|
-
// id: oldId,
|
|
768
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
769
|
-
// });
|
|
770
|
-
// }
|
|
771
|
-
// } else {
|
|
772
|
-
// this.log("⚠️ No matching debug info found by ID or URL", {
|
|
773
|
-
// oldId,
|
|
774
|
-
// pageUrl: page.url(),
|
|
775
|
-
// availableDebugIds: debugData.map((d) => d.id),
|
|
776
|
-
// availableDebugUrls: debugData.map((d) => d.url),
|
|
777
|
-
// alreadyMatched: Array.from(matchedDebugIds),
|
|
778
|
-
// });
|
|
779
|
-
// }
|
|
780
|
-
// }
|
|
781
|
-
// // Update the internal pages map with current CDP IDs
|
|
782
|
-
// if (updatedPages.size !== this.pages.size || updatedSelectedPageId !== this._selectedPageId) {
|
|
783
|
-
// this.log("🔄 Updating internal state with new CDP IDs", {
|
|
784
|
-
// oldPagesCount: this.pages.size,
|
|
785
|
-
// newPagesCount: updatedPages.size,
|
|
786
|
-
// oldSelectedId: this._selectedPageId,
|
|
787
|
-
// newSelectedId: updatedSelectedPageId,
|
|
788
|
-
// });
|
|
789
|
-
// this.pages = updatedPages;
|
|
790
|
-
// this._selectedPageId = updatedSelectedPageId;
|
|
791
|
-
// }
|
|
792
|
-
// const state = {
|
|
793
|
-
// pages: pagesData,
|
|
794
|
-
// selectedPageId: this._selectedPageId,
|
|
795
|
-
// };
|
|
796
|
-
// this.log("📦 Final state", state);
|
|
797
|
-
// return state;
|
|
798
|
-
// }
|
|
799
|
-
// async createTab(url: string = "about:blank"): Promise<void> {
|
|
800
|
-
// try {
|
|
801
|
-
// this.log("🆕 Creating new tab", { url });
|
|
802
|
-
// const page = await this.context.newPage();
|
|
803
|
-
// this.log("✅ New page created by Playwright", { url: page.url() });
|
|
804
|
-
// if (typeof url === "string" && url !== "about:blank") {
|
|
805
|
-
// this.log("🌐 Navigating to URL", { url });
|
|
806
|
-
// await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
807
|
-
// this.log("✅ Navigation complete", { finalUrl: page.url() });
|
|
808
|
-
// }
|
|
809
|
-
// // Wait longer for CDP to register and stabilize the page
|
|
810
|
-
// this.log("⏳ Waiting for CDP registration and stabilization...");
|
|
811
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000)); // Increased to 1 second
|
|
812
|
-
// // Try multiple times to get a stable page ID
|
|
813
|
-
// let id: string | null = null;
|
|
814
|
-
// let attempts = 0;
|
|
815
|
-
// const maxAttempts = 5;
|
|
816
|
-
// while (!id && attempts < maxAttempts) {
|
|
817
|
-
// attempts++;
|
|
818
|
-
// this.log(`🔍 Attempt ${attempts}/${maxAttempts} to get page ID`);
|
|
819
|
-
// id = await this.getPageId(page);
|
|
820
|
-
// if (!id) {
|
|
821
|
-
// this.log(`⏳ Page ID not found, waiting 500ms before retry...`);
|
|
822
|
-
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
823
|
-
// }
|
|
824
|
-
// }
|
|
825
|
-
// this.log("🔍 Retrieved page ID after retries", {
|
|
826
|
-
// id,
|
|
827
|
-
// url: page.url(),
|
|
828
|
-
// attempts,
|
|
829
|
-
// });
|
|
830
|
-
// if (id) {
|
|
831
|
-
// this.pages.set(id, page);
|
|
832
|
-
// this._selectedPageId = id;
|
|
833
|
-
// this.log("✅ Tab created successfully", {
|
|
834
|
-
// id,
|
|
835
|
-
// url: page.url(),
|
|
836
|
-
// totalPages: this.pages.size,
|
|
837
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
838
|
-
// selectedPageId: this._selectedPageId,
|
|
839
|
-
// });
|
|
840
|
-
// } else {
|
|
841
|
-
// this.log("❌ Failed to get page ID for new tab after all retries", {
|
|
842
|
-
// url: page.url(),
|
|
843
|
-
// attempts,
|
|
844
|
-
// });
|
|
845
|
-
// // Try to get debug data to see what's available
|
|
846
|
-
// const debugData = await this.getDebugURLs();
|
|
847
|
-
// this.log("🔍 Current CDP state after failed ID retrieval", {
|
|
848
|
-
// debugPages: debugData.map((p) => ({ id: p.id, url: p.url, type: p.type })),
|
|
849
|
-
// });
|
|
850
|
-
// }
|
|
851
|
-
// await this.syncState();
|
|
852
|
-
// } catch (error) {
|
|
853
|
-
// this.log("❌ Error creating tab", {
|
|
854
|
-
// url,
|
|
855
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
856
|
-
// stack: error instanceof Error ? error.stack : undefined,
|
|
857
|
-
// });
|
|
858
|
-
// throw error;
|
|
859
|
-
// }
|
|
860
|
-
// }
|
|
861
|
-
// async closeTab(pageId: string): Promise<void> {
|
|
862
|
-
// try {
|
|
863
|
-
// this.log("🗑️ Closing tab", { pageId });
|
|
864
|
-
// const page = this.pages.get(pageId);
|
|
865
|
-
// if (page) {
|
|
866
|
-
// this.log("✅ Found page to close", { pageId, url: page.url() });
|
|
867
|
-
// await page.close();
|
|
868
|
-
// this.log("✅ Page closed successfully", { pageId });
|
|
869
|
-
// } else {
|
|
870
|
-
// this.log("⚠️ Page not found", {
|
|
871
|
-
// pageId,
|
|
872
|
-
// availablePageIds: Array.from(this.pages.keys()),
|
|
873
|
-
// });
|
|
874
|
-
// }
|
|
875
|
-
// } catch (error) {
|
|
876
|
-
// this.log("❌ Error closing tab", {
|
|
877
|
-
// pageId,
|
|
878
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
879
|
-
// });
|
|
880
|
-
// throw error;
|
|
881
|
-
// }
|
|
882
|
-
// }
|
|
883
|
-
// async selectTab(pageId: string): Promise<void> {
|
|
884
|
-
// try {
|
|
885
|
-
// this.log("🎯 Selecting tab", { pageId });
|
|
886
|
-
// const page = this.pages.get(pageId);
|
|
887
|
-
// if (page) {
|
|
888
|
-
// this._selectedPageId = pageId;
|
|
889
|
-
// await page.bringToFront();
|
|
890
|
-
// this.log("✅ Tab selected successfully", {
|
|
891
|
-
// pageId,
|
|
892
|
-
// url: page.url(),
|
|
893
|
-
// selectedPageId: this._selectedPageId,
|
|
894
|
-
// });
|
|
895
|
-
// await this.syncState();
|
|
896
|
-
// } else {
|
|
897
|
-
// this.log("⚠️ Page not found for selection", {
|
|
898
|
-
// pageId,
|
|
899
|
-
// availablePageIds: Array.from(this.pages.keys()),
|
|
900
|
-
// });
|
|
901
|
-
// }
|
|
902
|
-
// } catch (error) {
|
|
903
|
-
// this.log("❌ Error selecting tab", {
|
|
904
|
-
// pageId,
|
|
905
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
906
|
-
// });
|
|
907
|
-
// throw error;
|
|
908
|
-
// }
|
|
909
|
-
// }
|
|
910
|
-
// getSelectedPage(): Page | null {
|
|
911
|
-
// const page = this._selectedPageId ? this.pages.get(this._selectedPageId) || null : null;
|
|
912
|
-
// this.log("🔍 Getting selected page", {
|
|
913
|
-
// selectedPageId: this._selectedPageId,
|
|
914
|
-
// found: !!page,
|
|
915
|
-
// url: page?.url(),
|
|
916
|
-
// });
|
|
917
|
-
// return page;
|
|
918
|
-
// }
|
|
919
|
-
// }
|