const normalizeUrl = (value) => { const raw = String(value || '').trim(); if (!raw) return null; try { return new URL(raw); } catch { return null; } }; const isSameTarget = (currentUrl, targetUrl) => { if (!currentUrl || !targetUrl) return false; if (currentUrl.startsWith(targetUrl)) return true; const current = normalizeUrl(currentUrl); const target = normalizeUrl(targetUrl); if (!current || !target) return false; if (current.origin !== target.origin) return false; if (!current.pathname.startsWith(target.pathname)) return false; return true; }; const isSameOrigin = (currentUrl, targetUrl) => { const current = normalizeUrl(currentUrl); const target = normalizeUrl(targetUrl); if (!current || !target) return false; return current.origin === target.origin; }; const isBlankLikePage = (url) => { const u = String(url || '').trim().toLowerCase(); if (!u) return true; if (u === 'about:blank' || u.startsWith('about:blank#') || u.startsWith('about:blank?')) return true; if (u === 'chrome://newtab/' || u.startsWith('chrome://newtab')) return true; if (u.startsWith('chrome://new-tab-page')) return true; return false; }; const preparePage = async ( chromium, { tabIndex = Number(process.env.TAB_INDEX), endpoint = process.env.CDP_ENDPOINT || 'http://127.0.0.1:9222', targetUrl, } = {}, ) => { const browser = await chromium.connectOverCDP(endpoint); const context = browser.contexts()[0]; if (!context) { throw new Error('No browser context available'); } await context.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); }); let pages = await context.pages(); let page = null; if (targetUrl) { // 1. Try exact/path match for (let i = 0; i < pages.length; i++) { const p = pages[i]; if (isSameTarget(p.url(), targetUrl)) { page = p; break; } } // 2. Try origin match if (!page) { for (let i = 0; i < pages.length; i++) { const p = pages[i]; if (isSameOrigin(p.url(), targetUrl)) { page = p; break; } } } } if (!page) { const isTabIndexSet = Number.isFinite(tabIndex) && tabIndex >= 0; // If targetUrl provided and NO specific tabIndex requested, avoid hijacking tab 0 if (targetUrl && !isTabIndexSet) { // Try blank page for (let i = 0; i < pages.length; i++) { if (isBlankLikePage(pages[i].url())) { page = pages[i]; break; } } // Else new page if (!page) { page = await context.newPage(); } } else { // Legacy/Default behavior: use tabIndex (default 0) const idx = isTabIndexSet ? Math.floor(tabIndex) : 0; while (pages.length <= idx) { await context.newPage(); pages = await context.pages(); } page = pages[idx]; } } await page.bringToFront(); if (targetUrl) { const currentUrl = page.url(); if (!currentUrl || !isSameTarget(currentUrl, targetUrl)) { await page.goto(targetUrl, { waitUntil: 'domcontentloaded' }); } } return { browser, context, page }; }; const safeDisconnectBrowser = async (browser) => { if (!browser) return; try { if (typeof browser.disconnect === 'function') { await browser.disconnect(); } else { await browser.close(); } } catch {} }; module.exports = { preparePage, safeDisconnectBrowser, };