diff --git a/README.md b/README.md index 4522878..9510f6e 100644 --- a/README.md +++ b/README.md @@ -196,14 +196,15 @@ sudo docker compose down #### 🌐 代理配置 -| 变量名 | 描述 | 默认值 | -| :------------------------------ | :--------------------------------------------------- | :-------- | -| `INITIAL_AUTH_INDEX` | 启动时使用的初始身份验证索引。 | `0` | -| `MAX_RETRIES` | 请求失败后的最大重试次数(仅对假流式和非流式生效)。 | `3` | -| `RETRY_DELAY` | 两次重试之间的间隔(毫秒)。 | `2000` | -| `SWITCH_ON_USES` | 自动切换帐户前允许的请求次数(设为 `0` 禁用)。 | `40` | -| `FAILURE_THRESHOLD` | 切换帐户前允许的连续失败次数(设为 `0` 禁用)。 | `3` | -| `IMMEDIATE_SWITCH_STATUS_CODES` | 触发立即切换帐户的 HTTP 状态码(逗号分隔)。 | `429,503` | +| 变量名 | 描述 | 默认值 | +| :------------------------------ | :---------------------------------------------------------------------------------------------------------- | :-------- | +| `INITIAL_AUTH_INDEX` | 启动时使用的初始身份验证索引。 | `0` | +| `ENABLE_AUTH_UPDATE` | 是否启用自动保存凭证更新。设为 `true` 启用后,将在每次登录/切换账号成功时以及每 24 小时自动更新 auth 文件。 | `false` | +| `MAX_RETRIES` | 请求失败后的最大重试次数(仅对假流式和非流式生效)。 | `3` | +| `RETRY_DELAY` | 两次重试之间的间隔(毫秒)。 | `2000` | +| `SWITCH_ON_USES` | 自动切换帐户前允许的请求次数(设为 `0` 禁用)。 | `40` | +| `FAILURE_THRESHOLD` | 切换帐户前允许的连续失败次数(设为 `0` 禁用)。 | `3` | +| `IMMEDIATE_SWITCH_STATUS_CODES` | 触发立即切换帐户的 HTTP 状态码(逗号分隔)。 | `429,503` | #### 🗒️ 其他配置 diff --git a/README_EN.md b/README_EN.md index 8376d16..8aca014 100644 --- a/README_EN.md +++ b/README_EN.md @@ -196,14 +196,15 @@ This endpoint is forwarded to the official Gemini API format endpoint. #### 🌐 Proxy Configuration -| Variable | Description | Default | -| :------------------------------ | :--------------------------------------------------------------------------------------------------- | :-------- | -| `INITIAL_AUTH_INDEX` | Initial authentication index to use on startup. | `0` | -| `MAX_RETRIES` | Maximum number of retries for failed requests (only effective for fake streaming and non-streaming). | `3` | -| `RETRY_DELAY` | Delay between retries in milliseconds. | `2000` | -| `SWITCH_ON_USES` | Number of requests before automatically switching accounts (`0` to disable). | `40` | -| `FAILURE_THRESHOLD` | Number of consecutive failures before switching accounts (`0` to disable). | `3` | -| `IMMEDIATE_SWITCH_STATUS_CODES` | HTTP status codes that trigger immediate account switching (comma-separated). | `429,503` | +| Variable | Description | Default | +| :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------- | +| `INITIAL_AUTH_INDEX` | Initial authentication index to use on startup. | `0` | +| `ENABLE_AUTH_UPDATE` | Whether to enable automatic auth credential updates. If set to `true`, the auth file will be automatically updated upon successful login/account switch and every 24 hours. | `false` | +| `MAX_RETRIES` | Maximum number of retries for failed requests (only effective for fake streaming and non-streaming). | `3` | +| `RETRY_DELAY` | Delay between retries in milliseconds. | `2000` | +| `SWITCH_ON_USES` | Number of requests before automatically switching accounts (`0` to disable). | `40` | +| `FAILURE_THRESHOLD` | Number of consecutive failures before switching accounts (`0` to disable). | `3` | +| `IMMEDIATE_SWITCH_STATUS_CODES` | HTTP status codes that trigger immediate account switching (comma-separated). | `429,503` | #### 🗒️ Other Configuration diff --git a/src/auth/AuthSource.js b/src/auth/AuthSource.js index 119a0c0..2dd515d 100644 --- a/src/auth/AuthSource.js +++ b/src/auth/AuthSource.js @@ -127,7 +127,7 @@ class AuthSource { validIndices.push(index); this.accountNameMap.set(index, authData.accountName || null); } catch (e) { - invalidSourceDescriptions.push(`auth-${index}`); + invalidSourceDescriptions.push(`auth-${index} (parse error)`); } } else { invalidSourceDescriptions.push(`auth-${index} (unreadable)`); diff --git a/src/core/BrowserManager.js b/src/core/BrowserManager.js index 19cc80a..2ee6ad0 100644 --- a/src/core/BrowserManager.js +++ b/src/core/BrowserManager.js @@ -96,6 +96,51 @@ class BrowserManager { this._currentAuthIndex = value; } + /** + * Feature: Update authentication file + * Writes the current storageState back to the auth file, effectively extending session validity. + * @param {number} authIndex - The auth index to update + */ + async _updateAuthFile(authIndex) { + if (!this.context) return; + + // Check availability of auto-update feature from config + if (!this.config.enableAuthUpdate) { + return; + } + + try { + const configDir = path.join(process.cwd(), "configs", "auth"); + const authFilePath = path.join(configDir, `auth-${authIndex}.json`); + + // Read original file content to preserve all fields (e.g. accountName, custom fields) + // Relies on AuthSource validation (checks valid index AND file existence) + const authData = this.authSource.getAuth(authIndex); + if (!authData) { + this.logger.warn( + `[Auth Update] Auth source #${authIndex} returned no data (invalid index or file missing), skipping update.` + ); + return; + } + + const storageState = await this.context.storageState(); + + // Merge new credentials into existing data + authData.cookies = storageState.cookies; + authData.origins = storageState.origins; + + // Note: We do NOT force-set accountName. If it was there, it stays; if not, it remains missing. + // This preserves the "missing state" as requested. + + // Overwrite the file with merged data + await fs.promises.writeFile(authFilePath, JSON.stringify(authData, null, 2)); + + this.logger.info(`[Auth Update] 💾 Successfully updated auth credentials for account #${authIndex}`); + } catch (error) { + this.logger.error(`[Auth Update] ❌ Failed to update auth file: ${error.message}`); + } + } + /** * Interface: Notify user activity * Used to force wake up the Launch detection when a request comes in @@ -305,7 +350,19 @@ class BrowserManager { } } - // 2. Popup & Overlay Cleanup + // 3. Auto-Save Auth: Every ~24 hours (21600 ticks * 4s = 86400s) + if (tickCount % 21600 === 0) { + if (this._currentAuthIndex !== -1) { + try { + this.logger.info("[HealthMonitor] 💾 Triggering daily periodic auth file update..."); + await this._updateAuthFile(this._currentAuthIndex); + } catch (e) { + this.logger.warn(`[HealthMonitor] Auth update failed: ${e.message}`); + } + } + } + + // 4. Popup & Overlay Cleanup await page.evaluate(() => { const blockers = [ "div.cdk-overlay-backdrop", @@ -887,6 +944,10 @@ class BrowserManager { this._startHealthMonitor(); this._startBackgroundWakeup(); this._currentAuthIndex = authIndex; + + // [Auth Update] Save the refreshed cookies to the auth file immediately + await this._updateAuthFile(authIndex); + this.logger.info("=================================================="); this.logger.info(`✅ [Browser] Account ${authIndex} context initialized successfully!`); this.logger.info("✅ [Browser] Browser client is ready."); diff --git a/src/utils/ConfigLoader.js b/src/utils/ConfigLoader.js index 184f452..44da057 100644 --- a/src/utils/ConfigLoader.js +++ b/src/utils/ConfigLoader.js @@ -23,6 +23,7 @@ class ConfigLoader { apiKeys: [], apiKeySource: "Not set", browserExecutablePath: null, + enableAuthUpdate: false, failureThreshold: 3, forceThinking: false, forceUrlContext: false, @@ -59,6 +60,8 @@ class ConfigLoader { if (process.env.FORCE_WEB_SEARCH) config.forceWebSearch = process.env.FORCE_WEB_SEARCH.toLowerCase() === "true"; if (process.env.FORCE_URL_CONTEXT) config.forceUrlContext = process.env.FORCE_URL_CONTEXT.toLowerCase() === "true"; + if (process.env.ENABLE_AUTH_UPDATE) + config.enableAuthUpdate = process.env.ENABLE_AUTH_UPDATE.toLowerCase() === "true"; let rawCodes = process.env.IMMEDIATE_SWITCH_STATUS_CODES; let codesSource = "environment variable"; @@ -132,6 +135,7 @@ class ConfigLoader { this.logger.info(` Force Thinking: ${config.forceThinking}`); this.logger.info(` Force Web Search: ${config.forceWebSearch}`); this.logger.info(` Force URL Context: ${config.forceUrlContext}`); + this.logger.info(` Auto Update Auth: ${config.enableAuthUpdate}`); this.logger.info( ` Usage-based Switch Threshold: ${ config.switchOnUses > 0 ? `Switch after every ${config.switchOnUses} requests` : "Disabled"