-
批量导入
-
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index 46a6f73..f809d69 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -158,10 +158,11 @@ const inputMail2925Email = document.getElementById('input-mail2925-email');
const inputMail2925Password = document.getElementById('input-mail2925-password');
const inputMail2925Import = document.getElementById('input-mail2925-import');
const btnAddMail2925Account = document.getElementById('btn-add-mail2925-account');
-const btnCancelMail2925Edit = document.getElementById('btn-cancel-mail2925-edit');
+const btnToggleMail2925Form = document.getElementById('btn-toggle-mail2925-form');
const btnImportMail2925Accounts = document.getElementById('btn-import-mail2925-accounts');
const btnDeleteAllMail2925Accounts = document.getElementById('btn-delete-all-mail2925-accounts');
const btnToggleMail2925List = document.getElementById('btn-toggle-mail2925-list');
+const mail2925FormShell = document.getElementById('mail2925-form-shell');
const mail2925ListShell = document.getElementById('mail2925-list-shell');
const mail2925AccountsList = document.getElementById('mail2925-accounts-list');
const inputLuckmailApiKey = document.getElementById('input-luckmail-api-key');
@@ -3136,14 +3137,15 @@ const mail2925Manager = window.SidepanelMail2925Manager?.createMail2925Manager({
},
dom: {
btnAddMail2925Account,
- btnCancelMail2925Edit,
btnDeleteAllMail2925Accounts,
btnImportMail2925Accounts,
+ btnToggleMail2925Form,
btnToggleMail2925List,
inputMail2925Email,
inputMail2925Import,
inputMail2925Password,
mail2925AccountsList,
+ mail2925FormShell,
mail2925ListShell,
},
helpers: {
diff --git a/tests/sidepanel-mail2925-edit.test.js b/tests/sidepanel-mail2925-edit.test.js
index 99221f8..f7dac78 100644
--- a/tests/sidepanel-mail2925-edit.test.js
+++ b/tests/sidepanel-mail2925-edit.test.js
@@ -2,9 +2,11 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
-test('sidepanel html contains cancel edit button for mail2925 form', () => {
+test('sidepanel html contains collapsible mail2925 form controls', () => {
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
- assert.match(html, /id="btn-cancel-mail2925-edit"/);
+ assert.match(html, /id="btn-toggle-mail2925-form"/);
+ assert.match(html, /id="mail2925-form-shell"/);
+ assert.doesNotMatch(html, /id="btn-cancel-mail2925-edit"/);
});
test('mail2925 manager renders edit action for existing accounts', () => {
@@ -43,14 +45,15 @@ test('mail2925 manager renders edit action for existing accounts', () => {
},
dom: {
btnAddMail2925Account: { textContent: '', disabled: false, addEventListener() {} },
- btnCancelMail2925Edit: { style: { display: 'none' }, addEventListener() {} },
btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
+ btnToggleMail2925Form: { textContent: '', setAttribute() {}, addEventListener() {} },
btnToggleMail2925List: { textContent: '', disabled: false, setAttribute() {}, addEventListener() {} },
inputMail2925Email: { value: '' },
inputMail2925Import: { value: '' },
inputMail2925Password: { value: '' },
mail2925AccountsList,
+ mail2925FormShell: { hidden: true },
mail2925ListShell: { classList: { toggle() {} } },
},
helpers: {
diff --git a/tests/sidepanel-mail2925-manager.test.js b/tests/sidepanel-mail2925-manager.test.js
index 40d2efa..a51d061 100644
--- a/tests/sidepanel-mail2925-manager.test.js
+++ b/tests/sidepanel-mail2925-manager.test.js
@@ -17,7 +17,9 @@ test('sidepanel html contains mail2925 pool toggle and selector controls', () =>
assert.match(html, /id="input-mail2925-use-account-pool"/);
assert.match(html, /id="select-mail2925-pool-account"/);
- assert.match(html, /id="btn-cancel-mail2925-edit"/);
+ assert.match(html, /id="btn-toggle-mail2925-form"/);
+ assert.match(html, /id="mail2925-form-shell"/);
+ assert.doesNotMatch(html, /id="btn-cancel-mail2925-edit"/);
});
test('mail2925 manager exposes a factory and renders empty state', () => {
@@ -38,6 +40,12 @@ test('mail2925 manager exposes a factory and renders empty state', () => {
assert.equal(typeof api?.createMail2925Manager, 'function');
const mail2925AccountsList = { innerHTML: '', addEventListener() {} };
+ const formToggleButton = {
+ textContent: '',
+ disabled: false,
+ setAttribute() {},
+ addEventListener() {},
+ };
const toggleButton = {
textContent: '',
disabled: false,
@@ -55,11 +63,13 @@ test('mail2925 manager exposes a factory and renders empty state', () => {
btnAddMail2925Account: { disabled: false, addEventListener() {} },
btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
+ btnToggleMail2925Form: formToggleButton,
btnToggleMail2925List: toggleButton,
inputMail2925Email: { value: '' },
inputMail2925Import: { value: '' },
inputMail2925Password: { value: '' },
mail2925AccountsList,
+ mail2925FormShell: { hidden: true },
mail2925ListShell: { classList: noopClassList },
},
helpers: {
@@ -88,3 +98,188 @@ test('mail2925 manager exposes a factory and renders empty state', () => {
manager.renderMail2925Accounts();
assert.match(mail2925AccountsList.innerHTML, /还没有 2925 账号/);
});
+
+test('mail2925 manager toggles form container from header button', () => {
+ const source = fs.readFileSync('sidepanel/mail-2925-manager.js', 'utf8');
+ const windowObject = {};
+ const localStorageMock = {
+ getItem() {
+ return null;
+ },
+ setItem() {},
+ };
+
+ const api = new Function('window', 'localStorage', `${source}; return window.SidepanelMail2925Manager;`)(
+ windowObject,
+ localStorageMock
+ );
+
+ const clickHandlers = {};
+ const formToggleButton = {
+ textContent: '',
+ disabled: false,
+ setAttribute(name, value) {
+ this[name] = value;
+ },
+ addEventListener(type, handler) {
+ clickHandlers[type] = handler;
+ },
+ };
+ const formShell = { hidden: true };
+
+ const manager = api.createMail2925Manager({
+ state: {
+ getLatestState: () => ({ currentMail2925AccountId: null, mail2925Accounts: [] }),
+ syncLatestState() {},
+ },
+ dom: {
+ btnAddMail2925Account: { textContent: '', disabled: false, addEventListener() {} },
+ btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
+ btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
+ btnToggleMail2925Form: formToggleButton,
+ btnToggleMail2925List: { textContent: '', disabled: false, setAttribute() {}, addEventListener() {} },
+ inputMail2925Email: { value: '', focus() { this.focused = true; } },
+ inputMail2925Import: { value: '' },
+ inputMail2925Password: { value: '' },
+ mail2925AccountsList: { innerHTML: '', addEventListener() {} },
+ mail2925FormShell: formShell,
+ mail2925ListShell: { classList: { toggle() {} } },
+ },
+ helpers: {
+ getMail2925Accounts: () => [],
+ escapeHtml: (value) => String(value || ''),
+ showToast() {},
+ openConfirmModal: async () => true,
+ copyTextToClipboard: async () => {},
+ refreshManagedAliasBaseEmail() {},
+ },
+ runtime: {
+ sendMessage: async () => ({}),
+ },
+ constants: {
+ copyIcon: '',
+ displayTimeZone: 'Asia/Shanghai',
+ expandedStorageKey: 'multipage-mail2925-list-expanded',
+ },
+ mail2925Utils: {},
+ });
+
+ manager.bindMail2925Events();
+ assert.equal(formShell.hidden, true);
+ assert.equal(formToggleButton.textContent, '添加账号');
+
+ clickHandlers.click();
+ assert.equal(formShell.hidden, false);
+ assert.equal(formToggleButton.textContent, '取消添加');
+
+ clickHandlers.click();
+ assert.equal(formShell.hidden, true);
+ assert.equal(formToggleButton.textContent, '添加账号');
+});
+
+test('mail2925 manager hides form after save succeeds', async () => {
+ const source = fs.readFileSync('sidepanel/mail-2925-manager.js', 'utf8');
+ const windowObject = {};
+ const localStorageMock = {
+ getItem() {
+ return null;
+ },
+ setItem() {},
+ };
+
+ const api = new Function('window', 'localStorage', `${source}; return window.SidepanelMail2925Manager;`)(
+ windowObject,
+ localStorageMock
+ );
+
+ let latestState = { currentMail2925AccountId: null, mail2925Accounts: [] };
+ const handlers = {};
+ const formToggleButton = {
+ textContent: '',
+ disabled: false,
+ setAttribute() {},
+ addEventListener(type, handler) {
+ if (type === 'click') handlers.toggle = handler;
+ },
+ };
+ const addButton = {
+ textContent: '',
+ disabled: false,
+ addEventListener(type, handler) {
+ if (type === 'click') handlers.add = handler;
+ },
+ };
+ const formShell = { hidden: true };
+ const inputMail2925Email = { value: '', focus() {} };
+ const inputMail2925Password = { value: '' };
+ const toastMessages = [];
+
+ const manager = api.createMail2925Manager({
+ state: {
+ getLatestState: () => latestState,
+ syncLatestState(patch) {
+ latestState = { ...latestState, ...patch };
+ },
+ },
+ dom: {
+ btnAddMail2925Account: addButton,
+ btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
+ btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
+ btnToggleMail2925Form: formToggleButton,
+ btnToggleMail2925List: { textContent: '', disabled: false, setAttribute() {}, addEventListener() {} },
+ inputMail2925Email,
+ inputMail2925Import: { value: '' },
+ inputMail2925Password,
+ mail2925AccountsList: { innerHTML: '', addEventListener() {} },
+ mail2925FormShell: formShell,
+ mail2925ListShell: { classList: { toggle() {} } },
+ },
+ helpers: {
+ getMail2925Accounts: (state) => state.mail2925Accounts || [],
+ escapeHtml: (value) => String(value || ''),
+ showToast(message) {
+ toastMessages.push(message);
+ },
+ openConfirmModal: async () => true,
+ copyTextToClipboard: async () => {},
+ refreshManagedAliasBaseEmail() {},
+ },
+ runtime: {
+ sendMessage: async () => ({
+ account: {
+ id: 'acc-1',
+ email: 'demo@2925.com',
+ password: 'secret',
+ enabled: true,
+ lastLoginAt: 0,
+ lastUsedAt: 0,
+ lastLimitAt: 0,
+ disabledUntil: 0,
+ lastError: '',
+ },
+ }),
+ },
+ constants: {
+ copyIcon: '',
+ displayTimeZone: 'Asia/Shanghai',
+ expandedStorageKey: 'multipage-mail2925-list-expanded',
+ },
+ mail2925Utils: {
+ upsertMail2925AccountInList: (accounts, nextAccount) => accounts.concat(nextAccount),
+ },
+ });
+
+ manager.bindMail2925Events();
+ handlers.toggle();
+ inputMail2925Email.value = 'demo@2925.com';
+ inputMail2925Password.value = 'secret';
+
+ await handlers.add();
+
+ assert.equal(formShell.hidden, true);
+ assert.equal(formToggleButton.textContent, '添加账号');
+ assert.equal(addButton.textContent, '添加账号');
+ assert.equal(inputMail2925Email.value, '');
+ assert.equal(inputMail2925Password.value, '');
+ assert.match(toastMessages.at(-1) || '', /已保存 2925 账号/);
+});
diff --git a/tests/step6-login-state.test.js b/tests/step6-login-state.test.js
index 324f07f..2cfbb55 100644
--- a/tests/step6-login-state.test.js
+++ b/tests/step6-login-state.test.js
@@ -146,6 +146,27 @@ return {
assert.strictEqual(snapshot.displayedEmail, 'display.user@example.com');
}
+{
+ const api = createApi({
+ pathname: '/email-verification',
+ retryState: {
+ retryEnabled: true,
+ titleMatched: false,
+ detailMatched: false,
+ routeErrorMatched: true,
+ },
+ verificationTarget: { id: 'otp' },
+ verificationVisible: true,
+ });
+
+ const snapshot = api.inspectLoginAuthState();
+ assert.strictEqual(
+ snapshot.state,
+ 'login_timeout_error_page',
+ '第七步在 /email-verification 的登录重试页应优先识别为登录超时报错页'
+ );
+}
+
{
const api = createApi({
oauthConsentPage: true,
diff --git a/tests/step6-timeout-recovery.test.js b/tests/step6-timeout-recovery.test.js
index b93247a..ca741eb 100644
--- a/tests/step6-timeout-recovery.test.js
+++ b/tests/step6-timeout-recovery.test.js
@@ -103,3 +103,111 @@ return {
assert.equal(result.state, 'login_timeout_error_page');
assert.equal(result.message, '当前页面处于登录超时报错页。');
});
+
+test('step 7 finalize converts verification page that falls into retry page into recoverable result', async () => {
+ const api = new Function(`
+const logs = [];
+let recoverCalls = 0;
+let currentState = 'verification_page';
+
+const location = {
+ href: 'https://auth.openai.com/email-verification',
+};
+
+function inspectLoginAuthState() {
+ return {
+ state: currentState,
+ url: location.href,
+ };
+}
+
+async function recoverCurrentAuthRetryPage() {
+ recoverCalls += 1;
+ return { recovered: true };
+}
+
+async function sleep() {
+ currentState = 'login_timeout_error_page';
+}
+
+function log(message, level = 'info') {
+ logs.push({ message, level });
+}
+
+function throwIfStopped() {}
+
+function getLoginAuthStateLabel(snapshot) {
+ switch (snapshot?.state) {
+ case 'verification_page':
+ return '登录验证码页';
+ case 'login_timeout_error_page':
+ return '登录超时报错页';
+ default:
+ return '未知页面';
+ }
+}
+
+${extractFunction('createStep6SuccessResult')}
+${extractFunction('createStep6RecoverableResult')}
+${extractFunction('normalizeStep6Snapshot')}
+${extractFunction('createStep6LoginTimeoutRecoverableResult')}
+${extractFunction('finalizeStep6VerificationReady')}
+
+return {
+ async run() {
+ return finalizeStep6VerificationReady({
+ logLabel: '步骤 7 收尾',
+ loginVerificationRequestedAt: 123,
+ via: 'password_submit',
+ });
+ },
+ snapshot() {
+ return { logs, recoverCalls };
+ },
+};
+`)();
+
+ const result = await api.run();
+ const snapshot = api.snapshot();
+
+ assert.equal(snapshot.recoverCalls, 1);
+ assert.equal(result.step6Outcome, 'recoverable');
+ assert.equal(result.reason, 'login_timeout_error_page');
+ assert.equal(result.state, 'login_timeout_error_page');
+ assert.equal(result.message, '登录验证码页面准备就绪前进入登录超时报错页。');
+});
+
+test('waitForLoginVerificationPageReady reports login timeout page without step8 restart prefix', async () => {
+ const api = new Function(`
+const location = {
+ href: 'https://auth.openai.com/email-verification',
+};
+
+function inspectLoginAuthState() {
+ return {
+ state: 'login_timeout_error_page',
+ url: location.href,
+ };
+}
+
+function throwIfStopped() {}
+async function sleep() {}
+
+function getLoginAuthStateLabel(snapshot) {
+ return snapshot?.state === 'login_timeout_error_page' ? '登录超时报错页' : '未知页面';
+}
+
+${extractFunction('waitForLoginVerificationPageReady')}
+
+return {
+ run() {
+ return waitForLoginVerificationPageReady(10);
+ },
+};
+`)();
+
+ await assert.rejects(
+ () => api.run(),
+ /当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:登录超时报错页。URL: https:\/\/auth\.openai\.com\/email-verification/
+ );
+});
diff --git a/项目完整链路说明.md b/项目完整链路说明.md
index 0b04547..9062194 100644
--- a/项目完整链路说明.md
+++ b/项目完整链路说明.md
@@ -639,7 +639,10 @@
- Step 4 在等待注册验证码页时,如果命中认证页 `Try again / 重试` 页,或 `/email-verification` 上的 `405 / Route Error` 重试页,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续回到密码页重提和验证码页确认流程。
- 但如果 Step 4 的认证重试页正文中出现 `user_already_exists`,则会直接视为“当前用户已存在”:不点击 `重试`,不再回到步骤 1 重开当前轮,而是立即结束当前轮;开启自动重试时直接进入下一轮。
- Step 7 在识别到登录超时报错页时,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复当前页面;若仍未恢复,则按原有可恢复失败逻辑重跑 Step 7。
+- Step 7 在首次识别到登录验证码页后,不会立刻把步骤判定为成功;还会额外做一轮“收尾确认”,确保页面稳定停留在登录验证码页。如果只是短暂进入验证码页、随后又掉进登录重试页,则会先走共享恢复逻辑,再按既有可恢复失败逻辑重跑 Step 7。
+- Step 7 的这轮收尾确认是主要责任边界;Step 8 默认建立在“登录验证码页已经由 Step 7 稳定确认”的前提上,只在后台入口保留防御性回退判断,不替代 Step 7 收尾。
- Step 8 如果发现认证页已经进入登录超时报错/重试页,会直接报错并回到 Step 7 重新开始,而不是在 Step 8 内部点击 `重试`。
+- Step 8 的登录重试页判定也覆盖 `/email-verification` 上的 `405 / Route Error`,避免这类页面被误当成普通未知页。
- 任意认证页重试页如果正文中出现 `max_check_attempts`,会被视为 Cloudflare 风控触发:后台立刻完全停止流程,侧边栏会复用现有确认弹窗提示等待 15~30 分钟后再试,避免继续刷新或反复重试加重风控,确认按钮显示为“我知道了”。
- Step 9 在点击 OAuth 同意页 `继续` 后,会额外检查是否进入认证页重试页;若命中则先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再重新执行当前轮的 `继续` 点击。
## 2026-04-21 2925 邮件时间窗补充
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 2/7] =?UTF-8?q?fix(mail2925):=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E8=BD=AE=E8=AF=A2=E4=B8=8E=E7=99=BB?=
=?UTF-8?q?=E5=BD=95=E6=80=81=E5=A4=8D=E7=94=A8=E9=80=BB=E8=BE=91=E5=B9=B6?=
=?UTF-8?q?=E6=B8=85=E7=90=86=E4=B8=AD=E6=96=87=E6=96=87=E6=A1=88=EF=BC=8C?=
=?UTF-8?q?=E9=87=8D=E6=9E=84=20sidepanel=EF=BC=9A=E7=BB=9F=E4=B8=80=20Hot?=
=?UTF-8?q?mail=20=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 账号池
+
@@ -411,33 +412,37 @@
本地助手
-
- 邮箱
-
-
-
- 客户端 ID
-
-
-
- 邮箱密码
-
-
-
- 刷新令牌
-
-
-
-
-
-
-
-
批量导入
-
-
-
+
@@ -455,7 +460,7 @@
-