message-router.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. (function attachBackgroundMessageRouter(root, factory) {
  2. root.MultiPageBackgroundMessageRouter = factory();
  3. })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundMessageRouterModule() {
  4. function createMessageRouter(deps = {}) {
  5. const {
  6. addLog,
  7. appendAccountRunRecord,
  8. batchUpdateLuckmailPurchases,
  9. buildLocalhostCleanupPrefix,
  10. buildLuckmailSessionSettingsPayload,
  11. buildPersistentSettingsPayload,
  12. broadcastDataUpdate,
  13. cancelScheduledAutoRun,
  14. checkIcloudSession,
  15. clearAccountRunHistory,
  16. clearAutoRunTimerAlarm,
  17. clearLuckmailRuntimeState,
  18. clearStopRequest,
  19. closeLocalhostCallbackTabs,
  20. closeTabsByUrlPrefix,
  21. deleteHotmailAccount,
  22. deleteHotmailAccounts,
  23. deleteIcloudAlias,
  24. deleteUsedIcloudAliases,
  25. disableUsedLuckmailPurchases,
  26. doesStepUseCompletionSignal,
  27. ensureManualInteractionAllowed,
  28. executeStep,
  29. executeStepViaCompletionSignal,
  30. exportSettingsBundle,
  31. fetchGeneratedEmail,
  32. finalizeStep3Completion,
  33. finalizeIcloudAliasAfterSuccessfulFlow,
  34. findHotmailAccount,
  35. flushCommand,
  36. getCurrentLuckmailPurchase,
  37. getPendingAutoRunTimerPlan,
  38. getSourceLabel,
  39. getState,
  40. getStopRequested,
  41. handleAutoRunLoopUnhandledError,
  42. importSettingsBundle,
  43. invalidateDownstreamAfterStepRestart,
  44. isAutoRunLockedState,
  45. isHotmailProvider,
  46. isLocalhostOAuthCallbackUrl,
  47. isLuckmailProvider,
  48. isStopError,
  49. launchAutoRunTimerPlan,
  50. listIcloudAliases,
  51. listLuckmailPurchasesForManagement,
  52. normalizeHotmailAccounts,
  53. normalizeRunCount,
  54. AUTO_RUN_TIMER_KIND_SCHEDULED_START,
  55. notifyStepComplete,
  56. notifyStepError,
  57. patchHotmailAccount,
  58. registerTab,
  59. requestStop,
  60. resetState,
  61. resumeAutoRun,
  62. scheduleAutoRun,
  63. selectLuckmailPurchase,
  64. setCurrentHotmailAccount,
  65. setEmailState,
  66. setEmailStateSilently,
  67. setIcloudAliasPreservedState,
  68. setIcloudAliasUsedState,
  69. setLuckmailPurchaseDisabledState,
  70. setLuckmailPurchasePreservedState,
  71. setLuckmailPurchaseUsedState,
  72. setPersistentSettings,
  73. setState,
  74. setStepStatus,
  75. skipAutoRunCountdown,
  76. skipStep,
  77. startAutoRunLoop,
  78. syncHotmailAccounts,
  79. testHotmailAccountMailAccess,
  80. upsertHotmailAccount,
  81. verifyHotmailAccount,
  82. } = deps;
  83. async function appendManualAccountRunRecordIfNeeded(status, stateOverride = null, reason = '') {
  84. if (typeof appendAccountRunRecord !== 'function') {
  85. return null;
  86. }
  87. const state = stateOverride || await getState();
  88. if (isAutoRunLockedState(state)) {
  89. return null;
  90. }
  91. return appendAccountRunRecord(status, state, reason);
  92. }
  93. async function handleStepData(step, payload) {
  94. switch (step) {
  95. case 1: {
  96. const updates = {};
  97. if (payload.oauthUrl) {
  98. updates.oauthUrl = payload.oauthUrl;
  99. broadcastDataUpdate({ oauthUrl: payload.oauthUrl });
  100. }
  101. if (payload.sub2apiSessionId !== undefined) updates.sub2apiSessionId = payload.sub2apiSessionId || null;
  102. if (payload.sub2apiOAuthState !== undefined) updates.sub2apiOAuthState = payload.sub2apiOAuthState || null;
  103. if (payload.sub2apiGroupId !== undefined) updates.sub2apiGroupId = payload.sub2apiGroupId || null;
  104. if (payload.sub2apiDraftName !== undefined) updates.sub2apiDraftName = payload.sub2apiDraftName || null;
  105. if (payload.sub2apiProxyId !== undefined) updates.sub2apiProxyId = payload.sub2apiProxyId || null;
  106. if (Object.keys(updates).length) {
  107. await setState(updates);
  108. }
  109. break;
  110. }
  111. case 2:
  112. if (payload.email) {
  113. await setEmailState(payload.email);
  114. }
  115. if (payload.skippedPasswordStep) {
  116. const latestState = await getState();
  117. const step3Status = latestState.stepStatuses?.[3];
  118. if (step3Status !== 'running' && step3Status !== 'completed' && step3Status !== 'manual_completed') {
  119. await setStepStatus(3, 'skipped');
  120. await addLog('步骤 2:提交邮箱后页面直接进入邮箱验证码页,已自动跳过步骤 3。', 'warn');
  121. }
  122. }
  123. break;
  124. case 3:
  125. if (payload.email) await setEmailState(payload.email);
  126. if (payload.signupVerificationRequestedAt) {
  127. await setState({ signupVerificationRequestedAt: payload.signupVerificationRequestedAt });
  128. }
  129. if (payload.loginVerificationRequestedAt) {
  130. await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
  131. }
  132. break;
  133. case 7:
  134. if (payload.loginVerificationRequestedAt) {
  135. await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
  136. }
  137. break;
  138. case 4:
  139. await setState({
  140. lastEmailTimestamp: payload.emailTimestamp || null,
  141. signupVerificationRequestedAt: null,
  142. });
  143. break;
  144. case 8:
  145. await setState({
  146. lastEmailTimestamp: payload.emailTimestamp || null,
  147. loginVerificationRequestedAt: null,
  148. });
  149. break;
  150. case 9:
  151. if (payload.localhostUrl) {
  152. if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
  153. throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。');
  154. }
  155. await setState({ localhostUrl: payload.localhostUrl });
  156. broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
  157. }
  158. break;
  159. case 10: {
  160. if (payload.localhostUrl) {
  161. await closeLocalhostCallbackTabs(payload.localhostUrl);
  162. }
  163. const latestState = await getState();
  164. if (latestState.currentHotmailAccountId && isHotmailProvider(latestState)) {
  165. await patchHotmailAccount(latestState.currentHotmailAccountId, {
  166. used: true,
  167. lastUsedAt: Date.now(),
  168. });
  169. await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
  170. }
  171. if (isLuckmailProvider(latestState)) {
  172. const currentPurchase = getCurrentLuckmailPurchase(latestState);
  173. if (currentPurchase?.id) {
  174. await setLuckmailPurchaseUsedState(currentPurchase.id, true);
  175. await addLog(`当前 LuckMail 邮箱 ${currentPurchase.email_address} 已在本地标记为已用。`, 'ok');
  176. }
  177. await clearLuckmailRuntimeState({ clearEmail: true });
  178. await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok');
  179. }
  180. const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl);
  181. if (localhostPrefix) {
  182. await closeTabsByUrlPrefix(localhostPrefix, {
  183. excludeUrls: [payload.localhostUrl],
  184. excludeLocalhostCallbacks: true,
  185. });
  186. }
  187. await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
  188. break;
  189. }
  190. default:
  191. break;
  192. }
  193. }
  194. async function handleMessage(message, sender) {
  195. switch (message.type) {
  196. case 'CONTENT_SCRIPT_READY': {
  197. const tabId = sender.tab?.id;
  198. if (tabId && message.source) {
  199. await registerTab(message.source, tabId);
  200. flushCommand(message.source, tabId);
  201. await addLog(`内容脚本已就绪:${getSourceLabel(message.source)}(标签页 ${tabId})`);
  202. }
  203. return { ok: true };
  204. }
  205. case 'LOG': {
  206. const { message: msg, level } = message.payload;
  207. await addLog(`[${getSourceLabel(message.source)}] ${msg}`, level);
  208. return { ok: true };
  209. }
  210. case 'STEP_COMPLETE': {
  211. if (getStopRequested()) {
  212. await setStepStatus(message.step, 'stopped');
  213. await appendManualAccountRunRecordIfNeeded(`step${message.step}_stopped`, null, '流程已被用户停止。');
  214. notifyStepError(message.step, '流程已被用户停止。');
  215. return { ok: true };
  216. }
  217. try {
  218. if (message.step === 3 && typeof finalizeStep3Completion === 'function') {
  219. await finalizeStep3Completion(message.payload || {});
  220. }
  221. } catch (error) {
  222. const errorMessage = error?.message || String(error || '步骤 3 提交后确认失败');
  223. await setStepStatus(message.step, 'failed');
  224. await addLog(`步骤 ${message.step} 失败:${errorMessage}`, 'error');
  225. await appendManualAccountRunRecordIfNeeded(`step${message.step}_failed`, null, errorMessage);
  226. notifyStepError(message.step, errorMessage);
  227. return { ok: true, error: errorMessage };
  228. }
  229. const completionState = message.step === 10 ? await getState() : null;
  230. await setStepStatus(message.step, 'completed');
  231. await addLog(`步骤 ${message.step} 已完成`, 'ok');
  232. await handleStepData(message.step, message.payload);
  233. if (message.step === 10 && typeof appendAccountRunRecord === 'function') {
  234. await appendAccountRunRecord('success', completionState);
  235. }
  236. notifyStepComplete(message.step, message.payload);
  237. return { ok: true };
  238. }
  239. case 'STEP_ERROR': {
  240. if (isStopError(message.error)) {
  241. await setStepStatus(message.step, 'stopped');
  242. await addLog(`步骤 ${message.step} 已被用户停止`, 'warn');
  243. await appendManualAccountRunRecordIfNeeded(`step${message.step}_stopped`, null, message.error);
  244. notifyStepError(message.step, message.error);
  245. } else {
  246. await setStepStatus(message.step, 'failed');
  247. await addLog(`步骤 ${message.step} 失败:${message.error}`, 'error');
  248. await appendManualAccountRunRecordIfNeeded(`step${message.step}_failed`, null, message.error);
  249. notifyStepError(message.step, message.error);
  250. }
  251. return { ok: true };
  252. }
  253. case 'GET_STATE': {
  254. return await getState();
  255. }
  256. case 'RESET': {
  257. clearStopRequest();
  258. await clearAutoRunTimerAlarm();
  259. await resetState();
  260. await addLog('流程已重置', 'info');
  261. return { ok: true };
  262. }
  263. case 'CLEAR_ACCOUNT_RUN_HISTORY': {
  264. const state = await getState();
  265. if (isAutoRunLockedState(state)) {
  266. throw new Error('自动流程运行中,当前不能清理邮箱记录。');
  267. }
  268. if (typeof clearAccountRunHistory !== 'function') {
  269. return { ok: true, clearedCount: 0 };
  270. }
  271. const result = await clearAccountRunHistory(state);
  272. return { ok: true, ...result };
  273. }
  274. case 'EXECUTE_STEP': {
  275. clearStopRequest();
  276. if (message.source === 'sidepanel') {
  277. await ensureManualInteractionAllowed('手动执行步骤');
  278. }
  279. const step = message.payload.step;
  280. if (message.source === 'sidepanel') {
  281. await invalidateDownstreamAfterStepRestart(step, { logLabel: `步骤 ${step} 重新执行` });
  282. }
  283. if (message.payload.email) {
  284. await setEmailState(message.payload.email);
  285. }
  286. if (message.payload.emailPrefix !== undefined) {
  287. await setPersistentSettings({ emailPrefix: message.payload.emailPrefix });
  288. await setState({ emailPrefix: message.payload.emailPrefix });
  289. }
  290. if (doesStepUseCompletionSignal(step)) {
  291. await executeStepViaCompletionSignal(step);
  292. } else {
  293. await executeStep(step);
  294. }
  295. return { ok: true };
  296. }
  297. case 'AUTO_RUN': {
  298. clearStopRequest();
  299. const state = await getState();
  300. if (getPendingAutoRunTimerPlan(state)) {
  301. throw new Error('已有自动运行倒计时计划,请先取消或立即开始。');
  302. }
  303. const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1);
  304. const autoRunSkipFailures = Boolean(message.payload?.autoRunSkipFailures);
  305. const mode = message.payload?.mode === 'continue' ? 'continue' : 'restart';
  306. await setState({ autoRunSkipFailures });
  307. startAutoRunLoop(totalRuns, { autoRunSkipFailures, mode });
  308. return { ok: true };
  309. }
  310. case 'SCHEDULE_AUTO_RUN': {
  311. clearStopRequest();
  312. const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1);
  313. return await scheduleAutoRun(totalRuns, {
  314. delayMinutes: message.payload?.delayMinutes,
  315. autoRunSkipFailures: Boolean(message.payload?.autoRunSkipFailures),
  316. mode: message.payload?.mode,
  317. });
  318. }
  319. case 'START_SCHEDULED_AUTO_RUN_NOW': {
  320. clearStopRequest();
  321. const started = await launchAutoRunTimerPlan('manual', {
  322. expectedKinds: [AUTO_RUN_TIMER_KIND_SCHEDULED_START],
  323. });
  324. if (!started) {
  325. throw new Error('当前没有可立即开始的倒计时计划。');
  326. }
  327. return { ok: true };
  328. }
  329. case 'CANCEL_SCHEDULED_AUTO_RUN': {
  330. const cancelled = await cancelScheduledAutoRun();
  331. if (!cancelled) {
  332. throw new Error('当前没有可取消的倒计时计划。');
  333. }
  334. return { ok: true };
  335. }
  336. case 'SKIP_AUTO_RUN_COUNTDOWN': {
  337. clearStopRequest();
  338. const skipped = await skipAutoRunCountdown();
  339. if (!skipped) {
  340. throw new Error('当前没有可立即开始的倒计时。');
  341. }
  342. return { ok: true };
  343. }
  344. case 'RESUME_AUTO_RUN': {
  345. clearStopRequest();
  346. if (message.payload.email) {
  347. await setEmailState(message.payload.email);
  348. }
  349. resumeAutoRun().catch((error) => {
  350. handleAutoRunLoopUnhandledError(error).catch(() => {});
  351. });
  352. return { ok: true };
  353. }
  354. case 'TAKEOVER_AUTO_RUN': {
  355. await requestStop({ logMessage: '已确认手动接管,正在停止自动流程并切换为手动控制...' });
  356. await addLog('自动流程已切换为手动控制。', 'warn');
  357. return { ok: true };
  358. }
  359. case 'SKIP_STEP': {
  360. const step = Number(message.payload?.step);
  361. return await skipStep(step);
  362. }
  363. case 'SAVE_SETTING': {
  364. const updates = buildPersistentSettingsPayload(message.payload || {});
  365. const sessionUpdates = buildLuckmailSessionSettingsPayload(message.payload || {});
  366. await setPersistentSettings(updates);
  367. await setState({
  368. ...updates,
  369. ...sessionUpdates,
  370. });
  371. return { ok: true, state: await getState() };
  372. }
  373. case 'EXPORT_SETTINGS': {
  374. return { ok: true, ...(await exportSettingsBundle()) };
  375. }
  376. case 'IMPORT_SETTINGS': {
  377. const state = await importSettingsBundle(message.payload?.config || null);
  378. return { ok: true, state };
  379. }
  380. case 'UPSERT_HOTMAIL_ACCOUNT': {
  381. const account = await upsertHotmailAccount(message.payload || {});
  382. return { ok: true, account };
  383. }
  384. case 'DELETE_HOTMAIL_ACCOUNT': {
  385. await deleteHotmailAccount(String(message.payload?.accountId || ''));
  386. return { ok: true };
  387. }
  388. case 'DELETE_HOTMAIL_ACCOUNTS': {
  389. const result = await deleteHotmailAccounts(String(message.payload?.mode || 'all'));
  390. return { ok: true, ...result };
  391. }
  392. case 'SELECT_HOTMAIL_ACCOUNT': {
  393. const account = await setCurrentHotmailAccount(String(message.payload?.accountId || ''), {
  394. markUsed: false,
  395. syncEmail: true,
  396. });
  397. return { ok: true, account };
  398. }
  399. case 'PATCH_HOTMAIL_ACCOUNT': {
  400. const account = await patchHotmailAccount(
  401. String(message.payload?.accountId || ''),
  402. message.payload?.updates || {}
  403. );
  404. return { ok: true, account };
  405. }
  406. case 'VERIFY_HOTMAIL_ACCOUNT':
  407. case 'AUTHORIZE_HOTMAIL_ACCOUNT': {
  408. const accountId = String(message.payload?.accountId || '');
  409. try {
  410. const result = await verifyHotmailAccount(accountId);
  411. await setCurrentHotmailAccount(result.account.id, { markUsed: false, syncEmail: true });
  412. await addLog(`Hotmail 账号 ${result.account.email} 校验通过,可直接用于收信。`, 'ok');
  413. return { ok: true, account: result.account, messageCount: result.messageCount };
  414. } catch (err) {
  415. const state = await getState();
  416. const accounts = normalizeHotmailAccounts(state.hotmailAccounts);
  417. const target = findHotmailAccount(accounts, accountId);
  418. if (target) {
  419. target.status = 'error';
  420. target.lastError = err.message;
  421. await syncHotmailAccounts(accounts.map((item) => (item.id === target.id ? target : item)));
  422. }
  423. throw err;
  424. }
  425. }
  426. case 'TEST_HOTMAIL_ACCOUNT': {
  427. const result = await testHotmailAccountMailAccess(String(message.payload?.accountId || ''));
  428. return { ok: true, ...result };
  429. }
  430. case 'LIST_LUCKMAIL_PURCHASES': {
  431. const purchases = await listLuckmailPurchasesForManagement();
  432. return { ok: true, purchases };
  433. }
  434. case 'SELECT_LUCKMAIL_PURCHASE': {
  435. const purchase = await selectLuckmailPurchase(message.payload?.purchaseId);
  436. return { ok: true, purchase };
  437. }
  438. case 'SET_LUCKMAIL_PURCHASE_USED_STATE': {
  439. const result = await setLuckmailPurchaseUsedState(message.payload?.purchaseId, Boolean(message.payload?.used));
  440. return { ok: true, ...result };
  441. }
  442. case 'SET_LUCKMAIL_PURCHASE_PRESERVED_STATE': {
  443. const purchase = await setLuckmailPurchasePreservedState(message.payload?.purchaseId, Boolean(message.payload?.preserved));
  444. return { ok: true, purchase };
  445. }
  446. case 'SET_LUCKMAIL_PURCHASE_DISABLED_STATE': {
  447. const purchase = await setLuckmailPurchaseDisabledState(message.payload?.purchaseId, Boolean(message.payload?.disabled));
  448. return { ok: true, purchase };
  449. }
  450. case 'BATCH_UPDATE_LUCKMAIL_PURCHASES': {
  451. const result = await batchUpdateLuckmailPurchases(message.payload || {});
  452. return { ok: true, ...result };
  453. }
  454. case 'DISABLE_USED_LUCKMAIL_PURCHASES': {
  455. const result = await disableUsedLuckmailPurchases();
  456. return { ok: true, ...result };
  457. }
  458. case 'SET_EMAIL_STATE': {
  459. const state = await getState();
  460. if (isAutoRunLockedState(state)) {
  461. throw new Error('自动流程运行中,当前不能手动修改邮箱。');
  462. }
  463. const email = String(message.payload?.email || '').trim() || null;
  464. await setEmailStateSilently(email);
  465. return { ok: true, email };
  466. }
  467. case 'SAVE_EMAIL': {
  468. const state = await getState();
  469. if (isAutoRunLockedState(state)) {
  470. throw new Error('自动流程运行中,当前不能手动修改邮箱。');
  471. }
  472. await setEmailState(message.payload.email);
  473. await resumeAutoRun();
  474. return { ok: true, email: message.payload.email };
  475. }
  476. case 'FETCH_GENERATED_EMAIL': {
  477. clearStopRequest();
  478. const state = await getState();
  479. if (isAutoRunLockedState(state)) {
  480. throw new Error('自动流程运行中,当前不能手动获取邮箱。');
  481. }
  482. const email = await fetchGeneratedEmail(state, message.payload || {});
  483. await resumeAutoRun();
  484. return { ok: true, email };
  485. }
  486. case 'FETCH_DUCK_EMAIL': {
  487. clearStopRequest();
  488. const state = await getState();
  489. if (isAutoRunLockedState(state)) {
  490. throw new Error('自动流程运行中,当前不能手动获取邮箱。');
  491. }
  492. const email = await fetchGeneratedEmail(state, { ...(message.payload || {}), generator: 'duck' });
  493. await resumeAutoRun();
  494. return { ok: true, email };
  495. }
  496. case 'CHECK_ICLOUD_SESSION': {
  497. clearStopRequest();
  498. return await checkIcloudSession();
  499. }
  500. case 'LIST_ICLOUD_ALIASES': {
  501. clearStopRequest();
  502. const aliases = await listIcloudAliases();
  503. return { ok: true, aliases };
  504. }
  505. case 'SET_ICLOUD_ALIAS_USED_STATE': {
  506. clearStopRequest();
  507. const result = await setIcloudAliasUsedState(message.payload || {});
  508. return { ok: true, ...result };
  509. }
  510. case 'SET_ICLOUD_ALIAS_PRESERVED_STATE': {
  511. clearStopRequest();
  512. const result = await setIcloudAliasPreservedState(message.payload || {});
  513. return { ok: true, ...result };
  514. }
  515. case 'DELETE_ICLOUD_ALIAS': {
  516. clearStopRequest();
  517. const result = await deleteIcloudAlias(message.payload || {});
  518. return { ok: true, ...result };
  519. }
  520. case 'DELETE_USED_ICLOUD_ALIASES': {
  521. clearStopRequest();
  522. const result = await deleteUsedIcloudAliases();
  523. return { ok: true, ...result };
  524. }
  525. case 'STOP_FLOW': {
  526. await requestStop();
  527. return { ok: true };
  528. }
  529. default:
  530. console.warn('Unknown message type:', message.type);
  531. return { error: `Unknown message type: ${message.type}` };
  532. }
  533. }
  534. return {
  535. handleMessage,
  536. handleStepData,
  537. };
  538. }
  539. return {
  540. createMessageRouter,
  541. };
  542. });