From 6c2ce63c4ed8e49483d493a44112daa7c2685ac4 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 20:03:47 +0800 Subject: [PATCH 01/18] feat: adapt to new AI Studio login flow - Update target URL to new AI Studio app endpoint - Add "Continue to the app" button handler - Remove manual script injection (app has built-in WebSocket client) - Simplify initialization flow for faster startup Co-Authored-By: Claude Sonnet 4.6 --- src/core/BrowserManager.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index e436f5c..d5d2762 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -27,7 +27,6 @@ class BrowserManager { // currentAuthIndex is the single source of truth for current account, accessed via getter/setter // -1 means no account is currently active (invalid/error state) this._currentAuthIndex = -1; - this.scriptFileName = "build.js"; // Flag to distinguish intentional close from unexpected disconnect // Used by ConnectionRegistry callback to skip unnecessary reconnect attempts @@ -509,8 +508,7 @@ class BrowserManager { */ async _navigateAndWakeUpPage(logPrefix = "[Browser]") { this.logger.info(`${logPrefix} Navigating to target page...`); - const targetUrl = - "https://aistudio.google.com/u/0/apps/bundled/blank?showPreview=true&showCode=true&showAssistant=true"; + const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; await this.page.goto(targetUrl, { timeout: 180000, waitUntil: "domcontentloaded", @@ -596,6 +594,11 @@ class BrowserManager { this.logger.info(`${logPrefix} 🔍 Starting intelligent popup detection (max 6s)...`); const popupConfigs = [ + { + logFound: `${logPrefix} ✅ Found "Continue to the app" button, clicking...`, + name: "Continue to the app", + selector: 'button:text("Continue to the app")', + }, { logFound: `${logPrefix} ✅ Found Cookie consent banner, clicking "Agree"...`, name: "Cookie consent", @@ -1096,8 +1099,6 @@ class BrowserManager { throw new Error(`Failed to get or parse auth source for index ${authIndex}.`); } - const buildScriptContent = this._loadAndConfigureBuildScript(); - try { // Viewport Randomization const randomWidth = 1920 + Math.floor(Math.random() * 50); @@ -1153,9 +1154,12 @@ class BrowserManager { await this._checkPageStatusAndErrors("[Browser]"); // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + // After clicking "Continue to the app", WebSocket will auto-connect await this._handlePopups("[Browser]"); - await this._injectScriptToEditor(buildScriptContent, "[Browser]"); + this.logger.info("[Browser] ✅ Waiting for WebSocket auto-connection after clicking button..."); + // Wait a bit for the WebSocket connection to establish + await this.page.waitForTimeout(3000); // Start background wakeup service - only started here during initial browser launch this._startBackgroundWakeup(); @@ -1179,7 +1183,7 @@ class BrowserManager { } /** - * Lightweight Reconnect: Refreshes the page and re-injects the script + * Lightweight Reconnect: Refreshes the page and clicks "Continue to the app" button * without restarting the entire browser instance. * * This method is called when WebSocket connection is lost but the browser @@ -1218,9 +1222,6 @@ class BrowserManager { } try { - // Load and configure the build.js script using the shared helper - const buildScriptContent = this._loadAndConfigureBuildScript(); - // Navigate to target page and wake it up await this._navigateAndWakeUpPage("[Reconnect]"); @@ -1228,10 +1229,12 @@ class BrowserManager { await this._checkPageStatusAndErrors("[Reconnect]"); // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + // After clicking "Continue to the app", WebSocket will auto-connect await this._handlePopups("[Reconnect]"); - // Use shared script injection helper with [Reconnect] log prefix - await this._injectScriptToEditor(buildScriptContent, "[Reconnect]"); + this.logger.info("[Reconnect] ✅ Waiting for WebSocket auto-connection after clicking button..."); + // Wait a bit for the WebSocket connection to establish + await this.page.waitForTimeout(3000); // [Auth Update] Save the refreshed cookies to the auth file immediately await this._updateAuthFile(authIndex); From 7fdacae14e41e1475ca7ae3ee50300aab4140cef Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 20:13:41 +0800 Subject: [PATCH 02/18] feat: add robust WebSocket initialization monitoring and error recovery - Add _checkPageErrors() to detect common initialization errors - Add _waitForWebSocketInit() to monitor browser console logs - Detect "System initialization complete" for success - Detect "System initialization failed" for failure - Check for page errors every second during 60s timeout - Auto-refresh page on errors (applet failed, concurrent updates, snapshot failed) - Implement 3-retry mechanism with page refresh between attempts - Apply to both launchOrSwitchContext and attemptLightweightReconnect Co-Authored-By: Claude Sonnet 4.6 --- src/core/BrowserManager.js | 157 +++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 6 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index d5d2762..84ec7b8 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -100,6 +100,98 @@ class BrowserManager { this._currentAuthIndex = value; } + /** + * Helper: Check for page errors that require refresh + * @returns {Object} Object with error flags + */ + async _checkPageErrors() { + try { + const hasError = await this.page.evaluate(() => { + // eslint-disable-next-line no-undef + const bodyText = document.body.innerText || ""; + return { + appletFailed: bodyText.includes("Failed to initialize applet"), + concurrentUpdates: + bodyText.includes("There are concurrent updates") || bodyText.includes("concurrent updates"), + snapshotFailed: + bodyText.includes("Failed to create snapshot") || bodyText.includes("Please try again"), + }; + }); + return hasError; + } catch (e) { + return { appletFailed: false, concurrentUpdates: false, snapshotFailed: false }; + } + } + + /** + * Helper: Wait for WebSocket initialization with log monitoring + * @param {string} logPrefix - Log prefix for messages + * @param {number} timeout - Timeout in milliseconds (default 60000) + * @returns {Promise} true if initialization succeeded, false if failed + */ + async _waitForWebSocketInit(logPrefix = "[Browser]", timeout = 60000) { + this.logger.info(`${logPrefix} ⏳ Waiting for WebSocket initialization (timeout: ${timeout / 1000}s)...`); + + let initSuccess = false; + let initFailed = false; + + // Set up console message listener + const consoleListener = msg => { + const msgText = msg.text(); + if (msgText.includes("System initialization complete, waiting for server instructions")) { + this.logger.info(`${logPrefix} ✅ Detected successful initialization message from browser`); + initSuccess = true; + } else if (msgText.includes("System initialization failed")) { + this.logger.warn(`${logPrefix} ❌ Detected initialization failure message from browser`); + initFailed = true; + } + }; + + this.page.on("console", consoleListener); + + const startTime = Date.now(); + const checkInterval = 1000; // Check every 1 second + + try { + while (Date.now() - startTime < timeout) { + // Check if initialization succeeded + if (initSuccess) { + this.page.off("console", consoleListener); + return true; + } + + // Check if initialization failed + if (initFailed) { + this.page.off("console", consoleListener); + this.logger.warn(`${logPrefix} Initialization failed, will attempt refresh...`); + return false; + } + + // Check for page errors + const errors = await this._checkPageErrors(); + if (errors.appletFailed || errors.concurrentUpdates || errors.snapshotFailed) { + this.logger.warn( + `${logPrefix} Detected page error: ${JSON.stringify(errors)}, will attempt refresh...` + ); + this.page.off("console", consoleListener); + return false; + } + + // Wait before next check + await this.page.waitForTimeout(checkInterval); + } + + // Timeout reached + this.page.off("console", consoleListener); + this.logger.error(`${logPrefix} ⏱️ WebSocket initialization timeout after ${timeout / 1000}s`); + return false; + } catch (error) { + this.page.off("console", consoleListener); + this.logger.error(`${logPrefix} Error during WebSocket initialization wait: ${error.message}`); + return false; + } + } + /** * Feature: Update authentication file * Writes the current storageState back to the auth file, effectively extending session validity. @@ -1157,9 +1249,36 @@ class BrowserManager { // After clicking "Continue to the app", WebSocket will auto-connect await this._handlePopups("[Browser]"); - this.logger.info("[Browser] ✅ Waiting for WebSocket auto-connection after clicking button..."); - // Wait a bit for the WebSocket connection to establish - await this.page.waitForTimeout(3000); + // Wait for WebSocket initialization with error checking and retry logic + const maxRetries = 3; + let retryCount = 0; + let initSuccess = false; + + while (retryCount < maxRetries && !initSuccess) { + if (retryCount > 0) { + this.logger.info(`[Browser] 🔄 Retry attempt ${retryCount}/${maxRetries - 1}...`); + // Refresh the page and re-handle popups + await this.page.reload({ waitUntil: "domcontentloaded" }); + await this.page.waitForTimeout(2000); + await this._handlePopups("[Browser]"); + } + + // Wait for WebSocket initialization (60 second timeout) + initSuccess = await this._waitForWebSocketInit("[Browser]", 60000); + + if (!initSuccess) { + retryCount++; + if (retryCount < maxRetries) { + this.logger.warn(`[Browser] Initialization failed, refreshing page...`); + } + } + } + + if (!initSuccess) { + throw new Error( + "WebSocket initialization failed after multiple retries. Please check browser logs and page errors." + ); + } // Start background wakeup service - only started here during initial browser launch this._startBackgroundWakeup(); @@ -1232,9 +1351,35 @@ class BrowserManager { // After clicking "Continue to the app", WebSocket will auto-connect await this._handlePopups("[Reconnect]"); - this.logger.info("[Reconnect] ✅ Waiting for WebSocket auto-connection after clicking button..."); - // Wait a bit for the WebSocket connection to establish - await this.page.waitForTimeout(3000); + // Wait for WebSocket initialization with error checking and retry logic + const maxRetries = 3; + let retryCount = 0; + let initSuccess = false; + + while (retryCount < maxRetries && !initSuccess) { + if (retryCount > 0) { + this.logger.info(`[Reconnect] 🔄 Retry attempt ${retryCount}/${maxRetries - 1}...`); + // Refresh the page and re-handle popups + await this.page.reload({ waitUntil: "domcontentloaded" }); + await this.page.waitForTimeout(2000); + await this._handlePopups("[Reconnect]"); + } + + // Wait for WebSocket initialization (60 second timeout) + initSuccess = await this._waitForWebSocketInit("[Reconnect]", 60000); + + if (!initSuccess) { + retryCount++; + if (retryCount < maxRetries) { + this.logger.warn(`[Reconnect] Initialization failed, refreshing page...`); + } + } + } + + if (!initSuccess) { + this.logger.error("[Reconnect] WebSocket initialization failed after multiple retries."); + return false; + } // [Auth Update] Save the refreshed cookies to the auth file immediately await this._updateAuthFile(authIndex); From ce9dbec141c155356332916dfa5cac974a5dd4fa Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 20:33:53 +0800 Subject: [PATCH 03/18] feat: add health monitor initialization and restart logic --- src/core/BrowserManager.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 84ec7b8..3b982e0 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -1280,8 +1280,9 @@ class BrowserManager { ); } - // Start background wakeup service - only started here during initial browser launch + // Start background services - only started here during initial browser launch this._startBackgroundWakeup(); + this._startHealthMonitor(); this._currentAuthIndex = authIndex; @@ -1381,6 +1382,11 @@ class BrowserManager { return false; } + // Restart health monitor after successful reconnect + // Note: _startBackgroundWakeup is not restarted because it's a continuous loop + // that checks this.page === currentPage, and will continue running after page reload + this._startHealthMonitor(); + // [Auth Update] Save the refreshed cookies to the auth file immediately await this._updateAuthFile(authIndex); From de0362094d07472d3221baadc8b1f7b8393ed931 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 20:38:02 +0800 Subject: [PATCH 04/18] refactor: simplify WebSocket initialization monitoring and improve flag handling --- src/core/BrowserManager.js | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 3b982e0..e1b2697 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -132,22 +132,9 @@ class BrowserManager { async _waitForWebSocketInit(logPrefix = "[Browser]", timeout = 60000) { this.logger.info(`${logPrefix} ⏳ Waiting for WebSocket initialization (timeout: ${timeout / 1000}s)...`); - let initSuccess = false; - let initFailed = false; - - // Set up console message listener - const consoleListener = msg => { - const msgText = msg.text(); - if (msgText.includes("System initialization complete, waiting for server instructions")) { - this.logger.info(`${logPrefix} ✅ Detected successful initialization message from browser`); - initSuccess = true; - } else if (msgText.includes("System initialization failed")) { - this.logger.warn(`${logPrefix} ❌ Detected initialization failure message from browser`); - initFailed = true; - } - }; - - this.page.on("console", consoleListener); + // Reset flags before waiting + this._wsInitSuccess = false; + this._wsInitFailed = false; const startTime = Date.now(); const checkInterval = 1000; // Check every 1 second @@ -155,14 +142,12 @@ class BrowserManager { try { while (Date.now() - startTime < timeout) { // Check if initialization succeeded - if (initSuccess) { - this.page.off("console", consoleListener); + if (this._wsInitSuccess) { return true; } // Check if initialization failed - if (initFailed) { - this.page.off("console", consoleListener); + if (this._wsInitFailed) { this.logger.warn(`${logPrefix} Initialization failed, will attempt refresh...`); return false; } @@ -173,7 +158,6 @@ class BrowserManager { this.logger.warn( `${logPrefix} Detected page error: ${JSON.stringify(errors)}, will attempt refresh...` ); - this.page.off("console", consoleListener); return false; } @@ -182,11 +166,9 @@ class BrowserManager { } // Timeout reached - this.page.off("console", consoleListener); this.logger.error(`${logPrefix} ⏱️ WebSocket initialization timeout after ${timeout / 1000}s`); return false; } catch (error) { - this.page.off("console", consoleListener); this.logger.error(`${logPrefix} Error during WebSocket initialization wait: ${error.message}`); return false; } @@ -1238,6 +1220,15 @@ class BrowserManager { } else if (msg.type() === "error") { this.logger.error(`[Browser Page Error] ${msgText}`); } + + // Check for WebSocket initialization status + if (msgText.includes("System initialization complete, waiting for server instructions")) { + this.logger.info(`[Browser] ✅ Detected successful initialization message from browser`); + this._wsInitSuccess = true; + } else if (msgText.includes("System initialization failed")) { + this.logger.warn(`[Browser] ❌ Detected initialization failure message from browser`); + this._wsInitFailed = true; + } }); await this._navigateAndWakeUpPage("[Browser]"); From 216f1a3161580d4391a80e62b724d2140ed76785 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 21:00:05 +0800 Subject: [PATCH 05/18] refactor: improve WebSocket initialization flow and flag handling --- src/core/BrowserManager.js | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index e1b2697..5da1e00 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -132,9 +132,8 @@ class BrowserManager { async _waitForWebSocketInit(logPrefix = "[Browser]", timeout = 60000) { this.logger.info(`${logPrefix} ⏳ Waiting for WebSocket initialization (timeout: ${timeout / 1000}s)...`); - // Reset flags before waiting - this._wsInitSuccess = false; - this._wsInitFailed = false; + // Don't reset flags here - they should be reset before calling this method + // This allows the method to detect if initialization already completed const startTime = Date.now(); const checkInterval = 1000; // Check every 1 second @@ -1236,10 +1235,6 @@ class BrowserManager { // Check for cookie expiration, region restrictions, and other errors await this._checkPageStatusAndErrors("[Browser]"); - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - // After clicking "Continue to the app", WebSocket will auto-connect - await this._handlePopups("[Browser]"); - // Wait for WebSocket initialization with error checking and retry logic const maxRetries = 3; let retryCount = 0; @@ -1251,9 +1246,17 @@ class BrowserManager { // Refresh the page and re-handle popups await this.page.reload({ waitUntil: "domcontentloaded" }); await this.page.waitForTimeout(2000); - await this._handlePopups("[Browser]"); } + // Reset flags before each attempt (before handling popups) + this._wsInitSuccess = false; + this._wsInitFailed = false; + + // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + // After clicking "Continue to the app", WebSocket will auto-connect + // Note: WebSocket might already be connected before clicking the button + await this._handlePopups("[Browser]"); + // Wait for WebSocket initialization (60 second timeout) initSuccess = await this._waitForWebSocketInit("[Browser]", 60000); @@ -1339,10 +1342,6 @@ class BrowserManager { // Check for cookie expiration, region restrictions, and other errors await this._checkPageStatusAndErrors("[Reconnect]"); - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - // After clicking "Continue to the app", WebSocket will auto-connect - await this._handlePopups("[Reconnect]"); - // Wait for WebSocket initialization with error checking and retry logic const maxRetries = 3; let retryCount = 0; @@ -1354,9 +1353,17 @@ class BrowserManager { // Refresh the page and re-handle popups await this.page.reload({ waitUntil: "domcontentloaded" }); await this.page.waitForTimeout(2000); - await this._handlePopups("[Reconnect]"); } + // Reset flags before each attempt (before handling popups) + this._wsInitSuccess = false; + this._wsInitFailed = false; + + // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + // After clicking "Continue to the app", WebSocket will auto-connect + // Note: WebSocket might already be connected before clicking the button + await this._handlePopups("[Reconnect]"); + // Wait for WebSocket initialization (60 second timeout) initSuccess = await this._waitForWebSocketInit("[Reconnect]", 60000); From a9d42fc45a5d2084137128518f1d9aedeca092b8 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 21:15:50 +0800 Subject: [PATCH 06/18] refactor: enhance WebSocket flag initialization, reuse console listener, and ensure consistent flag resets --- src/core/BrowserManager.js | 97 +++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 5da1e00..5952724 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -35,6 +35,11 @@ class BrowserManager { // Added for background wakeup logic from new core this.noButtonCount = 0; + // WebSocket initialization flags - track browser-side initialization status + this._wsInitSuccess = false; + this._wsInitFailed = false; + this._consoleListenerRegistered = false; + // Firefox/Camoufox does not use Chromium-style command line args. // We keep this empty; Camoufox has its own anti-fingerprinting optimizations built-in. this.launchArgs = []; @@ -1158,7 +1163,13 @@ class BrowserManager { await Promise.race([closePromise, timeoutPromise]); this.context = null; this.page = null; - this.logger.info("[Browser] Old API context closed."); + + // Reset flags when closing context, as page object is no longer valid + this._consoleListenerRegistered = false; + this._wsInitSuccess = false; + this._wsInitFailed = false; + + this.logger.info("[Browser] Old API context closed, flags reset."); } const sourceDescription = `File auth-${authIndex}.json`; @@ -1208,27 +1219,31 @@ class BrowserManager { this.logger.warn(`[Browser] Wakeup minor error: ${e.message}`); } - this.page.on("console", msg => { - const msgText = msg.text(); - if (msgText.includes("Content-Security-Policy")) { - return; - } + // Register console listener only once to avoid duplicate registrations + if (!this._consoleListenerRegistered) { + this.page.on("console", msg => { + const msgText = msg.text(); + if (msgText.includes("Content-Security-Policy")) { + return; + } - if (msgText.includes("[ProxyClient]")) { - this.logger.info(`[Browser] ${msgText.replace("[ProxyClient] ", "")}`); - } else if (msg.type() === "error") { - this.logger.error(`[Browser Page Error] ${msgText}`); - } + if (msgText.includes("[ProxyClient]")) { + this.logger.info(`[Browser] ${msgText.replace("[ProxyClient] ", "")}`); + } else if (msg.type() === "error") { + this.logger.error(`[Browser Page Error] ${msgText}`); + } - // Check for WebSocket initialization status - if (msgText.includes("System initialization complete, waiting for server instructions")) { - this.logger.info(`[Browser] ✅ Detected successful initialization message from browser`); - this._wsInitSuccess = true; - } else if (msgText.includes("System initialization failed")) { - this.logger.warn(`[Browser] ❌ Detected initialization failure message from browser`); - this._wsInitFailed = true; - } - }); + // Check for WebSocket initialization status + if (msgText.includes("System initialization complete, waiting for server instructions")) { + this.logger.info(`[Browser] ✅ Detected successful initialization message from browser`); + this._wsInitSuccess = true; + } else if (msgText.includes("System initialization failed")) { + this.logger.warn(`[Browser] ❌ Detected initialization failure message from browser`); + this._wsInitFailed = true; + } + }); + this._consoleListenerRegistered = true; + } await this._navigateAndWakeUpPage("[Browser]"); @@ -1240,21 +1255,25 @@ class BrowserManager { let retryCount = 0; let initSuccess = false; + // Reset flags before starting retry loop + this._wsInitSuccess = false; + this._wsInitFailed = false; + while (retryCount < maxRetries && !initSuccess) { if (retryCount > 0) { this.logger.info(`[Browser] 🔄 Retry attempt ${retryCount}/${maxRetries - 1}...`); + + // Reset flags before page refresh to ensure clean state + this._wsInitSuccess = false; + this._wsInitFailed = false; + // Refresh the page and re-handle popups await this.page.reload({ waitUntil: "domcontentloaded" }); await this.page.waitForTimeout(2000); } - // Reset flags before each attempt (before handling popups) - this._wsInitSuccess = false; - this._wsInitFailed = false; - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - // After clicking "Continue to the app", WebSocket will auto-connect - // Note: WebSocket might already be connected before clicking the button + // Always handle popups to ensure they don't block initialization await this._handlePopups("[Browser]"); // Wait for WebSocket initialization (60 second timeout) @@ -1347,21 +1366,25 @@ class BrowserManager { let retryCount = 0; let initSuccess = false; + // Reset flags before starting retry loop + this._wsInitSuccess = false; + this._wsInitFailed = false; + while (retryCount < maxRetries && !initSuccess) { if (retryCount > 0) { this.logger.info(`[Reconnect] 🔄 Retry attempt ${retryCount}/${maxRetries - 1}...`); + + // Reset flags before page refresh to ensure clean state + this._wsInitSuccess = false; + this._wsInitFailed = false; + // Refresh the page and re-handle popups await this.page.reload({ waitUntil: "domcontentloaded" }); await this.page.waitForTimeout(2000); } - // Reset flags before each attempt (before handling popups) - this._wsInitSuccess = false; - this._wsInitFailed = false; - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - // After clicking "Continue to the app", WebSocket will auto-connect - // Note: WebSocket might already be connected before clicking the button + // Always handle popups to ensure they don't block initialization await this._handlePopups("[Reconnect]"); // Wait for WebSocket initialization (60 second timeout) @@ -1422,12 +1445,18 @@ class BrowserManager { this.logger.warn(`[Browser] Error during close (ignored): ${e.message}`); } - // Reset all references + // Reset all references and flags this.browser = null; this.context = null; this.page = null; this._currentAuthIndex = -1; - this.logger.info("[Browser] Main browser instance closed, currentAuthIndex reset to -1."); + + // Reset WebSocket initialization flags + this._consoleListenerRegistered = false; + this._wsInitSuccess = false; + this._wsInitFailed = false; + + this.logger.info("[Browser] Main browser instance closed, all references and flags reset."); } // Reset flag after close is complete From bdd951b87dc3de4a4f69b659ed78bd799f4f8b59 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 21:27:31 +0800 Subject: [PATCH 07/18] refactor: deprecate WS_PORT env variable, enforce fixed WebSocket port 9998, and update documentation --- .env.example | 2 ++ README.md | 15 ++++++++------- README_EN.md | 1 + src/core/BrowserManager.js | 22 ++++++---------------- src/utils/ConfigLoader.js | 10 +++++++++- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index 80d648e..6d03767 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,8 @@ PORT=7860 HOST=0.0.0.0 # WebSocket server port for internal browser communication +# ⚠️ DEPRECATED: This environment variable is no longer supported in the current version +# The WebSocket port is now fixed at 9998 and cannot be customized WS_PORT=9998 # =================================== diff --git a/README.md b/README.md index 114f4ab..4414a4f 100644 --- a/README.md +++ b/README.md @@ -249,13 +249,14 @@ sudo docker compose down #### 🗒️ 其他配置 -| 变量名 | 描述 | 默认值 | -| :------------------------- | :---------------------------------------------------------------------------------- | :------- | -| `STREAMING_MODE` | 流式传输模式。`real` 为真流式,`fake` 为假流式。 | `real` | -| `FORCE_THINKING` | 强制为所有请求启用思考模式。 | `false` | -| `FORCE_WEB_SEARCH` | 强制为所有请求启用网络搜索。 | `false` | -| `FORCE_URL_CONTEXT` | 强制为所有请求启用 URL 上下文。 | `false` | -| `CAMOUFOX_EXECUTABLE_PATH` | Camoufox 浏览器的可执行文件路径(支持绝对或相对路径)。仅在手动下载浏览器时需配置。 | 自动检测 | +| 变量名 | 描述 | 默认值 | +| :------------------------- | :---------------------------------------------------------------------------------- | :--------- | +| `STREAMING_MODE` | 流式传输模式。`real` 为真流式,`fake` 为假流式。 | `real` | +| `FORCE_THINKING` | 强制为所有请求启用思考模式。 | `false` | +| `FORCE_WEB_SEARCH` | 强制为所有请求启用网络搜索。 | `false` | +| `FORCE_URL_CONTEXT` | 强制为所有请求启用 URL 上下文。 | `false` | +| `CAMOUFOX_EXECUTABLE_PATH` | Camoufox 浏览器的可执行文件路径(支持绝对或相对路径)。仅在手动下载浏览器时需配置。 | 自动检测 | +| ~~`WS_PORT`~~ | ~~WebSocket 服务器端口。~~ **⚠️ 已弃用:此环境变量不再支持,端口固定为 9998。** | ~~`9998`~~ | ### 🧠 模型列表配置 diff --git a/README_EN.md b/README_EN.md index 734a271..1202cc6 100644 --- a/README_EN.md +++ b/README_EN.md @@ -254,6 +254,7 @@ This endpoint forwards requests to the official Gemini API format endpoint. | `FORCE_WEB_SEARCH` | Force enable web search for all requests. | `false` | | `FORCE_URL_CONTEXT` | Force enable URL context for all requests. | `false` | | `CAMOUFOX_EXECUTABLE_PATH` | Path to the Camoufox browser executable (supports both absolute and relative paths). Only required if manually downloaded. | Auto-detected | +| ~~`WS_PORT`~~ | ~~WebSocket server port.~~ **⚠️ Deprecated: This environment variable is no longer supported. Port is fixed at 9998.** | ~~`9998`~~ | ### 🧠 Model List Configuration diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 5952724..b7d8ff9 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -428,22 +428,12 @@ class BrowserManager { } if (process.env.WS_PORT) { - const lines = buildScriptContent.split("\n"); - let portReplaced = false; - for (let i = 0; i < lines.length; i++) { - if (lines[i].includes('constructor(endpoint = "ws://127.0.0.1:9998")')) { - this.logger.info(`[Config] Found port config line: ${lines[i]}`); - lines[i] = ` constructor(endpoint = "ws://127.0.0.1:${process.env.WS_PORT}") {`; - this.logger.info(`[Config] Replaced with: ${lines[i]}`); - portReplaced = true; - break; - } - } - if (portReplaced) { - buildScriptContent = lines.join("\n"); - } else { - this.logger.warn("[Config] Failed to find port config line in build.js, using default."); - } + // WS_PORT environment variable is no longer supported + this.logger.error( + `[Config] ❌ WS_PORT environment variable is deprecated and no longer supported. ` + + `The WebSocket port is now fixed at 9998. Please remove WS_PORT from your .env file.` + ); + // Do not modify the default WS_PORT - keep it at 9998 } // Inject LOG_LEVEL configuration into build.js diff --git a/src/utils/ConfigLoader.js b/src/utils/ConfigLoader.js index 24ebf0f..dae30dc 100644 --- a/src/utils/ConfigLoader.js +++ b/src/utils/ConfigLoader.js @@ -50,7 +50,15 @@ class ConfigLoader { config.maxRetries = Math.max(1, parseInt(process.env.MAX_RETRIES, 10)) || config.maxRetries; if (process.env.RETRY_DELAY) config.retryDelay = Math.max(50, parseInt(process.env.RETRY_DELAY, 10)) || config.retryDelay; - if (process.env.WS_PORT) config.wsPort = parseInt(process.env.WS_PORT, 10) || config.wsPort; + if (process.env.WS_PORT) { + // WS_PORT environment variable is no longer supported + const logger = require("./LoggingService"); + logger.error( + `[Config] ❌ WS_PORT environment variable is deprecated and no longer supported. ` + + `The WebSocket port is now fixed at 9998. Please remove WS_PORT from your .env file.` + ); + // Do not modify config.wsPort - keep it at default 9998 + } if (process.env.CAMOUFOX_EXECUTABLE_PATH) config.browserExecutablePath = process.env.CAMOUFOX_EXECUTABLE_PATH; if (process.env.API_KEYS) { config.apiKeys = process.env.API_KEYS.split(","); From 2607cbe4145121dd89200ae73e8ed176dd12c06e Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 21:29:16 +0800 Subject: [PATCH 08/18] chore: remove WS_PORT customization, enforce fixed WebSocket endpoint at 127.0.0.1:9998 --- scripts/client/build.js | 2 +- src/core/BrowserManager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/client/build.js b/scripts/client/build.js index b1f887f..44f63b9 100644 --- a/scripts/client/build.js +++ b/scripts/client/build.js @@ -98,7 +98,7 @@ const Logger = { class ConnectionManager extends EventTarget { // [BrowserManager Injection Point] Do not modify the line below. - // This line is dynamically replaced by BrowserManager.js based on WS_PORT environment variable. + // WebSocket endpoint is now fixed at ws://127.0.0.1:9998 and cannot be customized. constructor(endpoint = "ws://127.0.0.1:9998") { super(); this.endpoint = endpoint; diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index b7d8ff9..150fe30 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -399,7 +399,7 @@ class BrowserManager { /** * Helper: Load and configure build.js script content - * Applies environment-specific configurations (TARGET_DOMAIN, WS_PORT, LOG_LEVEL) + * Applies environment-specific configurations (TARGET_DOMAIN, LOG_LEVEL) * @returns {string} Configured build.js script content */ _loadAndConfigureBuildScript() { From 2f16e4fff7f7bdc30701df3e2be6508295ad2a46 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 21:50:59 +0800 Subject: [PATCH 09/18] refactor: replace local logger import with class-level logger in ConfigLoader --- src/utils/ConfigLoader.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/ConfigLoader.js b/src/utils/ConfigLoader.js index dae30dc..a426b68 100644 --- a/src/utils/ConfigLoader.js +++ b/src/utils/ConfigLoader.js @@ -52,8 +52,7 @@ class ConfigLoader { config.retryDelay = Math.max(50, parseInt(process.env.RETRY_DELAY, 10)) || config.retryDelay; if (process.env.WS_PORT) { // WS_PORT environment variable is no longer supported - const logger = require("./LoggingService"); - logger.error( + this.logger.error( `[Config] ❌ WS_PORT environment variable is deprecated and no longer supported. ` + `The WebSocket port is now fixed at 9998. Please remove WS_PORT from your .env file.` ); From d37770616f40933f9d51b5cdde4c39a50c39ddde Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 22:04:28 +0800 Subject: [PATCH 10/18] fix: correct target URL and simplify app ID validation --- src/core/BrowserManager.js | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 150fe30..d039689 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -583,6 +583,35 @@ class BrowserManager { }); this.logger.info(`${logPrefix} Page loaded.`); + // Check if we were redirected to the wrong page + const currentUrl = this.page.url(); + const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; + + if (!currentUrl.includes(expectedAppId)) { + this.logger.warn(`${logPrefix} ⚠️ Page redirected to: ${currentUrl}`); + this.logger.info(`${logPrefix} Expected app ID: ${expectedAppId}`); + this.logger.info(`${logPrefix} Attempting to navigate again...`); + + // Wait a bit before retrying + await this.page.waitForTimeout(2000); + + // Try navigating again + await this.page.goto(targetUrl, { + timeout: 180000, + waitUntil: "domcontentloaded", + }); + + const retryUrl = this.page.url(); + if (!retryUrl.includes(expectedAppId)) { + this.logger.error(`${logPrefix} ❌ Still on wrong page after retry: ${retryUrl}`); + this.logger.warn(`${logPrefix} This may cause initialization issues, but continuing...`); + } else { + this.logger.info(`${logPrefix} ✅ Successfully navigated to correct page on retry`); + } + } else { + this.logger.info(`${logPrefix} ✅ Confirmed on correct page: ${currentUrl}`); + } + // Wake up window using JS and Human Movement try { await this.page.bringToFront(); @@ -677,11 +706,41 @@ class BrowserManager { name: "Got it dialog", selector: 'div.dialog button:text("Got it")', }, + { + logFound: `${logPrefix} ✅ Found "Got it" button (generic), clicking...`, + name: "Got it button", + selector: 'button:text("Got it")', + }, { logFound: `${logPrefix} ✅ Found onboarding tutorial popup, clicking close button...`, name: "Onboarding tutorial", selector: 'button[aria-label="Close"]', }, + { + logFound: `${logPrefix} ✅ Found "Dismiss" button, clicking...`, + name: "Dismiss button", + selector: 'button:text("Dismiss")', + }, + { + logFound: `${logPrefix} ✅ Found "Not now" button, clicking...`, + name: "Not now button", + selector: 'button:text("Not now")', + }, + { + logFound: `${logPrefix} ✅ Found "Maybe later" button, clicking...`, + name: "Maybe later button", + selector: 'button:text("Maybe later")', + }, + { + logFound: `${logPrefix} ✅ Found "Skip" button, clicking...`, + name: "Skip button", + selector: 'button:text("Skip")', + }, + { + logFound: `${logPrefix} ✅ Found update notification, clicking close...`, + name: "Update notification", + selector: 'button[aria-label="Close notification"]', + }, ]; // Polling-based detection with smart exit conditions @@ -754,6 +813,15 @@ class BrowserManager { await this.page.waitForTimeout(pollInterval); } } + + // Log final summary + if (handledPopups.size === 0) { + this.logger.info(`${logPrefix} ℹ️ No popups detected during scan`); + } else { + this.logger.info( + `${logPrefix} ✅ Successfully handled ${handledPopups.size} popup(s): ${Array.from(handledPopups).join(", ")}` + ); + } } /** From 4efffe1ff5fc257f083e0a0595fd563059b63a6f Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 22:23:15 +0800 Subject: [PATCH 11/18] fix: reorder initialization flow with URL validation after popup handling --- src/core/BrowserManager.js | 182 +++++++++++++++++++++++++++++-------- 1 file changed, 145 insertions(+), 37 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index d039689..889aad6 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -583,35 +583,6 @@ class BrowserManager { }); this.logger.info(`${logPrefix} Page loaded.`); - // Check if we were redirected to the wrong page - const currentUrl = this.page.url(); - const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; - - if (!currentUrl.includes(expectedAppId)) { - this.logger.warn(`${logPrefix} ⚠️ Page redirected to: ${currentUrl}`); - this.logger.info(`${logPrefix} Expected app ID: ${expectedAppId}`); - this.logger.info(`${logPrefix} Attempting to navigate again...`); - - // Wait a bit before retrying - await this.page.waitForTimeout(2000); - - // Try navigating again - await this.page.goto(targetUrl, { - timeout: 180000, - waitUntil: "domcontentloaded", - }); - - const retryUrl = this.page.url(); - if (!retryUrl.includes(expectedAppId)) { - this.logger.error(`${logPrefix} ❌ Still on wrong page after retry: ${retryUrl}`); - this.logger.warn(`${logPrefix} This may cause initialization issues, but continuing...`); - } else { - this.logger.info(`${logPrefix} ✅ Successfully navigated to correct page on retry`); - } - } else { - this.logger.info(`${logPrefix} ✅ Confirmed on correct page: ${currentUrl}`); - } - // Wake up window using JS and Human Movement try { await this.page.bringToFront(); @@ -824,6 +795,49 @@ class BrowserManager { } } + /** + * Helper: Try to click Launch button if it exists on the page + * This is not a popup, but a page button that may need to be clicked + * @param {string} logPrefix - Log prefix for messages (e.g., "[Browser]" or "[Reconnect]") + */ + async _tryClickLaunchButton(logPrefix = "[Browser]") { + try { + this.logger.info(`${logPrefix} 🔍 Checking for Launch button...`); + + // Try to find Launch button with multiple selectors + const launchSelectors = [ + 'button:text("Launch")', + 'button:has-text("Launch")', + 'button[aria-label*="Launch"]', + 'button span:has-text("Launch")', + 'div[role="button"]:has-text("Launch")', + ]; + + let clicked = false; + for (const selector of launchSelectors) { + try { + const element = this.page.locator(selector).first(); + if (await element.isVisible({ timeout: 2000 })) { + this.logger.info(`${logPrefix} ✅ Found Launch button with selector: ${selector}`); + await element.click({ force: true, timeout: 5000 }); + this.logger.info(`${logPrefix} ✅ Launch button clicked successfully`); + clicked = true; + await this.page.waitForTimeout(1000); + break; + } + } catch (e) { + // Continue to next selector + } + } + + if (!clicked) { + this.logger.info(`${logPrefix} ℹ️ No Launch button found (this is normal if already launched)`); + } + } catch (error) { + this.logger.warn(`${logPrefix} ⚠️ Error while checking for Launch button: ${error.message}`); + } + } + /** * Feature: Background Health Monitor (The "Scavenger") * Periodically cleans up popups and keeps the session alive. @@ -1308,6 +1322,52 @@ class BrowserManager { // Check for cookie expiration, region restrictions, and other errors await this._checkPageStatusAndErrors("[Browser]"); + // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + await this._handlePopups("[Browser]"); + + // Try to click Launch button if it exists (not a popup, but a page button) + await this._tryClickLaunchButton("[Browser]"); + + // Check if we were redirected to the wrong page after handling popups + const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; + const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; + let currentUrl = this.page.url(); + + if (!currentUrl.includes(expectedAppId)) { + this.logger.warn(`[Browser] ⚠️ Page redirected to: ${currentUrl}`); + this.logger.info(`[Browser] Expected app ID: ${expectedAppId}`); + this.logger.info(`[Browser] Attempting to navigate again...`); + + // Wait a bit before retrying + await this.page.waitForTimeout(2000); + + // Try navigating again + await this.page.goto(targetUrl, { + timeout: 180000, + waitUntil: "domcontentloaded", + }); + await this.page.waitForTimeout(2000); + + // Handle popups again after retry + await this._handlePopups("[Browser]"); + + // Try to click Launch button again after retry + await this._tryClickLaunchButton("[Browser]"); + + // Check URL again + currentUrl = this.page.url(); + if (!currentUrl.includes(expectedAppId)) { + this.logger.error(`[Browser] ❌ Still on wrong page after retry: ${currentUrl}`); + throw new Error( + `Failed to navigate to correct page. Current URL: ${currentUrl}, Expected app ID: ${expectedAppId}` + ); + } else { + this.logger.info(`[Browser] ✅ Successfully navigated to correct page on retry: ${currentUrl}`); + } + } else { + this.logger.info(`[Browser] ✅ Confirmed on correct page: ${currentUrl}`); + } + // Wait for WebSocket initialization with error checking and retry logic const maxRetries = 3; let retryCount = 0; @@ -1328,11 +1388,13 @@ class BrowserManager { // Refresh the page and re-handle popups await this.page.reload({ waitUntil: "domcontentloaded" }); await this.page.waitForTimeout(2000); - } - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - // Always handle popups to ensure they don't block initialization - await this._handlePopups("[Browser]"); + // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + await this._handlePopups("[Browser]"); + + // Try to click Launch button after reload + await this._tryClickLaunchButton("[Browser]"); + } // Wait for WebSocket initialization (60 second timeout) initSuccess = await this._waitForWebSocketInit("[Browser]", 60000); @@ -1419,6 +1481,50 @@ class BrowserManager { // Check for cookie expiration, region restrictions, and other errors await this._checkPageStatusAndErrors("[Reconnect]"); + // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + await this._handlePopups("[Reconnect]"); + + // Try to click Launch button if it exists (not a popup, but a page button) + await this._tryClickLaunchButton("[Reconnect]"); + + // Check if we were redirected to the wrong page after handling popups + const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; + const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; + let currentUrl = this.page.url(); + + if (!currentUrl.includes(expectedAppId)) { + this.logger.warn(`[Reconnect] ⚠️ Page redirected to: ${currentUrl}`); + this.logger.info(`[Reconnect] Expected app ID: ${expectedAppId}`); + this.logger.info(`[Reconnect] Attempting to navigate again...`); + + // Wait a bit before retrying + await this.page.waitForTimeout(2000); + + // Try navigating again + await this.page.goto(targetUrl, { + timeout: 180000, + waitUntil: "domcontentloaded", + }); + await this.page.waitForTimeout(2000); + + // Handle popups again after retry + await this._handlePopups("[Reconnect]"); + + // Try to click Launch button again after retry + await this._tryClickLaunchButton("[Reconnect]"); + + // Check URL again + currentUrl = this.page.url(); + if (!currentUrl.includes(expectedAppId)) { + this.logger.error(`[Reconnect] ❌ Still on wrong page after retry: ${currentUrl}`); + return false; + } else { + this.logger.info(`[Reconnect] ✅ Successfully navigated to correct page on retry: ${currentUrl}`); + } + } else { + this.logger.info(`[Reconnect] ✅ Confirmed on correct page: ${currentUrl}`); + } + // Wait for WebSocket initialization with error checking and retry logic const maxRetries = 3; let retryCount = 0; @@ -1439,11 +1545,13 @@ class BrowserManager { // Refresh the page and re-handle popups await this.page.reload({ waitUntil: "domcontentloaded" }); await this.page.waitForTimeout(2000); - } - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - // Always handle popups to ensure they don't block initialization - await this._handlePopups("[Reconnect]"); + // Handle various popups (Cookie consent, Got it, Onboarding, etc.) + await this._handlePopups("[Reconnect]"); + + // Try to click Launch button after reload + await this._tryClickLaunchButton("[Reconnect]"); + } // Wait for WebSocket initialization (60 second timeout) initSuccess = await this._waitForWebSocketInit("[Reconnect]", 60000); From 3d8f229ec7f460d6c129bf6c8e38ef94d37dded3 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 22:47:10 +0800 Subject: [PATCH 12/18] fix: skip redundant WebSocket initialization if already completed --- src/core/BrowserManager.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 889aad6..8e94fca 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -1373,9 +1373,11 @@ class BrowserManager { let retryCount = 0; let initSuccess = false; - // Reset flags before starting retry loop - this._wsInitSuccess = false; - this._wsInitFailed = false; + // Check if initialization already succeeded (console listener may have detected it) + if (this._wsInitSuccess) { + this.logger.info(`[Browser] ✅ WebSocket already initialized, skipping wait`); + initSuccess = true; + } while (retryCount < maxRetries && !initSuccess) { if (retryCount > 0) { @@ -1530,9 +1532,11 @@ class BrowserManager { let retryCount = 0; let initSuccess = false; - // Reset flags before starting retry loop - this._wsInitSuccess = false; - this._wsInitFailed = false; + // Check if initialization already succeeded (console listener may have detected it) + if (this._wsInitSuccess) { + this.logger.info(`[Reconnect] ✅ WebSocket already initialized, skipping wait`); + initSuccess = true; + } while (retryCount < maxRetries && !initSuccess) { if (retryCount > 0) { From 46287d99fd6b5aa2be8478cc390ab4ec7f2385f4 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Fri, 20 Feb 2026 22:57:08 +0800 Subject: [PATCH 13/18] fix: reset WebSocket flags before re-navigation to prevent stale state --- src/core/BrowserManager.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 8e94fca..ac5ca0d 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -1338,6 +1338,10 @@ class BrowserManager { this.logger.info(`[Browser] Expected app ID: ${expectedAppId}`); this.logger.info(`[Browser] Attempting to navigate again...`); + // Reset WebSocket initialization flags before re-navigation + this._wsInitSuccess = false; + this._wsInitFailed = false; + // Wait a bit before retrying await this.page.waitForTimeout(2000); @@ -1499,6 +1503,10 @@ class BrowserManager { this.logger.info(`[Reconnect] Expected app ID: ${expectedAppId}`); this.logger.info(`[Reconnect] Attempting to navigate again...`); + // Reset WebSocket initialization flags before re-navigation + this._wsInitSuccess = false; + this._wsInitFailed = false; + // Wait a bit before retrying await this.page.waitForTimeout(2000); From 82b80c55a2012ded11bb1128ad0f81ec089a3c7f Mon Sep 17 00:00:00 2001 From: bbbugg Date: Sat, 21 Feb 2026 10:42:43 +0800 Subject: [PATCH 14/18] fix: adjust popup handling order to ensure correct URL validation before interactions --- src/core/BrowserManager.js | 42 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index ac5ca0d..d465906 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -705,7 +705,7 @@ class BrowserManager { { logFound: `${logPrefix} ✅ Found "Skip" button, clicking...`, name: "Skip button", - selector: 'button:text("Skip")', + selector: 'button:text-is("Skip")', }, { logFound: `${logPrefix} ✅ Found update notification, clicking close...`, @@ -1322,13 +1322,7 @@ class BrowserManager { // Check for cookie expiration, region restrictions, and other errors await this._checkPageStatusAndErrors("[Browser]"); - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - await this._handlePopups("[Browser]"); - - // Try to click Launch button if it exists (not a popup, but a page button) - await this._tryClickLaunchButton("[Browser]"); - - // Check if we were redirected to the wrong page after handling popups + // Check if we were redirected to the wrong page BEFORE handling popups const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; let currentUrl = this.page.url(); @@ -1352,12 +1346,6 @@ class BrowserManager { }); await this.page.waitForTimeout(2000); - // Handle popups again after retry - await this._handlePopups("[Browser]"); - - // Try to click Launch button again after retry - await this._tryClickLaunchButton("[Browser]"); - // Check URL again currentUrl = this.page.url(); if (!currentUrl.includes(expectedAppId)) { @@ -1372,6 +1360,12 @@ class BrowserManager { this.logger.info(`[Browser] ✅ Confirmed on correct page: ${currentUrl}`); } + // Handle various popups AFTER URL check (Cookie consent, Got it, Onboarding, etc.) + await this._handlePopups("[Browser]"); + + // Try to click Launch button if it exists (not a popup, but a page button) + await this._tryClickLaunchButton("[Browser]"); + // Wait for WebSocket initialization with error checking and retry logic const maxRetries = 3; let retryCount = 0; @@ -1487,13 +1481,7 @@ class BrowserManager { // Check for cookie expiration, region restrictions, and other errors await this._checkPageStatusAndErrors("[Reconnect]"); - // Handle various popups (Cookie consent, Got it, Onboarding, etc.) - await this._handlePopups("[Reconnect]"); - - // Try to click Launch button if it exists (not a popup, but a page button) - await this._tryClickLaunchButton("[Reconnect]"); - - // Check if we were redirected to the wrong page after handling popups + // Check if we were redirected to the wrong page BEFORE handling popups const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; let currentUrl = this.page.url(); @@ -1517,12 +1505,6 @@ class BrowserManager { }); await this.page.waitForTimeout(2000); - // Handle popups again after retry - await this._handlePopups("[Reconnect]"); - - // Try to click Launch button again after retry - await this._tryClickLaunchButton("[Reconnect]"); - // Check URL again currentUrl = this.page.url(); if (!currentUrl.includes(expectedAppId)) { @@ -1535,6 +1517,12 @@ class BrowserManager { this.logger.info(`[Reconnect] ✅ Confirmed on correct page: ${currentUrl}`); } + // Handle various popups AFTER URL check (Cookie consent, Got it, Onboarding, etc.) + await this._handlePopups("[Reconnect]"); + + // Try to click Launch button if it exists (not a popup, but a page button) + await this._tryClickLaunchButton("[Reconnect]"); + // Wait for WebSocket initialization with error checking and retry logic const maxRetries = 3; let retryCount = 0; From 0c04c6da09bfcf60077e0a4b5194c3ff54ad3b38 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Sat, 21 Feb 2026 10:59:26 +0800 Subject: [PATCH 15/18] chore: filter out expected WebGL not supported warning in logs --- src/core/BrowserManager.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index d465906..4c78980 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -1299,6 +1299,11 @@ class BrowserManager { return; } + // Filter out WebGL not supported warning (expected when GPU is disabled for privacy) + if (msgText.includes("WebGL not supported")) { + return; + } + if (msgText.includes("[ProxyClient]")) { this.logger.info(`[Browser] ${msgText.replace("[ProxyClient] ", "")}`); } else if (msg.type() === "error") { From f30a41d51b497e71e10a79311f6a24bb010ba277 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Sat, 21 Feb 2026 11:17:42 +0800 Subject: [PATCH 16/18] fix: replace page reload with navigation to target URL for improved popup handling --- src/core/BrowserManager.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 4c78980..1c6625b 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -1390,8 +1390,11 @@ class BrowserManager { this._wsInitSuccess = false; this._wsInitFailed = false; - // Refresh the page and re-handle popups - await this.page.reload({ waitUntil: "domcontentloaded" }); + // Navigate to target page again + await this.page.goto(targetUrl, { + timeout: 180000, + waitUntil: "domcontentloaded", + }); await this.page.waitForTimeout(2000); // Handle various popups (Cookie consent, Got it, Onboarding, etc.) @@ -1547,8 +1550,11 @@ class BrowserManager { this._wsInitSuccess = false; this._wsInitFailed = false; - // Refresh the page and re-handle popups - await this.page.reload({ waitUntil: "domcontentloaded" }); + // Navigate to target page again + await this.page.goto(targetUrl, { + timeout: 180000, + waitUntil: "domcontentloaded", + }); await this.page.waitForTimeout(2000); // Handle various popups (Cookie consent, Got it, Onboarding, etc.) From d645ce15cd019f677ea2288e54d897a70e0fece1 Mon Sep 17 00:00:00 2001 From: bbbugg Date: Sat, 21 Feb 2026 13:47:12 +0800 Subject: [PATCH 17/18] fix: add random mouse movement simulation during wait to enhance human-like interaction --- src/core/BrowserManager.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 1c6625b..e0685c6 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -165,6 +165,18 @@ class BrowserManager { return false; } + // Random mouse movement while waiting (30% chance per iteration) + if (Math.random() < 0.3) { + try { + const vp = this.page.viewportSize() || { height: 1080, width: 1920 }; + const randomX = Math.floor(Math.random() * (vp.width * 0.7)); + const randomY = Math.floor(Math.random() * (vp.height * 0.7)); + await this._simulateHumanMovement(this.page, randomX, randomY); + } catch (e) { + // Ignore movement errors + } + } + // Wait before next check await this.page.waitForTimeout(checkInterval); } From c639fce8cd333ce135cce933877eda44d003d2dc Mon Sep 17 00:00:00 2001 From: bbbugg Date: Sat, 21 Feb 2026 14:17:16 +0800 Subject: [PATCH 18/18] refactor: implement navigation verification and retry mechanism for correct page loading --- src/core/BrowserManager.js | 131 +++++++++++++++---------------------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index e0685c6..cc4dc13 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -40,6 +40,10 @@ class BrowserManager { this._wsInitFailed = false; this._consoleListenerRegistered = false; + // Target URL for AI Studio app + this.targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; + this.expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; + // Firefox/Camoufox does not use Chromium-style command line args. // We keep this empty; Camoufox has its own anti-fingerprinting optimizations built-in. this.launchArgs = []; @@ -588,8 +592,7 @@ class BrowserManager { */ async _navigateAndWakeUpPage(logPrefix = "[Browser]") { this.logger.info(`${logPrefix} Navigating to target page...`); - const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; - await this.page.goto(targetUrl, { + await this.page.goto(this.targetUrl, { timeout: 180000, waitUntil: "domcontentloaded", }); @@ -620,6 +623,49 @@ class BrowserManager { await this.page.waitForTimeout(2000 + Math.random() * 2000); } + /** + * Helper: Verify navigation to correct page and retry if needed + * Throws error on failure, which will be caught by the caller's try-catch block + * @param {string} logPrefix - Log prefix for messages (e.g., "[Browser]" or "[Reconnect]") + * @throws {Error} If navigation fails after retry + */ + async _verifyAndRetryNavigation(logPrefix = "[Browser]") { + let currentUrl = this.page.url(); + + if (!currentUrl.includes(this.expectedAppId)) { + this.logger.warn(`${logPrefix} ⚠️ Page redirected to: ${currentUrl}`); + this.logger.info(`${logPrefix} Expected app ID: ${this.expectedAppId}`); + this.logger.info(`${logPrefix} Attempting to navigate again...`); + + // Reset WebSocket initialization flags before re-navigation + this._wsInitSuccess = false; + this._wsInitFailed = false; + + // Wait a bit before retrying + await this.page.waitForTimeout(2000); + + // Try navigating again + await this.page.goto(this.targetUrl, { + timeout: 180000, + waitUntil: "domcontentloaded", + }); + await this.page.waitForTimeout(2000); + + // Check URL again + currentUrl = this.page.url(); + if (!currentUrl.includes(this.expectedAppId)) { + this.logger.error(`${logPrefix} ❌ Still on wrong page after retry: ${currentUrl}`); + throw new Error( + `Failed to navigate to correct page. Current URL: ${currentUrl}, Expected app ID: ${this.expectedAppId}` + ); + } else { + this.logger.info(`${logPrefix} ✅ Successfully navigated to correct page on retry: ${currentUrl}`); + } + } else { + this.logger.info(`${logPrefix} ✅ Confirmed on correct page: ${currentUrl}`); + } + } + /** * Helper: Check page status and detect various error conditions * Detects: cookie expiration, region restrictions, 403 errors, page load failures @@ -786,9 +832,6 @@ class BrowserManager { // 1. Must have completed minimum iterations (ensure slow popups have time to load) // 2. Consecutive idle count exceeds threshold (no new popups appearing) if (i >= minIterations - 1 && consecutiveIdleCount >= idleThreshold) { - this.logger.info( - `${logPrefix} ✅ Popup detection complete (${i + 1} iterations, ${handledPopups.size} popups handled)` - ); break; } @@ -802,7 +845,7 @@ class BrowserManager { this.logger.info(`${logPrefix} ℹ️ No popups detected during scan`); } else { this.logger.info( - `${logPrefix} ✅ Successfully handled ${handledPopups.size} popup(s): ${Array.from(handledPopups).join(", ")}` + `${logPrefix} ✅ Popup detection complete: handled ${handledPopups.size} popup(s) - ${Array.from(handledPopups).join(", ")}` ); } } @@ -1340,42 +1383,7 @@ class BrowserManager { await this._checkPageStatusAndErrors("[Browser]"); // Check if we were redirected to the wrong page BEFORE handling popups - const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; - const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; - let currentUrl = this.page.url(); - - if (!currentUrl.includes(expectedAppId)) { - this.logger.warn(`[Browser] ⚠️ Page redirected to: ${currentUrl}`); - this.logger.info(`[Browser] Expected app ID: ${expectedAppId}`); - this.logger.info(`[Browser] Attempting to navigate again...`); - - // Reset WebSocket initialization flags before re-navigation - this._wsInitSuccess = false; - this._wsInitFailed = false; - - // Wait a bit before retrying - await this.page.waitForTimeout(2000); - - // Try navigating again - await this.page.goto(targetUrl, { - timeout: 180000, - waitUntil: "domcontentloaded", - }); - await this.page.waitForTimeout(2000); - - // Check URL again - currentUrl = this.page.url(); - if (!currentUrl.includes(expectedAppId)) { - this.logger.error(`[Browser] ❌ Still on wrong page after retry: ${currentUrl}`); - throw new Error( - `Failed to navigate to correct page. Current URL: ${currentUrl}, Expected app ID: ${expectedAppId}` - ); - } else { - this.logger.info(`[Browser] ✅ Successfully navigated to correct page on retry: ${currentUrl}`); - } - } else { - this.logger.info(`[Browser] ✅ Confirmed on correct page: ${currentUrl}`); - } + await this._verifyAndRetryNavigation("[Browser]"); // Handle various popups AFTER URL check (Cookie consent, Got it, Onboarding, etc.) await this._handlePopups("[Browser]"); @@ -1403,7 +1411,7 @@ class BrowserManager { this._wsInitFailed = false; // Navigate to target page again - await this.page.goto(targetUrl, { + await this.page.goto(this.targetUrl, { timeout: 180000, waitUntil: "domcontentloaded", }); @@ -1502,40 +1510,7 @@ class BrowserManager { await this._checkPageStatusAndErrors("[Reconnect]"); // Check if we were redirected to the wrong page BEFORE handling popups - const targetUrl = "https://ai.studio/apps/0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; - const expectedAppId = "0400c62c-9bcb-48c1-b056-9b5cf4cb5603"; - let currentUrl = this.page.url(); - - if (!currentUrl.includes(expectedAppId)) { - this.logger.warn(`[Reconnect] ⚠️ Page redirected to: ${currentUrl}`); - this.logger.info(`[Reconnect] Expected app ID: ${expectedAppId}`); - this.logger.info(`[Reconnect] Attempting to navigate again...`); - - // Reset WebSocket initialization flags before re-navigation - this._wsInitSuccess = false; - this._wsInitFailed = false; - - // Wait a bit before retrying - await this.page.waitForTimeout(2000); - - // Try navigating again - await this.page.goto(targetUrl, { - timeout: 180000, - waitUntil: "domcontentloaded", - }); - await this.page.waitForTimeout(2000); - - // Check URL again - currentUrl = this.page.url(); - if (!currentUrl.includes(expectedAppId)) { - this.logger.error(`[Reconnect] ❌ Still on wrong page after retry: ${currentUrl}`); - return false; - } else { - this.logger.info(`[Reconnect] ✅ Successfully navigated to correct page on retry: ${currentUrl}`); - } - } else { - this.logger.info(`[Reconnect] ✅ Confirmed on correct page: ${currentUrl}`); - } + await this._verifyAndRetryNavigation("[Reconnect]"); // Handle various popups AFTER URL check (Cookie consent, Got it, Onboarding, etc.) await this._handlePopups("[Reconnect]"); @@ -1563,7 +1538,7 @@ class BrowserManager { this._wsInitFailed = false; // Navigate to target page again - await this.page.goto(targetUrl, { + await this.page.goto(this.targetUrl, { timeout: 180000, waitUntil: "domcontentloaded", });