message-router.js 21 KB

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