message-router.js 19 KB

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