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