message-router.js 21 KB

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