From f0c3dcda62000710669f7a8f3fed58770e7c1703 Mon Sep 17 00:00:00 2001
From: QLHazyCoder <2825305047@qq.com>
Date: Thu, 16 Apr 2026 23:18:27 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E7=9B=B4=E6=8E=A5=E6=92=AC?=
=?UTF-8?q?=20Step=201~9=20=E7=9A=84=E6=A0=B8=E5=BF=83=E7=8A=B6=E6=80=81?=
=?UTF-8?q?=E6=9C=BA=EF=BC=8C=E8=80=8C=E6=98=AF=E5=85=88=E6=8A=8A=E6=9C=80?=
=?UTF-8?q?=E9=80=82=E5=90=88=E7=8B=AC=E7=AB=8B=E7=9A=84=E7=AE=A1=E7=90=86?=
=?UTF-8?q?=E5=99=A8=E5=92=8C=E6=A1=A5=E6=8E=A5=E5=B1=82=E6=8B=86=E5=87=BA?=
=?UTF-8?q?=E6=9D=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
background.js | 426 +----
background/generated-email-helpers.js | 220 +++
background/panel-bridge.js | 145 ++
background/signup-flow-helpers.js | 140 ++
.../2026-04-16-architecture-refactor-plan.md | 189 ++
manifest.json | 2 +-
sidepanel/hotmail-manager.js | 529 ++++++
sidepanel/icloud-manager.js | 502 ++++++
sidepanel/luckmail-manager.js | 467 +++++
sidepanel/sidepanel.html | 3 +
sidepanel/sidepanel.js | 1544 ++---------------
.../background-generated-email-module.test.js | 17 +
tests/background-panel-bridge-module.test.js | 17 +
tests/background-signup-flow-module.test.js | 17 +
tests/sidepanel-hotmail-manager.test.js | 79 +
tests/sidepanel-icloud-manager.test.js | 61 +
tests/sidepanel-luckmail-manager.test.js | 66 +
17 files changed, 2686 insertions(+), 1738 deletions(-)
create mode 100644 background/generated-email-helpers.js
create mode 100644 background/panel-bridge.js
create mode 100644 background/signup-flow-helpers.js
create mode 100644 docs/refactor/2026-04-16-architecture-refactor-plan.md
create mode 100644 sidepanel/hotmail-manager.js
create mode 100644 sidepanel/icloud-manager.js
create mode 100644 sidepanel/luckmail-manager.js
create mode 100644 tests/background-generated-email-module.test.js
create mode 100644 tests/background-panel-bridge-module.test.js
create mode 100644 tests/background-signup-flow-module.test.js
create mode 100644 tests/sidepanel-hotmail-manager.test.js
create mode 100644 tests/sidepanel-icloud-manager.test.js
create mode 100644 tests/sidepanel-luckmail-manager.test.js
diff --git a/background.js b/background.js
index 4cec4a1..1e93f41 100644
--- a/background.js
+++ b/background.js
@@ -1,6 +1,9 @@
// background.js — Service Worker: orchestration, state, tab management, message routing
importScripts(
+ 'background/panel-bridge.js',
+ 'background/generated-email-helpers.js',
+ 'background/signup-flow-helpers.js',
'data/names.js',
'hotmail-utils.js',
'microsoft-email.js',
@@ -5508,186 +5511,52 @@ function getEmailGeneratorLabel(generator) {
if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return 'Cloudflare Temp Email';
return 'Duck 邮箱';
}
+const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGeneratedEmailHelpers({
+ addLog,
+ buildCloudflareTempEmailHeaders,
+ CLOUDFLARE_TEMP_EMAIL_GENERATOR,
+ DUCK_AUTOFILL_URL,
+ fetch,
+ fetchIcloudHideMyEmail,
+ getCloudflareTempEmailAddressFromResponse,
+ getCloudflareTempEmailConfig,
+ getState,
+ joinCloudflareTempEmailUrl,
+ normalizeCloudflareDomain,
+ normalizeCloudflareTempEmailAddress,
+ normalizeEmailGenerator,
+ reuseOrCreateTab,
+ sendToContentScript,
+ setEmailState,
+ throwIfStopped,
+});
function generateCloudflareAliasLocalPart() {
- const letters = 'abcdefghijklmnopqrstuvwxyz';
- const digits = '0123456789';
- const chars = [];
-
- for (let i = 0; i < 6; i++) {
- chars.push(letters[Math.floor(Math.random() * letters.length)]);
- }
-
- for (let i = 0; i < 4; i++) {
- chars.push(digits[Math.floor(Math.random() * digits.length)]);
- }
-
- for (let i = chars.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [chars[i], chars[j]] = [chars[j], chars[i]];
- }
-
- return chars.join('');
+ return generatedEmailHelpers.generateCloudflareAliasLocalPart();
}
async function fetchCloudflareEmail(state, options = {}) {
- throwIfStopped();
- const latestState = state || await getState();
- const domain = normalizeCloudflareDomain(latestState.cloudflareDomain);
- if (!domain) {
- throw new Error('Cloudflare 域名为空或格式无效。');
- }
-
- const localPart = String(options.localPart || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
- const aliasEmail = `${localPart}@${domain}`;
-
- await setEmailState(aliasEmail);
- await addLog(`Cloudflare 邮箱:已生成 ${aliasEmail}`, 'ok');
- return aliasEmail;
+ return generatedEmailHelpers.fetchCloudflareEmail(state, options);
}
function ensureCloudflareTempEmailConfig(state, options = {}) {
- const {
- requireAdminAuth = false,
- requireDomain = false,
- } = options;
- const config = getCloudflareTempEmailConfig(state);
- if (!config.baseUrl) {
- throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。');
- }
- if (requireAdminAuth && !config.adminAuth) {
- throw new Error('Cloudflare Temp Email 缺少 Admin Auth。');
- }
- if (requireDomain && !config.domain) {
- throw new Error('Cloudflare Temp Email 域名为空或格式无效。');
- }
- return config;
+ return generatedEmailHelpers.ensureCloudflareTempEmailConfig(state, options);
}
async function requestCloudflareTempEmailJson(config, path, options = {}) {
- const {
- method = 'GET',
- payload,
- searchParams,
- timeoutMs = 20000,
- } = options;
-
- const url = new URL(joinCloudflareTempEmailUrl(config.baseUrl, path));
- if (searchParams && typeof searchParams === 'object') {
- for (const [key, value] of Object.entries(searchParams)) {
- if (value === undefined || value === null || value === '') continue;
- url.searchParams.set(key, String(value));
- }
- }
-
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
-
- let response;
- try {
- response = await fetch(url.toString(), {
- method,
- headers: buildCloudflareTempEmailHeaders(config, {
- json: payload !== undefined,
- }),
- body: payload !== undefined ? JSON.stringify(payload) : undefined,
- signal: controller.signal,
- });
- } catch (err) {
- const errorMessage = err?.name === 'AbortError'
- ? `Cloudflare Temp Email 请求超时(>${Math.round(timeoutMs / 1000)} 秒)`
- : `Cloudflare Temp Email 请求失败:${err.message}`;
- throw new Error(errorMessage);
- } finally {
- clearTimeout(timeoutId);
- }
-
- const text = await response.text();
- let parsed;
- try {
- parsed = text ? JSON.parse(text) : {};
- } catch {
- parsed = text;
- }
-
- if (!response.ok) {
- const payloadError = typeof parsed === 'object' && parsed
- ? (parsed.message || parsed.error || parsed.msg)
- : '';
- throw new Error(`Cloudflare Temp Email 请求失败:${payloadError || text || `HTTP ${response.status}`}`);
- }
-
- return parsed;
+ return generatedEmailHelpers.requestCloudflareTempEmailJson(config, path, options);
}
async function fetchCloudflareTempEmailAddress(state, options = {}) {
- throwIfStopped();
- const latestState = state || await getState();
- const config = ensureCloudflareTempEmailConfig(latestState, {
- requireAdminAuth: true,
- requireDomain: true,
- });
- const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
- const payload = {
- enablePrefix: true,
- name: requestedName,
- domain: config.domain,
- };
- const result = await requestCloudflareTempEmailJson(config, '/admin/new_address', {
- method: 'POST',
- payload,
- });
- const address = normalizeCloudflareTempEmailAddress(getCloudflareTempEmailAddressFromResponse(result));
- if (!address) {
- throw new Error('Cloudflare Temp Email 未返回可用邮箱地址。');
- }
-
- await setEmailState(address);
- await addLog(`Cloudflare Temp Email:已生成 ${address}`, 'ok');
- return address;
+ return generatedEmailHelpers.fetchCloudflareTempEmailAddress(state, options);
}
async function fetchDuckEmail(options = {}) {
- throwIfStopped();
- const { generateNew = true } = options;
-
- await addLog(`Duck 邮箱:正在打开自动填充设置(${generateNew ? '生成新地址' : '复用当前地址'})...`);
- await reuseOrCreateTab('duck-mail', DUCK_AUTOFILL_URL);
-
- const result = await sendToContentScript('duck-mail', {
- type: 'FETCH_DUCK_EMAIL',
- source: 'background',
- payload: { generateNew },
- });
-
- if (result?.error) {
- throw new Error(result.error);
- }
- if (!result?.email) {
- throw new Error('未返回 Duck 邮箱地址。');
- }
-
- await setEmailState(result.email);
- await addLog(`Duck 邮箱:${result.generated ? '已生成' : '已读取'} ${result.email}`, 'ok');
- return result.email;
+ return generatedEmailHelpers.fetchDuckEmail(options);
}
async function fetchGeneratedEmail(state, options = {}) {
- const currentState = state || await getState();
- const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator);
- if (generator === 'custom') {
- throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。');
- }
- if (generator === 'icloud') {
- return fetchIcloudHideMyEmail();
- }
- if (generator === 'cloudflare') {
- return fetchCloudflareEmail(currentState, options);
- }
- if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) {
- return fetchCloudflareTempEmailAddress(currentState, options);
- }
- return fetchDuckEmail(options);
+ return generatedEmailHelpers.fetchGeneratedEmail(state, options);
}
// ============================================================
@@ -6504,219 +6373,67 @@ async function resumeAutoRun() {
const SIGNUP_ENTRY_URL = 'https://chatgpt.com/';
const SIGNUP_PAGE_INJECT_FILES = ['content/utils.js', 'content/signup-page.js'];
+const panelBridge = self.MultiPageBackgroundPanelBridge?.createPanelBridge({
+ chrome,
+ addLog,
+ closeConflictingTabsForSource,
+ ensureContentScriptReadyOnTab,
+ getPanelMode,
+ normalizeSub2ApiUrl,
+ rememberSourceLastUrl,
+ sendToContentScript,
+ sendToContentScriptResilient,
+ waitForTabUrlFamily,
+ DEFAULT_SUB2API_GROUP_NAME,
+ SUB2API_STEP1_RESPONSE_TIMEOUT_MS,
+});
+const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpers({
+ addLog,
+ buildGeneratedAliasEmail,
+ chrome,
+ ensureContentScriptReadyOnTab,
+ ensureHotmailAccountForFlow,
+ ensureLuckmailPurchaseForFlow,
+ getTabId,
+ isGeneratedAliasProvider,
+ isHotmailProvider,
+ isLuckmailProvider,
+ isSignupPasswordPageUrl,
+ isTabAlive,
+ reuseOrCreateTab,
+ sendToContentScriptResilient,
+ setEmailState,
+ SIGNUP_ENTRY_URL,
+ SIGNUP_PAGE_INJECT_FILES,
+ waitForTabUrlMatch,
+});
async function requestOAuthUrlFromPanel(state, options = {}) {
- if (getPanelMode(state) === 'sub2api') {
- return requestSub2ApiOAuthUrl(state, options);
- }
- return requestCpaOAuthUrl(state, options);
+ return panelBridge.requestOAuthUrlFromPanel(state, options);
}
async function requestCpaOAuthUrl(state, options = {}) {
- const { logLabel = 'OAuth 刷新' } = options;
- if (!state.vpsUrl) {
- throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。');
- }
-
- await addLog(`${logLabel}:正在打开 CPA 面板...`);
-
- const injectFiles = ['content/activation-utils.js', 'content/utils.js', 'content/vps-panel.js'];
- await closeConflictingTabsForSource('vps-panel', state.vpsUrl);
-
- const tab = await chrome.tabs.create({ url: state.vpsUrl, active: true });
- const tabId = tab.id;
- await rememberSourceLastUrl('vps-panel', state.vpsUrl);
-
- await addLog(`${logLabel}:CPA 面板已打开,正在等待页面进入目标地址...`);
- const matchedTab = await waitForTabUrlFamily('vps-panel', tabId, state.vpsUrl, {
- timeoutMs: 15000,
- retryDelayMs: 400,
- });
- if (!matchedTab) {
- await addLog(`${logLabel}:CPA 页面尚未完全进入目标地址,继续尝试连接内容脚本...`, 'warn');
- }
-
- await ensureContentScriptReadyOnTab('vps-panel', tabId, {
- inject: injectFiles,
- timeoutMs: 45000,
- retryDelayMs: 900,
- logMessage: `${logLabel}:CPA 面板仍在加载,正在重试连接内容脚本...`,
- });
-
- const result = await sendToContentScriptResilient('vps-panel', {
- type: 'REQUEST_OAUTH_URL',
- source: 'background',
- payload: {
- vpsPassword: state.vpsPassword,
- logStep: 6,
- },
- }, {
- timeoutMs: 30000,
- retryDelayMs: 700,
- logMessage: `${logLabel}:CPA 面板通信未就绪,正在等待页面恢复...`,
- });
-
- if (result?.error) {
- throw new Error(result.error);
- }
- return result || {};
+ return panelBridge.requestCpaOAuthUrl(state, options);
}
async function requestSub2ApiOAuthUrl(state, options = {}) {
- const { logLabel = 'OAuth 刷新' } = options;
- const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl);
- const groupName = (state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME).trim() || DEFAULT_SUB2API_GROUP_NAME;
-
- if (!state.sub2apiEmail) {
- throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。');
- }
- if (!state.sub2apiPassword) {
- throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。');
- }
-
- await addLog(`${logLabel}:正在打开 SUB2API 后台...`);
-
- const injectFiles = ['content/utils.js', 'content/sub2api-panel.js'];
- await closeConflictingTabsForSource('sub2api-panel', sub2apiUrl);
-
- const tab = await chrome.tabs.create({ url: sub2apiUrl, active: true });
- const tabId = tab.id;
- await rememberSourceLastUrl('sub2api-panel', sub2apiUrl);
-
- await addLog(`${logLabel}:SUB2API 页面已打开,正在等待页面进入目标地址...`);
- const matchedTab = await waitForTabUrlFamily('sub2api-panel', tabId, sub2apiUrl, {
- timeoutMs: 15000,
- retryDelayMs: 400,
- });
- if (!matchedTab) {
- await addLog(`${logLabel}:SUB2API 页面尚未稳定,继续尝试连接内容脚本...`, 'warn');
- }
-
- await ensureContentScriptReadyOnTab('sub2api-panel', tabId, {
- inject: injectFiles,
- injectSource: 'sub2api-panel',
- timeoutMs: 45000,
- retryDelayMs: 900,
- logMessage: `${logLabel}:SUB2API 页面仍在加载,正在重试连接内容脚本...`,
- });
-
- const result = await sendToContentScript('sub2api-panel', {
- type: 'REQUEST_OAUTH_URL',
- source: 'background',
- payload: {
- sub2apiUrl,
- sub2apiEmail: state.sub2apiEmail,
- sub2apiPassword: state.sub2apiPassword,
- sub2apiGroupName: groupName,
- logStep: 6,
- },
- }, {
- responseTimeoutMs: SUB2API_STEP1_RESPONSE_TIMEOUT_MS,
- });
-
- if (result?.error) {
- throw new Error(result.error);
- }
- return result || {};
+ return panelBridge.requestSub2ApiOAuthUrl(state, options);
}
async function openSignupEntryTab(step = 1) {
- const tabId = await reuseOrCreateTab('signup-page', SIGNUP_ENTRY_URL, {
- inject: SIGNUP_PAGE_INJECT_FILES,
- injectSource: 'signup-page',
- });
-
- await ensureContentScriptReadyOnTab('signup-page', tabId, {
- inject: SIGNUP_PAGE_INJECT_FILES,
- injectSource: 'signup-page',
- timeoutMs: 45000,
- retryDelayMs: 900,
- logMessage: `步骤 ${step}:ChatGPT 官网仍在加载,正在重试连接内容脚本...`,
- });
-
- return tabId;
+ return signupFlowHelpers.openSignupEntryTab(step);
}
async function ensureSignupEntryPageReady(step = 1) {
- const tabId = await openSignupEntryTab(step);
- const result = await sendToContentScriptResilient('signup-page', {
- type: 'ENSURE_SIGNUP_ENTRY_READY',
- step,
- source: 'background',
- payload: {},
- }, {
- timeoutMs: 20000,
- retryDelayMs: 700,
- logMessage: `步骤 ${step}:官网注册入口正在切换,等待页面恢复...`,
- });
-
- if (result?.error) {
- throw new Error(result.error);
- }
-
- return { tabId, result: result || {} };
+ return signupFlowHelpers.ensureSignupEntryPageReady(step);
}
async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
- const { skipUrlWait = false } = options;
-
- if (!skipUrlWait) {
- const matchedTab = await waitForTabUrlMatch(tabId, (url) => isSignupPasswordPageUrl(url), {
- timeoutMs: 45000,
- retryDelayMs: 300,
- });
- if (!matchedTab) {
- throw new Error('等待进入密码页超时,请检查邮箱提交后页面是否仍停留在官网或邮箱页。');
- }
- }
-
- await ensureContentScriptReadyOnTab('signup-page', tabId, {
- inject: SIGNUP_PAGE_INJECT_FILES,
- injectSource: 'signup-page',
- timeoutMs: 45000,
- retryDelayMs: 900,
- logMessage: `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...`,
- });
-
- const result = await sendToContentScriptResilient('signup-page', {
- type: 'ENSURE_SIGNUP_PASSWORD_PAGE_READY',
- step,
- source: 'background',
- payload: {},
- }, {
- timeoutMs: 20000,
- retryDelayMs: 700,
- logMessage: `步骤 ${step}:认证页正在切换,等待密码页重新就绪...`,
- });
-
- if (result?.error) {
- throw new Error(result.error);
- }
-
- return result || {};
+ return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options);
}
async function resolveSignupEmailForFlow(state) {
- let resolvedEmail = state.email;
- if (isHotmailProvider(state)) {
- const account = await ensureHotmailAccountForFlow({
- allowAllocate: true,
- markUsed: true,
- preferredAccountId: state.currentHotmailAccountId || null,
- });
- resolvedEmail = account.email;
- } else if (isLuckmailProvider(state)) {
- const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
- resolvedEmail = purchase.email_address;
- } else if (isGeneratedAliasProvider(state)) {
- resolvedEmail = buildGeneratedAliasEmail(state);
- }
-
- if (!resolvedEmail) {
- throw new Error('缺少邮箱地址,请先在侧边栏粘贴邮箱。');
- }
-
- return resolvedEmail;
+ return signupFlowHelpers.resolveSignupEmailForFlow(state);
}
// ============================================================
@@ -6735,9 +6452,6 @@ async function executeStep1() {
async function executeStep2(state) {
const resolvedEmail = await resolveSignupEmailForFlow(state);
- if (resolvedEmail !== state.email) {
- await setEmailState(resolvedEmail);
- }
let signupTabId = await getTabId('signup-page');
if (!signupTabId || !(await isTabAlive('signup-page'))) {
diff --git a/background/generated-email-helpers.js b/background/generated-email-helpers.js
new file mode 100644
index 0000000..c070318
--- /dev/null
+++ b/background/generated-email-helpers.js
@@ -0,0 +1,220 @@
+(function attachGeneratedEmailHelpers(root, factory) {
+ root.MultiPageGeneratedEmailHelpers = factory();
+})(typeof self !== 'undefined' ? self : globalThis, function createGeneratedEmailHelpersModule() {
+ function createGeneratedEmailHelpers(deps = {}) {
+ const {
+ addLog,
+ buildCloudflareTempEmailHeaders,
+ CLOUDFLARE_TEMP_EMAIL_GENERATOR,
+ DUCK_AUTOFILL_URL,
+ fetch,
+ fetchIcloudHideMyEmail,
+ getCloudflareTempEmailAddressFromResponse,
+ getCloudflareTempEmailConfig,
+ getState,
+ joinCloudflareTempEmailUrl,
+ normalizeCloudflareDomain,
+ normalizeCloudflareTempEmailAddress,
+ normalizeEmailGenerator,
+ reuseOrCreateTab,
+ sendToContentScript,
+ setEmailState,
+ throwIfStopped,
+ } = deps;
+
+ function generateCloudflareAliasLocalPart() {
+ const letters = 'abcdefghijklmnopqrstuvwxyz';
+ const digits = '0123456789';
+ const chars = [];
+
+ for (let i = 0; i < 6; i++) {
+ chars.push(letters[Math.floor(Math.random() * letters.length)]);
+ }
+
+ for (let i = 0; i < 4; i++) {
+ chars.push(digits[Math.floor(Math.random() * digits.length)]);
+ }
+
+ for (let i = chars.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [chars[i], chars[j]] = [chars[j], chars[i]];
+ }
+
+ return chars.join('');
+ }
+
+ async function fetchCloudflareEmail(state, options = {}) {
+ throwIfStopped();
+ const latestState = state || await getState();
+ const domain = normalizeCloudflareDomain(latestState.cloudflareDomain);
+ if (!domain) {
+ throw new Error('Cloudflare 域名为空或格式无效。');
+ }
+
+ const localPart = String(options.localPart || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
+ const aliasEmail = `${localPart}@${domain}`;
+
+ await setEmailState(aliasEmail);
+ await addLog(`Cloudflare 邮箱:已生成 ${aliasEmail}`, 'ok');
+ return aliasEmail;
+ }
+
+ function ensureCloudflareTempEmailConfig(state, options = {}) {
+ const {
+ requireAdminAuth = false,
+ requireDomain = false,
+ } = options;
+ const config = getCloudflareTempEmailConfig(state);
+ if (!config.baseUrl) {
+ throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。');
+ }
+ if (requireAdminAuth && !config.adminAuth) {
+ throw new Error('Cloudflare Temp Email 缺少 Admin Auth。');
+ }
+ if (requireDomain && !config.domain) {
+ throw new Error('Cloudflare Temp Email 域名为空或格式无效。');
+ }
+ return config;
+ }
+
+ async function requestCloudflareTempEmailJson(config, path, options = {}) {
+ const {
+ method = 'GET',
+ payload,
+ searchParams,
+ timeoutMs = 20000,
+ } = options;
+
+ const url = new URL(joinCloudflareTempEmailUrl(config.baseUrl, path));
+ if (searchParams && typeof searchParams === 'object') {
+ for (const [key, value] of Object.entries(searchParams)) {
+ if (value === undefined || value === null || value === '') continue;
+ url.searchParams.set(key, String(value));
+ }
+ }
+
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
+
+ let response;
+ try {
+ response = await fetch(url.toString(), {
+ method,
+ headers: buildCloudflareTempEmailHeaders(config, {
+ json: payload !== undefined,
+ }),
+ body: payload !== undefined ? JSON.stringify(payload) : undefined,
+ signal: controller.signal,
+ });
+ } catch (err) {
+ const errorMessage = err?.name === 'AbortError'
+ ? `Cloudflare Temp Email 请求超时(>${Math.round(timeoutMs / 1000)} 秒)`
+ : `Cloudflare Temp Email 请求失败:${err.message}`;
+ throw new Error(errorMessage);
+ } finally {
+ clearTimeout(timeoutId);
+ }
+
+ const text = await response.text();
+ let parsed;
+ try {
+ parsed = text ? JSON.parse(text) : {};
+ } catch {
+ parsed = text;
+ }
+
+ if (!response.ok) {
+ const payloadError = typeof parsed === 'object' && parsed
+ ? (parsed.message || parsed.error || parsed.msg)
+ : '';
+ throw new Error(`Cloudflare Temp Email 请求失败:${payloadError || text || `HTTP ${response.status}`}`);
+ }
+
+ return parsed;
+ }
+
+ async function fetchCloudflareTempEmailAddress(state, options = {}) {
+ throwIfStopped();
+ const latestState = state || await getState();
+ const config = ensureCloudflareTempEmailConfig(latestState, {
+ requireAdminAuth: true,
+ requireDomain: true,
+ });
+ const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
+ const payload = {
+ enablePrefix: true,
+ name: requestedName,
+ domain: config.domain,
+ };
+ const result = await requestCloudflareTempEmailJson(config, '/admin/new_address', {
+ method: 'POST',
+ payload,
+ });
+ const address = normalizeCloudflareTempEmailAddress(getCloudflareTempEmailAddressFromResponse(result));
+ if (!address) {
+ throw new Error('Cloudflare Temp Email 未返回可用邮箱地址。');
+ }
+
+ await setEmailState(address);
+ await addLog(`Cloudflare Temp Email:已生成 ${address}`, 'ok');
+ return address;
+ }
+
+ async function fetchDuckEmail(options = {}) {
+ throwIfStopped();
+ const { generateNew = true } = options;
+
+ await addLog(`Duck 邮箱:正在打开自动填充设置(${generateNew ? '生成新地址' : '复用当前地址'})...`);
+ await reuseOrCreateTab('duck-mail', DUCK_AUTOFILL_URL);
+
+ const result = await sendToContentScript('duck-mail', {
+ type: 'FETCH_DUCK_EMAIL',
+ source: 'background',
+ payload: { generateNew },
+ });
+
+ if (result?.error) {
+ throw new Error(result.error);
+ }
+ if (!result?.email) {
+ throw new Error('未返回 Duck 邮箱地址。');
+ }
+
+ await setEmailState(result.email);
+ await addLog(`Duck 邮箱:${result.generated ? '已生成' : '已读取'} ${result.email}`, 'ok');
+ return result.email;
+ }
+
+ async function fetchGeneratedEmail(state, options = {}) {
+ const currentState = state || await getState();
+ const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator);
+ if (generator === 'custom') {
+ throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。');
+ }
+ if (generator === 'icloud') {
+ return fetchIcloudHideMyEmail();
+ }
+ if (generator === 'cloudflare') {
+ return fetchCloudflareEmail(currentState, options);
+ }
+ if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) {
+ return fetchCloudflareTempEmailAddress(currentState, options);
+ }
+ return fetchDuckEmail(options);
+ }
+
+ return {
+ ensureCloudflareTempEmailConfig,
+ fetchCloudflareEmail,
+ fetchCloudflareTempEmailAddress,
+ fetchDuckEmail,
+ fetchGeneratedEmail,
+ generateCloudflareAliasLocalPart,
+ requestCloudflareTempEmailJson,
+ };
+ }
+
+ return {
+ createGeneratedEmailHelpers,
+ };
+});
diff --git a/background/panel-bridge.js b/background/panel-bridge.js
new file mode 100644
index 0000000..61ef8ba
--- /dev/null
+++ b/background/panel-bridge.js
@@ -0,0 +1,145 @@
+(function attachBackgroundPanelBridge(root, factory) {
+ root.MultiPageBackgroundPanelBridge = factory();
+})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPanelBridgeModule() {
+ function createPanelBridge(deps = {}) {
+ const {
+ chrome,
+ addLog,
+ closeConflictingTabsForSource,
+ ensureContentScriptReadyOnTab,
+ getPanelMode,
+ normalizeSub2ApiUrl,
+ rememberSourceLastUrl,
+ sendToContentScript,
+ sendToContentScriptResilient,
+ waitForTabUrlFamily,
+ DEFAULT_SUB2API_GROUP_NAME,
+ SUB2API_STEP1_RESPONSE_TIMEOUT_MS,
+ } = deps;
+
+ async function requestOAuthUrlFromPanel(state, options = {}) {
+ if (getPanelMode(state) === 'sub2api') {
+ return requestSub2ApiOAuthUrl(state, options);
+ }
+ return requestCpaOAuthUrl(state, options);
+ }
+
+ async function requestCpaOAuthUrl(state, options = {}) {
+ const { logLabel = 'OAuth 刷新' } = options;
+ if (!state.vpsUrl) {
+ throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。');
+ }
+
+ await addLog(`${logLabel}:正在打开 CPA 面板...`);
+
+ const injectFiles = ['content/activation-utils.js', 'content/utils.js', 'content/vps-panel.js'];
+ await closeConflictingTabsForSource('vps-panel', state.vpsUrl);
+
+ const tab = await chrome.tabs.create({ url: state.vpsUrl, active: true });
+ const tabId = tab.id;
+ await rememberSourceLastUrl('vps-panel', state.vpsUrl);
+
+ await addLog(`${logLabel}:CPA 面板已打开,正在等待页面进入目标地址...`);
+ const matchedTab = await waitForTabUrlFamily('vps-panel', tabId, state.vpsUrl, {
+ timeoutMs: 15000,
+ retryDelayMs: 400,
+ });
+ if (!matchedTab) {
+ await addLog(`${logLabel}:CPA 页面尚未完全进入目标地址,继续尝试连接内容脚本...`, 'warn');
+ }
+
+ await ensureContentScriptReadyOnTab('vps-panel', tabId, {
+ inject: injectFiles,
+ timeoutMs: 45000,
+ retryDelayMs: 900,
+ logMessage: `${logLabel}:CPA 面板仍在加载,正在重试连接内容脚本...`,
+ });
+
+ const result = await sendToContentScriptResilient('vps-panel', {
+ type: 'REQUEST_OAUTH_URL',
+ source: 'background',
+ payload: {
+ vpsPassword: state.vpsPassword,
+ logStep: 6,
+ },
+ }, {
+ timeoutMs: 30000,
+ retryDelayMs: 700,
+ logMessage: `${logLabel}:CPA 面板通信未就绪,正在等待页面恢复...`,
+ });
+
+ if (result?.error) {
+ throw new Error(result.error);
+ }
+ return result || {};
+ }
+
+ async function requestSub2ApiOAuthUrl(state, options = {}) {
+ const { logLabel = 'OAuth 刷新' } = options;
+ const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl);
+ const groupName = (state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME).trim() || DEFAULT_SUB2API_GROUP_NAME;
+
+ if (!state.sub2apiEmail) {
+ throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。');
+ }
+ if (!state.sub2apiPassword) {
+ throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。');
+ }
+
+ await addLog(`${logLabel}:正在打开 SUB2API 后台...`);
+
+ const injectFiles = ['content/utils.js', 'content/sub2api-panel.js'];
+ await closeConflictingTabsForSource('sub2api-panel', sub2apiUrl);
+
+ const tab = await chrome.tabs.create({ url: sub2apiUrl, active: true });
+ const tabId = tab.id;
+ await rememberSourceLastUrl('sub2api-panel', sub2apiUrl);
+
+ await addLog(`${logLabel}:SUB2API 页面已打开,正在等待页面进入目标地址...`);
+ const matchedTab = await waitForTabUrlFamily('sub2api-panel', tabId, sub2apiUrl, {
+ timeoutMs: 15000,
+ retryDelayMs: 400,
+ });
+ if (!matchedTab) {
+ await addLog(`${logLabel}:SUB2API 页面尚未稳定,继续尝试连接内容脚本...`, 'warn');
+ }
+
+ await ensureContentScriptReadyOnTab('sub2api-panel', tabId, {
+ inject: injectFiles,
+ injectSource: 'sub2api-panel',
+ timeoutMs: 45000,
+ retryDelayMs: 900,
+ logMessage: `${logLabel}:SUB2API 页面仍在加载,正在重试连接内容脚本...`,
+ });
+
+ const result = await sendToContentScript('sub2api-panel', {
+ type: 'REQUEST_OAUTH_URL',
+ source: 'background',
+ payload: {
+ sub2apiUrl,
+ sub2apiEmail: state.sub2apiEmail,
+ sub2apiPassword: state.sub2apiPassword,
+ sub2apiGroupName: groupName,
+ logStep: 6,
+ },
+ }, {
+ responseTimeoutMs: SUB2API_STEP1_RESPONSE_TIMEOUT_MS,
+ });
+
+ if (result?.error) {
+ throw new Error(result.error);
+ }
+ return result || {};
+ }
+
+ return {
+ requestOAuthUrlFromPanel,
+ requestCpaOAuthUrl,
+ requestSub2ApiOAuthUrl,
+ };
+ }
+
+ return {
+ createPanelBridge,
+ };
+});
diff --git a/background/signup-flow-helpers.js b/background/signup-flow-helpers.js
new file mode 100644
index 0000000..6fe97d7
--- /dev/null
+++ b/background/signup-flow-helpers.js
@@ -0,0 +1,140 @@
+(function attachSignupFlowHelpers(root, factory) {
+ root.MultiPageSignupFlowHelpers = factory();
+})(typeof self !== 'undefined' ? self : globalThis, function createSignupFlowHelpersModule() {
+ function createSignupFlowHelpers(deps = {}) {
+ const {
+ addLog,
+ buildGeneratedAliasEmail,
+ chrome,
+ ensureContentScriptReadyOnTab,
+ ensureHotmailAccountForFlow,
+ ensureLuckmailPurchaseForFlow,
+ getTabId,
+ isGeneratedAliasProvider,
+ isHotmailProvider,
+ isLuckmailProvider,
+ isSignupPasswordPageUrl,
+ isTabAlive,
+ reuseOrCreateTab,
+ sendToContentScriptResilient,
+ setEmailState,
+ SIGNUP_ENTRY_URL,
+ SIGNUP_PAGE_INJECT_FILES,
+ waitForTabUrlMatch,
+ } = deps;
+
+ async function openSignupEntryTab(step = 1) {
+ const tabId = await reuseOrCreateTab('signup-page', SIGNUP_ENTRY_URL, {
+ inject: SIGNUP_PAGE_INJECT_FILES,
+ injectSource: 'signup-page',
+ });
+
+ await ensureContentScriptReadyOnTab('signup-page', tabId, {
+ inject: SIGNUP_PAGE_INJECT_FILES,
+ injectSource: 'signup-page',
+ timeoutMs: 45000,
+ retryDelayMs: 900,
+ logMessage: `步骤 ${step}:ChatGPT 官网仍在加载,正在重试连接内容脚本...`,
+ });
+
+ return tabId;
+ }
+
+ async function ensureSignupEntryPageReady(step = 1) {
+ const tabId = await openSignupEntryTab(step);
+ const result = await sendToContentScriptResilient('signup-page', {
+ type: 'ENSURE_SIGNUP_ENTRY_READY',
+ step,
+ source: 'background',
+ payload: {},
+ }, {
+ timeoutMs: 20000,
+ retryDelayMs: 700,
+ logMessage: `步骤 ${step}:官网注册入口正在切换,等待页面恢复...`,
+ });
+
+ if (result?.error) {
+ throw new Error(result.error);
+ }
+
+ return { tabId, result: result || {} };
+ }
+
+ async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
+ const { skipUrlWait = false } = options;
+
+ if (!skipUrlWait) {
+ const matchedTab = await waitForTabUrlMatch(tabId, (url) => isSignupPasswordPageUrl(url), {
+ timeoutMs: 45000,
+ retryDelayMs: 300,
+ });
+ if (!matchedTab) {
+ throw new Error('等待进入密码页超时,请检查邮箱提交后页面是否仍停留在官网或邮箱页。');
+ }
+ }
+
+ await ensureContentScriptReadyOnTab('signup-page', tabId, {
+ inject: SIGNUP_PAGE_INJECT_FILES,
+ injectSource: 'signup-page',
+ timeoutMs: 45000,
+ retryDelayMs: 900,
+ logMessage: `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...`,
+ });
+
+ const result = await sendToContentScriptResilient('signup-page', {
+ type: 'ENSURE_SIGNUP_PASSWORD_PAGE_READY',
+ step,
+ source: 'background',
+ payload: {},
+ }, {
+ timeoutMs: 20000,
+ retryDelayMs: 700,
+ logMessage: `步骤 ${step}:认证页正在切换,等待密码页重新就绪...`,
+ });
+
+ if (result?.error) {
+ throw new Error(result.error);
+ }
+
+ return result || {};
+ }
+
+ async function resolveSignupEmailForFlow(state) {
+ let resolvedEmail = state.email;
+ if (isHotmailProvider(state)) {
+ const account = await ensureHotmailAccountForFlow({
+ allowAllocate: true,
+ markUsed: true,
+ preferredAccountId: state.currentHotmailAccountId || null,
+ });
+ resolvedEmail = account.email;
+ } else if (isLuckmailProvider(state)) {
+ const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
+ resolvedEmail = purchase.email_address;
+ } else if (isGeneratedAliasProvider(state)) {
+ resolvedEmail = buildGeneratedAliasEmail(state);
+ }
+
+ if (!resolvedEmail) {
+ throw new Error('缺少邮箱地址,请先在侧边栏粘贴邮箱。');
+ }
+
+ if (resolvedEmail !== state.email) {
+ await setEmailState(resolvedEmail);
+ }
+
+ return resolvedEmail;
+ }
+
+ return {
+ ensureSignupEntryPageReady,
+ ensureSignupPasswordPageReadyInTab,
+ openSignupEntryTab,
+ resolveSignupEmailForFlow,
+ };
+ }
+
+ return {
+ createSignupFlowHelpers,
+ };
+});
diff --git a/docs/refactor/2026-04-16-architecture-refactor-plan.md b/docs/refactor/2026-04-16-architecture-refactor-plan.md
new file mode 100644
index 0000000..180198a
--- /dev/null
+++ b/docs/refactor/2026-04-16-architecture-refactor-plan.md
@@ -0,0 +1,189 @@
+# 2026-04-16 Architecture Refactor Plan
+
+## 目标
+
+在不破坏现有 1~9 步自动化流程、消息流、状态机和测试保护面的前提下,逐步拆分当前超大的主文件,降低后续开发和排障成本。
+
+## 当前事实
+
+- 这是一个无构建步骤的 Manifest V3 扩展。
+- `background.js` 是 classic service worker,当前依赖 `importScripts(...)`。
+- `sidepanel/sidepanel.html` 通过多个 `
+
+
+