diff --git a/README.md b/README.md index c025153..e5f912e 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ 2. 在 `2925 账号池` 中添加 `邮箱 / 密码` 3. 在“2925 基邮箱”右侧打开 `号池` 开关后,基邮箱输入框会切成下拉框,只能从 2925 账号池中选择邮箱;关闭开关则回退到原来的手填基邮箱路线 4. 可先点 `使用此账号` 让当前别名基邮箱切到这条 2925 账号,再点 `登录` 手动验证网页邮箱登录态 -5. 只有在 `号池` 开关开启时,自动流程执行到 Step 4 / Step 8 前才会自动检查 2925 登录态;如果未登录,会直接使用当前账号自动登录;点击登录后若 `20 秒`内仍未进入收件箱,则会判定当前登录失败 +5. 只有在 `号池` 开关开启时,自动流程执行到 Step 4 / Step 8 前才会自动检查 2925 登录态;如果未登录,会先清理登录 cookie、等待 `3 秒`,再打开登录页,并在页面打开后再等待 `3 秒`,然后使用当前账号自动登录;点击登录后若 `20 秒`内仍未进入收件箱,则会判定当前登录失败 6. 当 Step 4 / Step 8 轮询邮箱时遇到“子邮箱已达上限邮箱”,扩展会记录当前时间;如果还有下一个可用账号,就禁用当前账号 24 小时并自动切换登录;如果没有下一个可用账号,或当前未启用号池模式,则会直接复用现有“手动暂停 / 停止”逻辑终止自动流程 7. 如果你同时开启了 `Auto` 的自动重试,当前尝试结束后会按现有逻辑自动进入下一次尝试,不需要再手动介入 8. `Mail = 2925` 仍然走 Gmail / 2925 共用的别名邮箱链路;实际注册邮箱会基于当前 2925 账号邮箱生成,例如 `name@2925.com -> name123456@2925.com` diff --git a/background.js b/background.js index 0cbd4c9..8795baf 100644 --- a/background.js +++ b/background.js @@ -5395,6 +5395,7 @@ const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMa pickMail2925AccountForRun, requestStop, reuseOrCreateTab, + sendToContentScriptResilient, sendToMailContentScriptResilient, setPersistentSettings, setState, diff --git a/background/mail-2925-session.js b/background/mail-2925-session.js index 04f23e3..3bdc609 100644 --- a/background/mail-2925-session.js +++ b/background/mail-2925-session.js @@ -17,6 +17,7 @@ isAutoRunLockedState, requestStop, reuseOrCreateTab, + sendToContentScriptResilient, sendToMailContentScriptResilient, setPersistentSettings, setState, @@ -128,6 +129,20 @@ return findMail2925Account(state.mail2925Accounts, state.currentMail2925AccountId) || null; } + async function getMail2925CurrentTabUrl() { + try { + const state = await getState(); + const tabId = Number(state?.tabRegistry?.[MAIL2925_SOURCE]?.tabId || 0); + if (!Number.isInteger(tabId) || tabId <= 0) { + return ''; + } + const tab = await chrome.tabs.get(tabId); + return String(tab?.url || '').trim(); + } catch { + return ''; + } + } + async function setCurrentMail2925Account(accountId, options = {}) { const { logMessage = '', updateLastUsedAt = false } = options; const state = await getState(); @@ -376,6 +391,9 @@ inject: MAIL2925_INJECT, injectSource: MAIL2925_INJECT_SOURCE, }); + if (forceRelogin && typeof sleepWithStop === 'function') { + await sleepWithStop(3000); + } let result; try { @@ -398,6 +416,8 @@ } ); } catch (err) { + const failedUrl = await getMail2925CurrentTabUrl(); + await addLog(`2925:ENSURE_MAIL2925_SESSION 通信失败,当前地址=${failedUrl || 'unknown'};原因=${getErrorMessage(err) || 'unknown'}`, 'warn'); const stopped = await stopAutoRunForMail2925LoginFailure( `2925:${actionLabel}失败(${getErrorMessage(err) || '20 秒内未进入收件箱'}),已按手动停止逻辑暂停自动流程。` ); @@ -408,6 +428,8 @@ } if (!result?.loggedIn) { + const notLoggedInUrl = await getMail2925CurrentTabUrl(); + await addLog(`2925:20 秒登录等待结束但仍未进入收件箱,当前地址=${notLoggedInUrl || 'unknown'}`, 'warn'); const stopped = await stopAutoRunForMail2925LoginFailure( `2925:${actionLabel}失败(20 秒内未进入收件箱),已按手动停止逻辑暂停自动流程。` ); @@ -418,6 +440,8 @@ } if (result?.error) { + const resultErrorUrl = await getMail2925CurrentTabUrl(); + await addLog(`2925:登录页返回业务错误,当前地址=${resultErrorUrl || 'unknown'};错误=${result.error}`, 'warn'); const stopped = await stopAutoRunForMail2925LoginFailure( `2925:${actionLabel}失败(${result.error}),已按手动停止逻辑暂停自动流程。` ); @@ -450,6 +474,129 @@ }; } + // Override the earlier version with a simpler login-page-only flow. + async function ensureMail2925MailboxSession(options = {}) { + const { + accountId = null, + forceRelogin = false, + actionLabel = '确保 2925 邮箱登录态', + } = options; + const account = await ensureMail2925AccountForFlow({ + allowAllocate: true, + preferredAccountId: accountId, + }); + + if (forceRelogin) { + const removedCount = await clearMail2925SessionCookies(); + await addLog(`2925:已清理 ${removedCount} 个登录相关 cookie,准备使用 ${account.email} 重新登录。`, 'info'); + if (typeof sleepWithStop === 'function') { + await addLog('2925:清理 cookie 后等待 3 秒,再打开登录页...', 'info'); + await sleepWithStop(3000); + } + } + + throwIfStopped(); + await addLog(`2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(forceRelogin=${forceRelogin ? 'true' : 'false'})`, 'info'); + await reuseOrCreateTab(MAIL2925_SOURCE, forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL, { + inject: MAIL2925_INJECT, + injectSource: MAIL2925_INJECT_SOURCE, + }); + const openedUrl = await getMail2925CurrentTabUrl(); + await addLog(`2925:打开页后当前标签地址:${openedUrl || 'unknown'}`, 'info'); + + if (forceRelogin && typeof sleepWithStop === 'function') { + await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info'); + await sleepWithStop(3000); + } + + const sendLoginMessage = typeof sendToContentScriptResilient === 'function' + ? sendToContentScriptResilient + : async (source, message, runtimeOptions = {}) => sendToMailContentScriptResilient( + getMail2925MailConfig(), + message, + { + timeoutMs: runtimeOptions.timeoutMs, + responseTimeoutMs: runtimeOptions.responseTimeoutMs, + maxRecoveryAttempts: 0, + } + ); + + let result; + try { + const beforeSendUrl = await getMail2925CurrentTabUrl(); + await addLog(`2925:发送 ENSURE_MAIL2925_SESSION 前当前地址:${beforeSendUrl || 'unknown'}`, 'info'); + result = await sendLoginMessage( + MAIL2925_SOURCE, + { + type: 'ENSURE_MAIL2925_SESSION', + step: 0, + source: 'background', + payload: { + email: account.email, + password: account.password, + forceLogin: forceRelogin, + }, + }, + { + timeoutMs: forceRelogin ? 30000 : 25000, + retryDelayMs: 800, + responseTimeoutMs: forceRelogin ? 30000 : 25000, + logMessage: `步骤 0:2925 登录页通信异常,正在等待当前页面重新就绪后继续确认登录态...`, + } + ); + } catch (err) { + const stopped = await stopAutoRunForMail2925LoginFailure( + `2925:${actionLabel}失败(${getErrorMessage(err) || '20 秒内未进入收件箱'}),已按手动停止逻辑暂停自动流程。` + ); + if (stopped) { + throw new Error('流程已被用户停止。'); + } + throw err; + } + + if (result?.error) { + const stopped = await stopAutoRunForMail2925LoginFailure( + `2925:${actionLabel}失败(${result.error}),已按手动停止逻辑暂停自动流程。` + ); + if (stopped) { + throw new Error('流程已被用户停止。'); + } + throw new Error(result.error); + } + + if (result?.limitReached) { + throw new Error(`${MAIL2925_LIMIT_ERROR_PREFIX}${result.limitMessage || '2925 子邮箱已达上限邮箱'}`); + } + + if (!result?.loggedIn) { + const stopped = await stopAutoRunForMail2925LoginFailure( + `2925:${actionLabel}失败(20 秒内未进入收件箱),已按手动停止逻辑暂停自动流程。` + ); + if (stopped) { + throw new Error('流程已被用户停止。'); + } + throw new Error(`2925:${actionLabel}失败,当前页面仍未进入收件箱。`); + } + + await patchMail2925Account(account.id, { + lastLoginAt: Date.now(), + lastError: '', + }); + await setState({ currentMail2925AccountId: account.id }); + broadcastDataUpdate({ currentMail2925AccountId: account.id }); + const finalUrl = await getMail2925CurrentTabUrl(); + await addLog(`2925:登录态确认成功,当前地址=${finalUrl || 'unknown'}`, 'ok'); + + return { + account: await ensureMail2925AccountForFlow({ + allowAllocate: false, + preferredAccountId: account.id, + }), + mail: getMail2925MailConfig(), + result, + }; + } + async function handleMail2925LimitReachedError(step, error) { const reason = getErrorMessage(error).replace(MAIL2925_LIMIT_ERROR_PREFIX, '').trim() || '子邮箱已达上限邮箱'; diff --git a/content/mail-2925.js b/content/mail-2925.js index ffd8993..4c036b1 100644 --- a/content/mail-2925.js +++ b/content/mail-2925.js @@ -833,10 +833,12 @@ async function ensureMail2925Session(payload = {}) { const email = String(payload?.email || '').trim(); const password = String(payload?.password || ''); const forceLogin = Boolean(payload?.forceLogin); + log(`步骤 0:2925 登录态检查开始,当前地址 ${location.href},forceLogin=${forceLogin ? 'true' : 'false'}`, 'info'); for (let attempt = 0; attempt < 10; attempt += 1) { throwIfStopped(); const currentState = detectMail2925ViewState(); + log(`步骤 0:2925 登录页状态探测,第 ${attempt + 1}/10 次,状态=${currentState.view},地址=${location.href}`, 'info'); if (currentState.view === 'limit') { return { ok: false, @@ -860,6 +862,7 @@ async function ensureMail2925Session(payload = {}) { } const loginState = detectMail2925ViewState(); + log(`步骤 0:2925 准备执行登录,当前状态=${loginState.view},地址=${location.href}`, 'info'); if (loginState.view === 'mailbox') { return { ok: true, @@ -892,9 +895,12 @@ async function ensureMail2925Session(payload = {}) { await sleep(150); fillInput(passwordInput, password); await sleep(200); + log(`步骤 0:2925 已定位到登录表单,准备点击“登录”,当前地址 ${location.href}`, 'info'); simulateClick(loginButton); + log(`步骤 0:2925 已点击“登录”,点击后地址 ${location.href}`, 'info'); const finalState = await waitForMail2925View('mailbox', 20000); + log(`步骤 0:2925 登录等待结束,状态=${finalState.view},地址=${location.href}`, 'info'); if (finalState.view !== 'mailbox') { throw new Error('2925:提交账号密码后未进入收件箱。'); } diff --git a/tests/background-mail2925-relogin-wait.test.js b/tests/background-mail2925-relogin-wait.test.js new file mode 100644 index 0000000..fe7634b --- /dev/null +++ b/tests/background-mail2925-relogin-wait.test.js @@ -0,0 +1,66 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const mail2925Utils = require('../mail2925-utils.js'); + +test('ensureMail2925MailboxSession waits 3 seconds before and after opening login page on force relogin', async () => { + const source = fs.readFileSync('background/mail-2925-session.js', 'utf8'); + const globalScope = {}; + const api = new Function('self', `${source}; return self.MultiPageBackgroundMail2925Session;`)(globalScope); + + let currentState = { + autoRunning: false, + mail2925Accounts: mail2925Utils.normalizeMail2925Accounts([ + { id: 'acc-1', email: 'acc1@2925.com', password: 'p1', enabled: true, lastUsedAt: 10 }, + ]), + currentMail2925AccountId: 'acc-1', + }; + const events = { + sleeps: [], + }; + + const manager = api.createMail2925SessionManager({ + addLog: async () => {}, + broadcastDataUpdate: () => {}, + chrome: { + cookies: { + getAll: async () => [], + remove: async () => ({ ok: true }), + }, + browsingData: { + removeCookies: async () => {}, + }, + }, + findMail2925Account: mail2925Utils.findMail2925Account, + getMail2925AccountStatus: mail2925Utils.getMail2925AccountStatus, + getState: async () => currentState, + isAutoRunLockedState: () => false, + isMail2925AccountAvailable: mail2925Utils.isMail2925AccountAvailable, + MAIL2925_LIMIT_COOLDOWN_MS: mail2925Utils.MAIL2925_LIMIT_COOLDOWN_MS, + normalizeMail2925Account: mail2925Utils.normalizeMail2925Account, + normalizeMail2925Accounts: mail2925Utils.normalizeMail2925Accounts, + pickMail2925AccountForRun: mail2925Utils.pickMail2925AccountForRun, + requestStop: async () => {}, + reuseOrCreateTab: async () => 1, + sendToMailContentScriptResilient: async () => ({ loggedIn: true }), + setPersistentSettings: async (payload) => { + currentState = { ...currentState, ...payload }; + }, + setState: async (updates) => { + currentState = { ...currentState, ...updates }; + }, + sleepWithStop: async (ms) => { + events.sleeps.push(ms); + }, + throwIfStopped: () => {}, + upsertMail2925AccountInList: mail2925Utils.upsertMail2925AccountInList, + }); + + await manager.ensureMail2925MailboxSession({ + accountId: 'acc-1', + forceRelogin: true, + actionLabel: '步骤 4:确认 2925 邮箱登录态', + }); + + assert.deepStrictEqual(events.sleeps, [3000, 3000]); +}); diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 2e9062d..cbd413c 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -308,7 +308,7 @@ 补充行为: - `2925` provider 会关闭 Step 4 / 8 的自动重发间隔 25 秒节流;每次“重新发送验证码”之间,会在邮箱页内部执行一轮固定 15 次的刷新轮询,不再因 OAuth 剩余时间预算而缩短。 -- 当 provider 为 `2925` 时,后台会在进入 Step 4 / Step 8 之前先确保当前 2925 账号已登录网页邮箱;如果当前标签页仍未登录,会自动补一次“清理 2925 cookie -> 使用当前账号密码重新登录”。 +- 当 provider 为 `2925` 时,后台会在进入 Step 4 / Step 8 之前先确保当前 2925 账号已登录网页邮箱;如果当前标签页仍未登录,会自动补一次“清理 2925 cookie -> 等待 3 秒 -> 打开登录页 -> 页面打开后再等待 3 秒 -> 使用当前账号密码重新登录”。 - 普通邮箱仍会携带 `filterAfterTimestamp` 做时间窗筛选;`2925` 当前既不依赖时间窗,也不再做“新旧邮件快照差集”比较,而是每次刷新后直接遍历当前列表中的匹配邮件。 - 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。 - 验证码提交重试上限当前为 15 次;页面明确拒绝验证码时,会在上限内继续拉取新验证码并重提。