diff --git a/background/mail-2925-session.js b/background/mail-2925-session.js index 5d73f83..2c9f900 100644 --- a/background/mail-2925-session.js +++ b/background/mail-2925-session.js @@ -446,7 +446,7 @@ const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL; await addLog( forceRelogin - ? `2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(forceRelogin=true)` + ? `2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(强制重登录)` : `2925:准备打开邮箱页 ${MAIL2925_URL}(登录页自动登录=${allowLoginWhenOnLoginPage ? '开启' : '关闭'})`, 'info' ); @@ -467,7 +467,7 @@ (url) => isMail2925LoginUrl(url), { timeoutMs: 15000, retryDelayMs: 300 } ); - await addLog(`2925:等待最终落到登录页结果:${matchedLoginTab?.url || 'timeout'}`, matchedLoginTab ? 'info' : 'warn'); + await addLog(`2925:等待最终落到登录页结果:${matchedLoginTab?.url || '超时'}`, matchedLoginTab ? 'info' : 'warn'); if (matchedLoginTab?.url) { openedUrl = String(matchedLoginTab.url || '').trim(); } diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index f99ba4c..a11c81d 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -376,7 +376,10 @@ async function syncSelectedMail2925PoolAccount(options = {}) { throw new Error(response.error); } - syncLatestState({ currentMail2925AccountId: response.account?.id || accountId }); + syncLatestState({ + currentMail2925AccountId: response.account?.id || accountId, + ...(response.account?.email ? { mail2925BaseEmail: String(response.account.email).trim() } : {}), + }); setManagedAliasBaseEmailInputForProvider('2925', latestState); if (!silent) { showToast(`已切换当前 2925 号池邮箱为 ${response.account?.email || accountId}`, 'success', 1800); @@ -2430,6 +2433,24 @@ function getCurrentMail2925Email(state = latestState) { return String(getCurrentMail2925Account(state)?.email || '').trim(); } +function syncMail2925BaseEmailFromCurrentAccount(state = latestState, options = {}) { + const { persist = false } = options; + if (!isMail2925AccountPoolEnabled(state)) { + return false; + } + + const currentEmail = getCurrentMail2925Email(state); + if (!currentEmail || currentEmail === String(state?.mail2925BaseEmail || '').trim()) { + return false; + } + + syncLatestState({ mail2925BaseEmail: currentEmail }); + if (persist) { + saveSettings({ silent: true }).catch(() => {}); + } + return true; +} + function getCurrentLuckmailPurchase(state = latestState) { return state?.currentLuckmailPurchase || null; } @@ -3158,6 +3179,7 @@ const mail2925Manager = window.SidepanelMail2925Manager?.createMail2925Manager({ getMail2925Accounts, openConfirmModal, refreshManagedAliasBaseEmail: () => { + syncMail2925BaseEmailFromCurrentAccount(latestState, { persist: true }); setManagedAliasBaseEmailInputForProvider('2925', latestState); }, showToast, diff --git a/tests/sidepanel-mail2925-base-email.test.js b/tests/sidepanel-mail2925-base-email.test.js new file mode 100644 index 0000000..340534d --- /dev/null +++ b/tests/sidepanel-mail2925-base-email.test.js @@ -0,0 +1,144 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8'); + +function extractFunction(name) { + const markers = [`async function ${name}(`, `function ${name}(`]; + const start = markers + .map((marker) => sidepanelSource.indexOf(marker)) + .find((index) => index >= 0); + if (start < 0) { + throw new Error(`missing function ${name}`); + } + + let parenDepth = 0; + let signatureEnded = false; + let braceStart = -1; + for (let i = start; i < sidepanelSource.length; i += 1) { + const ch = sidepanelSource[i]; + if (ch === '(') { + parenDepth += 1; + } else if (ch === ')') { + parenDepth -= 1; + if (parenDepth === 0) { + signatureEnded = true; + } + } else if (ch === '{' && signatureEnded) { + braceStart = i; + break; + } + } + + let depth = 0; + let end = braceStart; + for (; end < sidepanelSource.length; end += 1) { + const ch = sidepanelSource[end]; + if (ch === '{') depth += 1; + if (ch === '}') { + depth -= 1; + if (depth === 0) { + end += 1; + break; + } + } + } + + return sidepanelSource.slice(start, end); +} + +test('syncSelectedMail2925PoolAccount writes selected pool email back to mail2925BaseEmail', async () => { + const bundle = [ + extractFunction('getMail2925Accounts'), + extractFunction('getCurrentMail2925Account'), + extractFunction('getCurrentMail2925Email'), + extractFunction('isMail2925AccountPoolEnabled'), + extractFunction('syncMail2925PoolAccountOptions'), + extractFunction('getPreferredMail2925PoolAccountId'), + extractFunction('syncSelectedMail2925PoolAccount'), + ].join('\n'); + + const api = new Function(` +let latestState = { + mail2925UseAccountPool: true, + mail2925BaseEmail: 'old@2925.com', + currentMail2925AccountId: '', + mail2925Accounts: [{ id: 'acc-1', email: 'new@2925.com' }], +}; +const selectMail2925PoolAccount = { value: 'acc-1', innerHTML: '' }; +const chrome = { + runtime: { + async sendMessage() { + return { account: { id: 'acc-1', email: 'new@2925.com' } }; + }, + }, +}; +const toastEvents = []; +function syncLatestState(patch) { + latestState = { ...latestState, ...patch }; +} +function setManagedAliasBaseEmailInputForProvider() {} +function showToast(message) { + toastEvents.push(message); +} +function escapeHtml(value) { + return String(value || ''); +} +${bundle} +return { + syncSelectedMail2925PoolAccount, + getLatestState() { + return latestState; + }, +}; +`)(); + + await api.syncSelectedMail2925PoolAccount({ silent: true }); + + assert.equal(api.getLatestState().currentMail2925AccountId, 'acc-1'); + assert.equal(api.getLatestState().mail2925BaseEmail, 'new@2925.com'); +}); + +test('syncMail2925BaseEmailFromCurrentAccount reuses current pool account email for manual base email field', async () => { + const bundle = [ + extractFunction('getMail2925Accounts'), + extractFunction('getCurrentMail2925Account'), + extractFunction('getCurrentMail2925Email'), + extractFunction('isMail2925AccountPoolEnabled'), + extractFunction('syncMail2925BaseEmailFromCurrentAccount'), + ].join('\n'); + + const api = new Function(` +let latestState = { + mail2925UseAccountPool: true, + mail2925BaseEmail: 'old@2925.com', + currentMail2925AccountId: 'acc-1', + mail2925Accounts: [{ id: 'acc-1', email: 'new@2925.com' }], +}; +let saveCalls = 0; +function syncLatestState(patch) { + latestState = { ...latestState, ...patch }; +} +async function saveSettings() { + saveCalls += 1; +} +${bundle} +return { + syncMail2925BaseEmailFromCurrentAccount, + getLatestState() { + return latestState; + }, + getSaveCalls() { + return saveCalls; + }, +}; +`)(); + + const changed = api.syncMail2925BaseEmailFromCurrentAccount(undefined, { persist: true }); + await Promise.resolve(); + + assert.equal(changed, true); + assert.equal(api.getLatestState().mail2925BaseEmail, 'new@2925.com'); + assert.equal(api.getSaveCalls(), 1); +}); diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 08da8df..4567a48 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -560,6 +560,7 @@ 5. 如果登录提交后 20 秒内仍未进入收件箱,且当前正处于自动运行中,则后台会直接复用现有 `requestStop()` 停止链路,把整个自动流程停成和用户手动点击“停止”一致的状态;这类情况常见于图片验证、行为验证或其他阻断登录的中间页 6. 如果没有下一个可用账号,或当前未启用号池模式,则不会继续消耗自动重试次数,而是直接复用现有 `requestStop()` 停止链路,把整个自动流程停成和用户手动点击“停止”一致的状态 7. sidepanel 中 2925 账号池的新增表单也走与 Hotmail 相同的共享交互:默认收起,头部按钮切换“添加账号 / 取消添加”,操作行右侧提供“批量导入”,保存成功后自动收起并清空。 +8. 当 2925 号池模式开启时,当前选中的号池邮箱会同步回写到同一个 `mail2925BaseEmail` 字段;因此用户切换号池账号后,即使再次关闭号池模式,也会直接沿用刚才选中的邮箱作为手动基邮箱,无需重新输入。 ### 7.5 iCloud