@dev-blinq/cucumber_client 1.0.1588-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 +583 -303
- 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,191 +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 });
|
|
248
246
|
}
|
|
247
|
+
// If this is the first page, select it
|
|
248
|
+
if (this.pages.size === 1) {
|
|
249
|
+
this._selectedPageId = stableTabId;
|
|
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
|
-
// Fetch debug data from /json endpoint (more reliable for matching)
|
|
315
|
+
this.log("🔍 Finding CDP target ID for page", { pageUrl });
|
|
307
316
|
const debugData = await this.getDebugURLs();
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
id: pageData.id,
|
|
317
|
-
url: pageUrl,
|
|
318
|
-
});
|
|
319
|
-
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;
|
|
320
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;
|
|
321
333
|
}
|
|
322
|
-
|
|
323
|
-
// Normalized URL match
|
|
334
|
+
// 2. Normalized URL match
|
|
324
335
|
const normalizeUrl = (url) => {
|
|
325
336
|
try {
|
|
326
337
|
const u = new URL(url);
|
|
327
|
-
|
|
328
|
-
return normalized;
|
|
338
|
+
return u.hostname.replace(/^www\./, "") + u.pathname + u.search;
|
|
329
339
|
}
|
|
330
340
|
catch {
|
|
331
341
|
return url;
|
|
332
342
|
}
|
|
333
343
|
};
|
|
334
344
|
const normalizedPageUrl = normalizeUrl(pageUrl);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
this.log("✅ Found normalized URL match in /json", {
|
|
340
|
-
id: pageData.id,
|
|
341
|
-
pageUrl: normalizedPageUrl,
|
|
342
|
-
debugUrl: normalizedDebugUrl,
|
|
343
|
-
});
|
|
344
|
-
return pageData.id;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
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;
|
|
347
349
|
}
|
|
348
|
-
//
|
|
349
|
-
this.log("⚠️ Not found in /json, trying CDP Target.getTargetInfo", { pageUrl });
|
|
350
|
+
// 3. Fallback to CDP Session
|
|
350
351
|
try {
|
|
351
352
|
const cdpSession = await page.context().newCDPSession(page);
|
|
352
353
|
const { targetInfo } = await cdpSession.send("Target.getTargetInfo");
|
|
353
354
|
await cdpSession.detach();
|
|
354
355
|
if (targetInfo && targetInfo.targetId) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (targetExists) {
|
|
358
|
-
this.log("✅ Got target ID from CDP session and verified in /json", {
|
|
359
|
-
targetId: targetInfo.targetId,
|
|
360
|
-
url: pageUrl,
|
|
361
|
-
});
|
|
362
|
-
return targetInfo.targetId;
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
this.log("⚠️ Target ID from CDP session not found in /json (ID mismatch)", {
|
|
366
|
-
cdpTargetId: targetInfo.targetId,
|
|
367
|
-
availableIds: debugData.map((d) => d.id),
|
|
368
|
-
});
|
|
369
|
-
}
|
|
356
|
+
this.log("✅ Found CDP ID by session", { id: targetInfo.targetId });
|
|
357
|
+
return targetInfo.targetId;
|
|
370
358
|
}
|
|
371
359
|
}
|
|
372
360
|
catch (cdpError) {
|
|
373
|
-
this.log("⚠️ Failed to get target ID from CDP session", {
|
|
374
|
-
error: cdpError instanceof Error ? cdpError.message : String(cdpError),
|
|
375
|
-
});
|
|
361
|
+
this.log("⚠️ Failed to get target ID from CDP session", { error: cdpError });
|
|
376
362
|
}
|
|
377
|
-
this.log("❌ No match found for page", {
|
|
378
|
-
pageUrl,
|
|
379
|
-
normalizedPageUrl,
|
|
380
|
-
availablePages: debugData.filter((p) => p.type === "page").map((p) => p.url),
|
|
381
|
-
});
|
|
363
|
+
this.log("❌ No match found for page", { pageUrl });
|
|
382
364
|
return null;
|
|
383
365
|
}
|
|
384
366
|
catch (error) {
|
|
385
|
-
this.log("❌ Error
|
|
386
|
-
error: error instanceof Error ? error.message : String(error),
|
|
387
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
388
|
-
});
|
|
367
|
+
this.log("❌ Error finding CDP target ID", { error });
|
|
389
368
|
return null;
|
|
390
369
|
}
|
|
391
370
|
}
|
|
392
371
|
async getDebugURLs() {
|
|
393
372
|
const url = `${this.CDP_CONNECT_URL}/json`;
|
|
394
|
-
this.log("📡 Fetching debug URLs", { url });
|
|
395
373
|
try {
|
|
396
374
|
const response = await fetch(url);
|
|
397
375
|
if (!response.ok) {
|
|
398
|
-
this.log("❌ Failed to fetch debug URLs", {
|
|
399
|
-
status: response.status,
|
|
400
|
-
statusText: response.statusText,
|
|
401
|
-
});
|
|
402
376
|
throw new Error("Error while fetching debug URL");
|
|
403
377
|
}
|
|
404
|
-
|
|
405
|
-
this.log("✅ Debug URLs fetched successfully", {
|
|
406
|
-
count: data.length,
|
|
407
|
-
pages: data.map((p) => ({ id: p.id, type: p.type, url: p.url })),
|
|
408
|
-
});
|
|
409
|
-
return data;
|
|
378
|
+
return await response.json();
|
|
410
379
|
}
|
|
411
380
|
catch (error) {
|
|
412
|
-
this.log("❌ Exception while fetching debug URLs", {
|
|
413
|
-
|
|
414
|
-
});
|
|
415
|
-
throw error;
|
|
381
|
+
this.log("❌ Exception while fetching debug URLs", { error });
|
|
382
|
+
return []; // Return empty array on failure
|
|
416
383
|
}
|
|
417
384
|
}
|
|
418
385
|
async syncState() {
|
|
@@ -422,110 +389,48 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
422
389
|
this.log("✅ State sync complete", {
|
|
423
390
|
pagesCount: state.pages.length,
|
|
424
391
|
selectedPageId: state.selectedPageId,
|
|
425
|
-
pages: state.pages.map((p) => ({ id: p.id, title: p.title, url: p.url })),
|
|
426
392
|
});
|
|
427
393
|
this.emit("BrowserService.stateSync", state);
|
|
428
394
|
}
|
|
429
395
|
catch (error) {
|
|
430
|
-
this.log("❌ Error syncing state", {
|
|
431
|
-
error: error instanceof Error ? error.message : String(error),
|
|
432
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
433
|
-
});
|
|
396
|
+
this.log("❌ Error syncing state", { error });
|
|
434
397
|
}
|
|
435
398
|
}
|
|
436
399
|
async getState() {
|
|
437
|
-
this.log("📊 Getting current state"
|
|
438
|
-
internalPagesCount: this.pages.size,
|
|
439
|
-
internalPageIds: Array.from(this.pages.keys()),
|
|
440
|
-
selectedPageId: this._selectedPageId,
|
|
441
|
-
});
|
|
400
|
+
this.log("📊 Getting current state");
|
|
442
401
|
const debugData = await this.getDebugURLs();
|
|
443
|
-
this.log("📊 Debug data for state", {
|
|
444
|
-
debugPagesCount: debugData.length,
|
|
445
|
-
debugPages: debugData.map((p) => ({ id: p.id, type: p.type, url: p.url })),
|
|
446
|
-
});
|
|
447
402
|
const pagesData = [];
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
url: page.url(),
|
|
455
|
-
});
|
|
456
|
-
// Try to find by old ID first (most common case - ID hasn't changed)
|
|
457
|
-
let debugInfo = debugData.find((d) => d.id === oldId && !matchedDebugIds.has(d.id));
|
|
458
|
-
// Fallback: Try to find by URL (page ID may have changed)
|
|
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
|
|
459
409
|
if (!debugInfo) {
|
|
460
|
-
this.log("⚠️
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
this.log("✅ Found page by URL match, updating ID", {
|
|
467
|
-
oldId,
|
|
468
|
-
newId: debugInfo.id,
|
|
469
|
-
url: page.url(),
|
|
470
|
-
});
|
|
471
|
-
// Update selected page ID if this was the selected page and ID changed
|
|
472
|
-
if (oldId === this._selectedPageId) {
|
|
473
|
-
updatedSelectedPageId = debugInfo.id;
|
|
474
|
-
this.log("🔄 Updated selected page ID", {
|
|
475
|
-
oldId,
|
|
476
|
-
newId: debugInfo.id,
|
|
477
|
-
});
|
|
478
|
-
}
|
|
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 });
|
|
479
416
|
}
|
|
480
417
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
id:
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
486
424
|
});
|
|
487
425
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
matchedDebugIds.add(debugInfo.id);
|
|
491
|
-
try {
|
|
492
|
-
const pageData = {
|
|
493
|
-
id: debugInfo.id,
|
|
494
|
-
title: await page.title(),
|
|
495
|
-
url: page.url(),
|
|
496
|
-
wsDebuggerUrl: debugInfo.webSocketDebuggerUrl || "",
|
|
497
|
-
};
|
|
498
|
-
pagesData.push(pageData);
|
|
499
|
-
updatedPages.set(debugInfo.id, page);
|
|
500
|
-
this.log("✅ Page added to state", pageData);
|
|
501
|
-
}
|
|
502
|
-
catch (error) {
|
|
503
|
-
this.log("❌ Error getting page data", {
|
|
504
|
-
id: oldId,
|
|
505
|
-
error: error instanceof Error ? error.message : String(error),
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
this.log("⚠️ No matching debug info found by ID or URL", {
|
|
511
|
-
oldId,
|
|
512
|
-
pageUrl: page.url(),
|
|
513
|
-
availableDebugIds: debugData.map((d) => d.id),
|
|
514
|
-
availableDebugUrls: debugData.map((d) => d.url),
|
|
515
|
-
alreadyMatched: Array.from(matchedDebugIds),
|
|
516
|
-
});
|
|
426
|
+
catch (error) {
|
|
427
|
+
this.log("❌ Error getting page data (page might be closed)", { stableTabId });
|
|
517
428
|
}
|
|
518
429
|
}
|
|
519
|
-
//
|
|
520
|
-
if (
|
|
521
|
-
this.
|
|
522
|
-
|
|
523
|
-
newPagesCount: updatedPages.size,
|
|
524
|
-
oldSelectedId: this._selectedPageId,
|
|
525
|
-
newSelectedId: updatedSelectedPageId,
|
|
526
|
-
});
|
|
527
|
-
this.pages = updatedPages;
|
|
528
|
-
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 });
|
|
529
434
|
}
|
|
530
435
|
const state = {
|
|
531
436
|
pages: pagesData,
|
|
@@ -537,127 +442,502 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
537
442
|
async createTab(url = "about:blank") {
|
|
538
443
|
try {
|
|
539
444
|
this.log("🆕 Creating new tab", { url });
|
|
540
|
-
const page = await this.context.newPage();
|
|
541
|
-
|
|
542
|
-
if (typeof url === "string" && url !== "about:blank") {
|
|
543
|
-
this.log("🌐 Navigating to URL", { url });
|
|
445
|
+
const page = await this.context.newPage(); // This will trigger the 'page' event
|
|
446
|
+
if (url !== "about:blank") {
|
|
544
447
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
545
|
-
this.log("✅ Navigation complete", { finalUrl: page.url() });
|
|
546
448
|
}
|
|
547
|
-
//
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
while (!id && attempts < maxAttempts) {
|
|
555
|
-
attempts++;
|
|
556
|
-
this.log(`🔍 Attempt ${attempts}/${maxAttempts} to get page ID`);
|
|
557
|
-
id = await this.getPageId(page);
|
|
558
|
-
if (!id) {
|
|
559
|
-
this.log(`⏳ Page ID not found, waiting 500ms before retry...`);
|
|
560
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
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;
|
|
561
456
|
}
|
|
562
457
|
}
|
|
563
|
-
this.log("🔍 Retrieved page ID after retries", {
|
|
564
|
-
id,
|
|
565
|
-
url: page.url(),
|
|
566
|
-
attempts,
|
|
567
|
-
});
|
|
568
|
-
if (id) {
|
|
569
|
-
this.pages.set(id, page);
|
|
570
|
-
this._selectedPageId = id;
|
|
571
|
-
this.log("✅ Tab created successfully", {
|
|
572
|
-
id,
|
|
573
|
-
url: page.url(),
|
|
574
|
-
totalPages: this.pages.size,
|
|
575
|
-
allPageIds: Array.from(this.pages.keys()),
|
|
576
|
-
selectedPageId: this._selectedPageId,
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
this.log("❌ Failed to get page ID for new tab after all retries", {
|
|
581
|
-
url: page.url(),
|
|
582
|
-
attempts,
|
|
583
|
-
});
|
|
584
|
-
// Try to get debug data to see what's available
|
|
585
|
-
const debugData = await this.getDebugURLs();
|
|
586
|
-
this.log("🔍 Current CDP state after failed ID retrieval", {
|
|
587
|
-
debugPages: debugData.map((p) => ({ id: p.id, url: p.url, type: p.type })),
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
458
|
await this.syncState();
|
|
591
459
|
}
|
|
592
460
|
catch (error) {
|
|
593
|
-
this.log("❌ Error creating tab", {
|
|
594
|
-
url,
|
|
595
|
-
error: error instanceof Error ? error.message : String(error),
|
|
596
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
597
|
-
});
|
|
598
|
-
throw error;
|
|
461
|
+
this.log("❌ Error creating tab", { error });
|
|
599
462
|
}
|
|
600
463
|
}
|
|
601
|
-
async closeTab(
|
|
464
|
+
async closeTab(stableTabId) {
|
|
602
465
|
try {
|
|
603
|
-
this.log("🗑️ Closing tab", {
|
|
604
|
-
const
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
await page.close();
|
|
608
|
-
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
|
|
609
470
|
}
|
|
610
471
|
else {
|
|
611
|
-
this.log("⚠️ Page not found", {
|
|
612
|
-
pageId,
|
|
613
|
-
availablePageIds: Array.from(this.pages.keys()),
|
|
614
|
-
});
|
|
472
|
+
this.log("⚠️ Page not found for closing", { stableTabId });
|
|
615
473
|
}
|
|
474
|
+
// 'close' event handler will manage state update
|
|
616
475
|
}
|
|
617
476
|
catch (error) {
|
|
618
|
-
this.log("❌ Error closing tab", {
|
|
619
|
-
pageId,
|
|
620
|
-
error: error instanceof Error ? error.message : String(error),
|
|
621
|
-
});
|
|
622
|
-
throw error;
|
|
477
|
+
this.log("❌ Error closing tab", { error });
|
|
623
478
|
}
|
|
624
479
|
}
|
|
625
|
-
async selectTab(
|
|
480
|
+
async selectTab(stableTabId) {
|
|
626
481
|
try {
|
|
627
|
-
this.log("🎯 Selecting tab", {
|
|
628
|
-
const
|
|
629
|
-
if (
|
|
630
|
-
this._selectedPageId =
|
|
631
|
-
await page.bringToFront();
|
|
632
|
-
this.log("✅ Tab selected successfully", {
|
|
633
|
-
pageId,
|
|
634
|
-
url: page.url(),
|
|
635
|
-
selectedPageId: this._selectedPageId,
|
|
636
|
-
});
|
|
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 });
|
|
637
488
|
await this.syncState();
|
|
638
489
|
}
|
|
639
490
|
else {
|
|
640
|
-
this.log("⚠️ Page not found for selection", {
|
|
641
|
-
pageId,
|
|
642
|
-
availablePageIds: Array.from(this.pages.keys()),
|
|
643
|
-
});
|
|
491
|
+
this.log("⚠️ Page not found for selection", { stableTabId });
|
|
644
492
|
}
|
|
645
493
|
}
|
|
646
494
|
catch (error) {
|
|
647
|
-
this.log("❌ Error selecting tab", {
|
|
648
|
-
pageId,
|
|
649
|
-
error: error instanceof Error ? error.message : String(error),
|
|
650
|
-
});
|
|
651
|
-
throw error;
|
|
495
|
+
this.log("❌ Error selecting tab", { error });
|
|
652
496
|
}
|
|
653
497
|
}
|
|
498
|
+
// ... (getSelectedPage remains the same, but operates on new state)
|
|
654
499
|
getSelectedPage() {
|
|
655
|
-
const
|
|
500
|
+
const pageInfo = this._selectedPageId ? this.pages.get(this._selectedPageId) : null;
|
|
656
501
|
this.log("🔍 Getting selected page", {
|
|
657
502
|
selectedPageId: this._selectedPageId,
|
|
658
|
-
found: !!
|
|
659
|
-
url: page
|
|
503
|
+
found: !!pageInfo,
|
|
504
|
+
url: pageInfo?.page.url(),
|
|
660
505
|
});
|
|
661
|
-
return page;
|
|
506
|
+
return pageInfo?.page || null;
|
|
662
507
|
}
|
|
663
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
|
+
// }
|