From 7c204056851730553f380a71ad3bbc6e1ab60fcb Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Tue, 21 Apr 2026 18:02:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(mail2925):=20=E4=BF=AE=E5=A4=8D=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=A0=81=E8=BD=AE=E8=AF=A2=E4=B8=8E=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=80=81=E5=A4=8D=E7=94=A8=E9=80=BB=E8=BE=91=E5=B9=B6=E6=B8=85?= =?UTF-8?q?=E7=90=86=E4=B8=AD=E6=96=87=E6=96=87=E6=A1=88=EF=BC=8C=E9=87=8D?= =?UTF-8?q?=E6=9E=84=20sidepanel=EF=BC=9A=E7=BB=9F=E4=B8=80=20Hotmail=20?= =?UTF-8?q?=E5=92=8C=202925=20=E5=8F=B7=E6=B1=A0=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 抽出共享 account-pool-ui helper - 统一添加账号、取消添加、批量导入交互 - 调整号池列表收起高度与按钮布局 - 补充测试并同步更新项目文档 --- background.js | 1 + background/mail-2925-session.js | 341 +++++++----------- background/steps/fetch-login-code.js | 51 ++- background/steps/fetch-signup-code.js | 59 +-- sidepanel/account-pool-ui.js | 58 +++ sidepanel/hotmail-manager.js | 31 +- sidepanel/mail-2925-manager.js | 64 ++-- sidepanel/sidepanel.css | 8 +- sidepanel/sidepanel.html | 66 ++-- sidepanel/sidepanel.js | 4 + tests/background-icloud-mail-provider.test.js | 29 ++ ...background-mail2925-entry-behavior.test.js | 207 +++++++++++ .../background-mail2925-relogin-wait.test.js | 2 +- ...background-mail2925-session-module.test.js | 2 +- tests/background-step4-filter-window.test.js | 22 +- tests/background-step7-recovery.test.js | 21 +- tests/sidepanel-hotmail-manager.test.js | 253 ++++++++++++- tests/sidepanel-mail2925-edit.test.js | 47 ++- tests/sidepanel-mail2925-manager.test.js | 63 +++- 项目完整链路说明.md | 14 +- 项目开发规范(AI协作).md | 1 + 项目文件结构说明.md | 11 +- 22 files changed, 1014 insertions(+), 341 deletions(-) create mode 100644 sidepanel/account-pool-ui.js create mode 100644 tests/background-mail2925-entry-behavior.test.js diff --git a/background.js b/background.js index 43b89dc..83b9e09 100644 --- a/background.js +++ b/background.js @@ -6564,6 +6564,7 @@ function getMailConfig(state) { } if (provider === '2925') { return { + provider: '2925', source: 'mail-2925', url: 'https://2925.com/#/mailList', label: '2925 邮箱', diff --git a/background/mail-2925-session.js b/background/mail-2925-session.js index 4649231..5d73f83 100644 --- a/background/mail-2925-session.js +++ b/background/mail-2925-session.js @@ -6,16 +6,16 @@ addLog, broadcastDataUpdate, chrome, + ensureContentScriptReadyOnTab, findMail2925Account, getMail2925AccountStatus, + getState, + isAutoRunLockedState, isMail2925AccountAvailable, MAIL2925_LIMIT_COOLDOWN_MS, normalizeMail2925Account, normalizeMail2925Accounts, pickMail2925AccountForRun, - getState, - isAutoRunLockedState, - ensureContentScriptReadyOnTab, requestStop, reuseOrCreateTab, sendToContentScriptResilient, @@ -86,8 +86,10 @@ function isMail2925LimitReachedError(error) { const message = getErrorMessage(error); + const normalized = message.toLowerCase(); return message.startsWith(MAIL2925_LIMIT_ERROR_PREFIX) - || /子邮箱.{0,12}已达上限|已达上限邮箱|子邮箱上限|邮箱已达上限/i.test(message); + || normalized.includes('子邮箱已达上限') + || normalized.includes('已达上限邮箱'); } function isMail2925ThreadTerminatedError(error) { @@ -135,7 +137,7 @@ try { const state = await getState(); const tabId = Number(state?.tabRegistry?.[MAIL2925_SOURCE]?.tabId || 0); - if (!Number.isInteger(tabId) || tabId <= 0) { + if (!Number.isInteger(tabId) || tabId <= 0 || typeof chrome.tabs?.get !== 'function') { return ''; } const tab = await chrome.tabs.get(tabId); @@ -145,6 +147,28 @@ } } + async function getMail2925TabUrlById(tabId) { + try { + if (!Number.isInteger(Number(tabId)) || Number(tabId) <= 0 || typeof chrome.tabs?.get !== 'function') { + return ''; + } + const tab = await chrome.tabs.get(Number(tabId)); + return String(tab?.url || '').trim(); + } catch { + return ''; + } + } + + function isMail2925LoginUrl(rawUrl = '') { + try { + const parsed = new URL(String(rawUrl || '')); + return (parsed.hostname === '2925.com' || parsed.hostname === 'www.2925.com') + && /^\/login\/?$/.test(parsed.pathname); + } catch { + return false; + } + } + async function setCurrentMail2925Account(accountId, options = {}) { const { logMessage = '', updateLastUsedAt = false } = options; const state = await getState(); @@ -361,7 +385,7 @@ origins: MAIL2925_COOKIE_ORIGINS, }); } catch (_) { - // Best-effort cleanup only. + // 这里只做尽力清理。 } } @@ -373,172 +397,13 @@ accountId = null, forceRelogin = false, actionLabel = '确保 2925 邮箱登录态', + allowLoginWhenOnLoginPage = true, } = options; const account = await ensureMail2925AccountForFlow({ allowAllocate: true, preferredAccountId: accountId, }); - if (forceRelogin) { - const removedCount = await clearMail2925SessionCookies(); - await addLog(`2925:已清理 ${removedCount} 个登录相关 cookie,准备使用 ${account.email} 重新登录。`, 'info'); - } - - if (forceRelogin && typeof sleepWithStop === 'function') { - await sleepWithStop(3000); - } - - throwIfStopped(); - const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL; - const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, { - inject: MAIL2925_INJECT, - injectSource: MAIL2925_INJECT_SOURCE, - }); - if (forceRelogin && typeof sleepWithStop === 'function') { - await sleepWithStop(3000); - } - - let result; - try { - result = await sendToMailContentScriptResilient( - getMail2925MailConfig(), - { - type: 'ENSURE_MAIL2925_SESSION', - step: 0, - source: 'background', - payload: { - email: account.email, - password: account.password, - forceLogin: forceRelogin, - }, - }, - { - timeoutMs: forceRelogin ? 30000 : 25000, - responseTimeoutMs: forceRelogin ? 30000 : 25000, - maxRecoveryAttempts: 2, - } - ); - } 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 秒内未进入收件箱'}),已按手动停止逻辑暂停自动流程。` - ); - if (stopped) { - throw new Error('流程已被用户停止。'); - } - throw err; - } - - if (!result?.loggedIn) { - const notLoggedInUrl = await getMail2925CurrentTabUrl(); - await addLog(`2925:20 秒登录等待结束但仍未进入收件箱,当前地址=${notLoggedInUrl || 'unknown'}`, 'warn'); - const stopped = await stopAutoRunForMail2925LoginFailure( - `2925:${actionLabel}失败(20 秒内未进入收件箱),已按手动停止逻辑暂停自动流程。` - ); - if (stopped) { - throw new Error('流程已被用户停止。'); - } - throw new Error(`2925:${actionLabel}失败,当前页面仍未进入收件箱。`); - } - - if (result?.error) { - const resultErrorUrl = await getMail2925CurrentTabUrl(); - await addLog(`2925:登录页返回业务错误,当前地址=${resultErrorUrl || 'unknown'};错误=${result.error}`, 'warn'); - 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) { - throw new Error(`2925:${actionLabel}失败,当前页面仍未进入收件箱。`); - } - - await patchMail2925Account(account.id, { - lastLoginAt: Date.now(), - lastError: '', - }); - await setState({ currentMail2925AccountId: account.id }); - broadcastDataUpdate({ currentMail2925AccountId: account.id }); - - return { - account: await ensureMail2925AccountForFlow({ - allowAllocate: false, - preferredAccountId: account.id, - }), - mail: getMail2925MailConfig(), - result, - }; - } - - // 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'); - const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL; - const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, { - inject: MAIL2925_INJECT, - injectSource: MAIL2925_INJECT_SOURCE, - }); - const openedUrl = await getMail2925CurrentTabUrl(); - await addLog(`2925:打开页后当前标签地址:${openedUrl || 'unknown'}`, 'info'); - - if (forceRelogin && typeof waitForTabUrlMatch === 'function') { - const matchedLoginTab = await waitForTabUrlMatch( - tabId, - (url) => { - try { - const parsed = new URL(String(url || '')); - return (parsed.hostname === '2925.com' || parsed.hostname === 'www.2925.com') - && /^\/login\/?$/.test(parsed.pathname); - } catch { - return false; - } - }, - { timeoutMs: 15000, retryDelayMs: 300 } - ); - await addLog(`2925:等待最终落到登录页结果:${matchedLoginTab?.url || 'timeout'}`, matchedLoginTab ? 'info' : 'warn'); - if (matchedLoginTab && typeof ensureContentScriptReadyOnTab === 'function') { - await ensureContentScriptReadyOnTab(MAIL2925_SOURCE, tabId, { - inject: MAIL2925_INJECT, - injectSource: MAIL2925_INJECT_SOURCE, - timeoutMs: 20000, - retryDelayMs: 800, - logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...', - }); - } - } - - if (forceRelogin && typeof sleepWithStop === 'function') { - await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info'); - await sleepWithStop(3000); - } - const sendLoginMessage = typeof sendToContentScriptResilient === 'function' ? sendToContentScriptResilient : async (source, message, runtimeOptions = {}) => sendToMailContentScriptResilient( @@ -551,9 +416,104 @@ } ); + const buildSuccessPayload = async (result = {}) => ({ + account: await ensureMail2925AccountForFlow({ + allowAllocate: false, + preferredAccountId: account.id, + }), + mail: getMail2925MailConfig(), + result, + }); + + const failMailboxSession = async (message) => { + const stopped = await stopAutoRunForMail2925LoginFailure(`${message}已按手动停止逻辑暂停自动流程。`); + if (stopped) { + throw new Error('流程已被用户停止。'); + } + throw new Error(message); + }; + + 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(); + const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL; + await addLog( + forceRelogin + ? `2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(forceRelogin=true)` + : `2925:准备打开邮箱页 ${MAIL2925_URL}(登录页自动登录=${allowLoginWhenOnLoginPage ? '开启' : '关闭'})`, + 'info' + ); + const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, { + inject: MAIL2925_INJECT, + injectSource: MAIL2925_INJECT_SOURCE, + }); + + let openedUrl = await getMail2925TabUrlById(tabId); + if (!openedUrl) { + openedUrl = await getMail2925CurrentTabUrl(); + } + await addLog(`2925:打开页后当前标签地址:${openedUrl || 'unknown'}`, 'info'); + + if (forceRelogin && typeof waitForTabUrlMatch === 'function') { + const matchedLoginTab = await waitForTabUrlMatch( + tabId, + (url) => isMail2925LoginUrl(url), + { timeoutMs: 15000, retryDelayMs: 300 } + ); + await addLog(`2925:等待最终落到登录页结果:${matchedLoginTab?.url || 'timeout'}`, matchedLoginTab ? 'info' : 'warn'); + if (matchedLoginTab?.url) { + openedUrl = String(matchedLoginTab.url || '').trim(); + } + if (matchedLoginTab && typeof ensureContentScriptReadyOnTab === 'function') { + await ensureContentScriptReadyOnTab(MAIL2925_SOURCE, tabId, { + inject: MAIL2925_INJECT, + injectSource: MAIL2925_INJECT_SOURCE, + timeoutMs: 20000, + retryDelayMs: 800, + logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...', + }); + } + } + + if (!forceRelogin && !isMail2925LoginUrl(openedUrl)) { + await addLog('2925:当前邮箱页未跳转到登录页,将直接复用已登录会话。', 'info'); + return buildSuccessPayload({ + loggedIn: true, + currentView: 'mailbox', + currentUrl: openedUrl, + usedExistingSession: true, + }); + } + + if (!forceRelogin && !allowLoginWhenOnLoginPage) { + await failMailboxSession(`2925:${actionLabel}失败,当前页面已跳转到登录页,且当前未启用 2925 账号池,不执行自动登录。`); + } + + if (typeof ensureContentScriptReadyOnTab === 'function') { + await ensureContentScriptReadyOnTab(MAIL2925_SOURCE, tabId, { + inject: MAIL2925_INJECT, + injectSource: MAIL2925_INJECT_SOURCE, + timeoutMs: 20000, + retryDelayMs: 800, + logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...', + }); + } + + if (forceRelogin && typeof sleepWithStop === 'function') { + await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info'); + await sleepWithStop(3000); + } + let result; try { - const beforeSendUrl = await getMail2925CurrentTabUrl(); + const beforeSendUrl = (await getMail2925TabUrlById(tabId)) || await getMail2925CurrentTabUrl(); await addLog(`2925:发送 ENSURE_MAIL2925_SESSION 前当前地址:${beforeSendUrl || 'unknown'}`, 'info'); result = await sendLoginMessage( MAIL2925_SOURCE, @@ -571,13 +531,12 @@ timeoutMs: forceRelogin ? 30000 : 25000, retryDelayMs: 800, responseTimeoutMs: forceRelogin ? 30000 : 25000, - logMessage: `步骤 0:2925 登录页通信异常,正在等待当前页面重新就绪后继续确认登录态...`, + logMessage: '步骤 0:2925 登录页通信异常,正在等待当前页面重新就绪后继续确认登录态...', } ); } catch (err) { - const stopped = await stopAutoRunForMail2925LoginFailure( - `2925:${actionLabel}失败(${getErrorMessage(err) || '20 秒内未进入收件箱'}),已按手动停止逻辑暂停自动流程。` - ); + const message = `2925:${actionLabel}失败(${getErrorMessage(err) || '20 秒内未进入收件箱'})。`; + const stopped = await stopAutoRunForMail2925LoginFailure(`${message}已按手动停止逻辑暂停自动流程。`); if (stopped) { throw new Error('流程已被用户停止。'); } @@ -585,27 +544,13 @@ } if (result?.error) { - const stopped = await stopAutoRunForMail2925LoginFailure( - `2925:${actionLabel}失败(${result.error}),已按手动停止逻辑暂停自动流程。` - ); - if (stopped) { - throw new Error('流程已被用户停止。'); - } - throw new Error(result.error); + await failMailboxSession(`2925:${actionLabel}失败(${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 failMailboxSession(`2925:${actionLabel}失败,登录后仍未进入收件箱。`); } await patchMail2925Account(account.id, { @@ -614,17 +559,10 @@ }); await setState({ currentMail2925AccountId: account.id }); broadcastDataUpdate({ currentMail2925AccountId: account.id }); - const finalUrl = await getMail2925CurrentTabUrl(); + const finalUrl = (await getMail2925TabUrlById(tabId)) || await getMail2925CurrentTabUrl(); await addLog(`2925:登录态确认成功,当前地址=${finalUrl || 'unknown'}`, 'ok'); - return { - account: await ensureMail2925AccountForFlow({ - allowAllocate: false, - preferredAccountId: account.id, - }), - mail: getMail2925MailConfig(), - result, - }; + return buildSuccessPayload(result); } async function handleMail2925LimitReachedError(step, error) { @@ -635,7 +573,7 @@ if (!currentAccount) { if (typeof requestStop === 'function') { await requestStop({ - logMessage: `步骤 ${step}:2925 检测到“${reason}”,且当前没有可识别账号,已按手动停止逻辑暂停流程。`, + logMessage: `步骤 ${step}:2925 检测到“${reason}”,但当前没有可识别的账号可供处理。`, }); } return new Error('流程已被用户停止。'); @@ -648,7 +586,7 @@ lastError: reason, }); await addLog( - `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,已禁用 24 小时,恢复时间 ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })}。`, + `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,已禁用到 ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })}。`, 'warn' ); @@ -663,7 +601,7 @@ broadcastDataUpdate({ currentMail2925AccountId: null }); if (typeof requestStop === 'function') { await requestStop({ - logMessage: `步骤 ${step}:2925 账号 ${currentAccount.email} 已因“${reason}”禁用 24 小时,且当前没有可切换的下一个账号,已按手动停止逻辑暂停流程。`, + logMessage: `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,且当前没有可切换的下一个账号。`, }); } return new Error('流程已被用户停止。'); @@ -673,11 +611,12 @@ await ensureMail2925MailboxSession({ accountId: nextAccount.id, forceRelogin: true, + allowLoginWhenOnLoginPage: true, actionLabel: `步骤 ${step}:切换 2925 账号`, }); - await addLog(`步骤 ${step}:2925 已自动切换到下一个账号 ${nextAccount.email} 并完成登录,当前尝试将直接结束。`, 'warn'); + await addLog(`步骤 ${step}:2925 已切换到下一个账号 ${nextAccount.email}。`, 'warn'); return buildMail2925ThreadTerminatedError( - `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”并已禁用 24 小时,已切换到 ${nextAccount.email},当前尝试结束,等待自动重试进入下一次尝试。` + `步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,已切换到 ${nextAccount.email},当前尝试结束,等待下一次重试。` ); } diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index 9e16ab6..8bf53c4 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -9,7 +9,6 @@ chrome, CLOUDFLARE_TEMP_EMAIL_PROVIDER, confirmCustomVerificationStepBypass, - ensureMail2925MailboxSession, ensureStep8VerificationPageReady, getOAuthFlowRemainingMs, getOAuthFlowStepTimeoutMs, @@ -58,6 +57,28 @@ return String(value || '').trim().toLowerCase(); } + async function focusOrOpenMailTab(mail) { + const alive = await isTabAlive(mail.source); + if (alive) { + if (mail.navigateOnReuse) { + await reuseOrCreateTab(mail.source, mail.url, { + inject: mail.inject, + injectSource: mail.injectSource, + }); + return; + } + + const tabId = await getTabId(mail.source); + await chrome.tabs.update(tabId, { active: true }); + return; + } + + await reuseOrCreateTab(mail.source, mail.url, { + inject: mail.inject, + injectSource: mail.injectSource, + }); + } + async function runStep8Attempt(state) { const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); @@ -111,33 +132,11 @@ || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER ) { await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`); - } else if (mail.provider === '2925') { - if (state?.mail2925UseAccountPool && typeof ensureMail2925MailboxSession === 'function') { - await ensureMail2925MailboxSession({ - accountId: state.currentMail2925AccountId || null, - actionLabel: '步骤 8:确认 2925 邮箱登录态', - }); - } - await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`); } else { await addLog(`步骤 8:正在打开${mail.label}...`); - - const alive = await isTabAlive(mail.source); - if (alive) { - if (mail.navigateOnReuse) { - await reuseOrCreateTab(mail.source, mail.url, { - inject: mail.inject, - injectSource: mail.injectSource, - }); - } else { - const tabId = await getTabId(mail.source); - await chrome.tabs.update(tabId, { active: true }); - } - } else { - await reuseOrCreateTab(mail.source, mail.url, { - inject: mail.inject, - injectSource: mail.injectSource, - }); + await focusOrOpenMailTab(mail); + if (mail.provider === '2925') { + await addLog(`步骤 8:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info'); } } diff --git a/background/steps/fetch-signup-code.js b/background/steps/fetch-signup-code.js index e85b250..6308e6d 100644 --- a/background/steps/fetch-signup-code.js +++ b/background/steps/fetch-signup-code.js @@ -24,15 +24,39 @@ throwIfStopped, } = deps; + async function focusOrOpenMailTab(mail) { + const alive = await isTabAlive(mail.source); + if (alive) { + if (mail.navigateOnReuse) { + await reuseOrCreateTab(mail.source, mail.url, { + inject: mail.inject, + injectSource: mail.injectSource, + }); + return; + } + + const tabId = await getTabId(mail.source); + await chrome.tabs.update(tabId, { active: true }); + return; + } + + await reuseOrCreateTab(mail.source, mail.url, { + inject: mail.inject, + injectSource: mail.injectSource, + }); + } + async function executeStep4(state) { const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); + const stepStartedAt = Date.now(); const verificationFilterAfterTimestamp = mail.provider === '2925' ? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS) : stepStartedAt; const verificationSessionKey = `4:${stepStartedAt}`; const signupTabId = await getTabId('signup-page'); + if (!signupTabId) { throw new Error('认证页面标签页已关闭,无法继续步骤 4。'); } @@ -40,6 +64,7 @@ await chrome.tabs.update(signupTabId, { active: true }); throwIfStopped(); await addLog('步骤 4:正在确认注册验证码页面是否就绪,必要时自动恢复密码页超时报错...'); + const prepareResult = await sendToContentScriptResilient( 'signup-page', { @@ -73,36 +98,28 @@ } throwIfStopped(); - if (mail.provider === HOTMAIL_PROVIDER || mail.provider === LUCKMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) { + if ( + mail.provider === HOTMAIL_PROVIDER + || mail.provider === LUCKMAIL_PROVIDER + || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER + ) { await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`); } else if (mail.provider === '2925') { - if (state?.mail2925UseAccountPool && typeof ensureMail2925MailboxSession === 'function') { + await addLog(`步骤 4:正在打开${mail.label}...`); + if (typeof ensureMail2925MailboxSession === 'function') { await ensureMail2925MailboxSession({ accountId: state.currentMail2925AccountId || null, + forceRelogin: false, + allowLoginWhenOnLoginPage: Boolean(state?.mail2925UseAccountPool), actionLabel: '步骤 4:确认 2925 邮箱登录态', }); + } else { + await focusOrOpenMailTab(mail); } - await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`); + await addLog(`步骤 4:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info'); } else { await addLog(`步骤 4:正在打开${mail.label}...`); - - const alive = await isTabAlive(mail.source); - if (alive) { - if (mail.navigateOnReuse) { - await reuseOrCreateTab(mail.source, mail.url, { - inject: mail.inject, - injectSource: mail.injectSource, - }); - } else { - const tabId = await getTabId(mail.source); - await chrome.tabs.update(tabId, { active: true }); - } - } else { - await reuseOrCreateTab(mail.source, mail.url, { - inject: mail.inject, - injectSource: mail.injectSource, - }); - } + await focusOrOpenMailTab(mail); } await resolveVerificationStep(4, state, mail, { diff --git a/sidepanel/account-pool-ui.js b/sidepanel/account-pool-ui.js new file mode 100644 index 0000000..642b55f --- /dev/null +++ b/sidepanel/account-pool-ui.js @@ -0,0 +1,58 @@ +(function attachSidepanelAccountPoolUi(globalScope) { + function createAccountPoolFormController(options = {}) { + const { + formShell = null, + toggleButton = null, + hiddenLabel = '添加账号', + visibleLabel = '取消添加', + onClear = null, + onFocus = null, + } = options; + + let visible = false; + + function sync() { + if (formShell) { + formShell.hidden = !visible; + } + if (toggleButton) { + toggleButton.textContent = visible ? visibleLabel : hiddenLabel; + toggleButton.setAttribute('aria-expanded', String(visible)); + } + } + + function setVisible(nextVisible, controlOptions = {}) { + const { + clearForm = false, + focusField = false, + } = controlOptions; + + visible = Boolean(nextVisible); + if (clearForm && typeof onClear === 'function') { + onClear(); + } + + sync(); + + if (visible && focusField && typeof onFocus === 'function') { + onFocus(); + } + } + + function isVisible() { + return visible; + } + + sync(); + + return { + isVisible, + setVisible, + sync, + }; + } + + globalScope.SidepanelAccountPoolUi = { + createAccountPoolFormController, + }; +})(window); diff --git a/sidepanel/hotmail-manager.js b/sidepanel/hotmail-manager.js index 2bef025..23fd45b 100644 --- a/sidepanel/hotmail-manager.js +++ b/sidepanel/hotmail-manager.js @@ -12,6 +12,7 @@ const expandedStorageKey = constants.expandedStorageKey || 'multipage-hotmail-list-expanded'; const displayTimeZone = constants.displayTimeZone || 'Asia/Shanghai'; const copyIcon = constants.copyIcon || ''; + const createAccountPoolFormController = globalScope.SidepanelAccountPoolUi?.createAccountPoolFormController; let actionInFlight = false; let listExpanded = false; @@ -177,6 +178,25 @@ dom.inputHotmailRefreshToken.value = ''; } + const formController = typeof createAccountPoolFormController === 'function' + ? createAccountPoolFormController({ + formShell: dom.hotmailFormShell, + toggleButton: dom.btnToggleHotmailForm, + hiddenLabel: '添加账号', + visibleLabel: '取消添加', + onClear: () => { + clearHotmailForm(); + }, + onFocus: () => { + dom.inputHotmailEmail?.focus?.(); + }, + }) + : { + isVisible: () => false, + setVisible() {}, + sync() {}, + }; + function renderHotmailAccounts() { if (!dom.hotmailAccountsList) return; const latestState = state.getLatestState(); @@ -318,7 +338,7 @@ } helpers.showToast(`已保存 Hotmail 账号 ${email}`, 'success', 1800); - clearHotmailForm(); + formController.setVisible(false, { clearForm: true }); } catch (err) { helpers.showToast(`保存 Hotmail 账号失败:${err.message}`, 'error'); } finally { @@ -474,6 +494,14 @@ setHotmailListExpanded(!listExpanded); }); + dom.btnToggleHotmailForm?.addEventListener('click', () => { + if (formController.isVisible()) { + formController.setVisible(false, { clearForm: true }); + return; + } + formController.setVisible(true, { focusField: true }); + }); + dom.btnHotmailUsageGuide?.addEventListener('click', async () => { await helpers.openConfirmModal({ title: '使用教程', @@ -514,6 +542,7 @@ dom.btnAddHotmailAccount?.addEventListener('click', handleAddHotmailAccount); dom.btnImportHotmailAccounts?.addEventListener('click', handleImportHotmailAccounts); dom.hotmailAccountsList?.addEventListener('click', handleAccountListClick); + formController.sync(); } return { diff --git a/sidepanel/mail-2925-manager.js b/sidepanel/mail-2925-manager.js index 0c83b32..078f7df 100644 --- a/sidepanel/mail-2925-manager.js +++ b/sidepanel/mail-2925-manager.js @@ -12,11 +12,11 @@ const expandedStorageKey = constants.expandedStorageKey || 'multipage-mail2925-list-expanded'; const displayTimeZone = constants.displayTimeZone || 'Asia/Shanghai'; const copyIcon = constants.copyIcon || ''; + const createAccountPoolFormController = globalScope.SidepanelAccountPoolUi?.createAccountPoolFormController; let actionInFlight = false; let listExpanded = false; let editingAccountId = ''; - let formVisible = false; function getMail2925Accounts(currentState = state.getLatestState()) { return helpers.getMail2925Accounts(currentState); @@ -119,34 +119,24 @@ if (dom.inputMail2925Password) dom.inputMail2925Password.value = ''; } - function syncMail2925FormUi() { - if (dom.mail2925FormShell) { - dom.mail2925FormShell.hidden = !formVisible; - } - if (dom.btnToggleMail2925Form) { - dom.btnToggleMail2925Form.textContent = formVisible ? '取消添加' : '添加账号'; - dom.btnToggleMail2925Form.setAttribute('aria-expanded', String(formVisible)); - } - } - - function setMail2925FormVisible(visible, options = {}) { - const { - clearForm = false, - focusField = false, - } = options; - - formVisible = Boolean(visible); - if (!formVisible && clearForm) { - stopEditingAccount({ clearForm: true }); - } else if (clearForm) { - clearMail2925Form(); - } - - syncMail2925FormUi(); - if (formVisible && focusField) { - dom.inputMail2925Email?.focus?.(); - } - } + const formController = typeof createAccountPoolFormController === 'function' + ? createAccountPoolFormController({ + formShell: dom.mail2925FormShell, + toggleButton: dom.btnToggleMail2925Form, + hiddenLabel: '添加账号', + visibleLabel: '取消添加', + onClear: () => { + stopEditingAccount({ clearForm: true }); + }, + onFocus: () => { + dom.inputMail2925Email?.focus?.(); + }, + }) + : { + isVisible: () => false, + setVisible() {}, + sync() {}, + }; function syncEditUi() { if (dom.btnAddMail2925Account) { @@ -159,7 +149,7 @@ editingAccountId = account.id; if (dom.inputMail2925Email) dom.inputMail2925Email.value = String(account.email || '').trim(); if (dom.inputMail2925Password) dom.inputMail2925Password.value = String(account.password || ''); - setMail2925FormVisible(true, { focusField: false }); + formController.setVisible(true, { focusField: false }); syncEditUi(); } @@ -262,7 +252,7 @@ } applyMail2925AccountMutation(response.account); - setMail2925FormVisible(false, { clearForm: true }); + formController.setVisible(false, { clearForm: true }); helpers.showToast( updatingExisting ? `已更新 2925 账号 ${email}` @@ -360,7 +350,7 @@ mail2925Accounts: [], currentMail2925AccountId: null, }); - setMail2925FormVisible(false, { clearForm: true }); + formController.setVisible(false, { clearForm: true }); refreshManagedAliasBaseEmail(); renderMail2925Accounts(); helpers.showToast(`已删除全部 ${response.deletedCount || 0} 个 2925 账号`, 'success', 2200); @@ -488,7 +478,7 @@ } state.syncLatestState(nextState); if (editingAccountId === accountId) { - setMail2925FormVisible(false, { clearForm: true }); + formController.setVisible(false, { clearForm: true }); } refreshManagedAliasBaseEmail(); renderMail2925Accounts(); @@ -508,11 +498,11 @@ }); dom.btnToggleMail2925Form?.addEventListener('click', () => { - if (formVisible) { - setMail2925FormVisible(false, { clearForm: true }); + if (formController.isVisible()) { + formController.setVisible(false, { clearForm: true }); return; } - setMail2925FormVisible(true, { clearForm: !editingAccountId, focusField: true }); + formController.setVisible(true, { clearForm: !editingAccountId, focusField: true }); }); dom.btnDeleteAllMail2925Accounts?.addEventListener('click', async () => { @@ -532,7 +522,7 @@ dom.btnImportMail2925Accounts?.addEventListener('click', handleImportMail2925Accounts); dom.mail2925AccountsList?.addEventListener('click', handleAccountListClick); syncEditUi(); - syncMail2925FormUi(); + formController.sync(); } return { diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index 575b9cd..e40671b 100644 --- a/sidepanel/sidepanel.css +++ b/sidepanel/sidepanel.css @@ -1112,17 +1112,17 @@ header { visibility: hidden; } -.mail2925-form-shell { +.account-pool-form-shell { display: flex; flex-direction: column; gap: 10px; } -.mail2925-actions-inline { +.account-pool-actions-inline { width: 100%; } -.mail2925-import-action { +.account-pool-import-action { margin-left: auto; } @@ -1148,7 +1148,7 @@ header { } .hotmail-list-shell.is-collapsed { - max-height: 236px; + max-height: 176px; } .hotmail-list-shell.is-expanded { diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 1d71932..2f7f26c 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -389,6 +389,7 @@ Hotmail 账号池