verification-flow.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. (function attachBackgroundVerificationFlow(root, factory) {
  2. root.MultiPageBackgroundVerificationFlow = factory();
  3. })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundVerificationFlowModule() {
  4. function createVerificationFlowHelpers(deps = {}) {
  5. const {
  6. addLog,
  7. chrome,
  8. CLOUDFLARE_TEMP_EMAIL_PROVIDER,
  9. completeStepFromBackground,
  10. confirmCustomVerificationStepBypassRequest,
  11. getHotmailVerificationPollConfig,
  12. getHotmailVerificationRequestTimestamp,
  13. getState,
  14. getTabId,
  15. HOTMAIL_PROVIDER,
  16. isStopError,
  17. LUCKMAIL_PROVIDER,
  18. MAIL_2925_VERIFICATION_INTERVAL_MS,
  19. MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
  20. pollCloudflareTempEmailVerificationCode,
  21. pollHotmailVerificationCode,
  22. pollLuckmailVerificationCode,
  23. sendToContentScript,
  24. sendToMailContentScriptResilient,
  25. setState,
  26. sleepWithStop,
  27. throwIfStopped,
  28. VERIFICATION_POLL_MAX_ROUNDS,
  29. } = deps;
  30. function getVerificationCodeStateKey(step) {
  31. return step === 4 ? 'lastSignupCode' : 'lastLoginCode';
  32. }
  33. function getVerificationCodeLabel(step) {
  34. return step === 4 ? '注册' : '登录';
  35. }
  36. function getVerificationResendStateKey() {
  37. return 'verificationResendCount';
  38. }
  39. function normalizeVerificationResendCount(value, fallback = 0) {
  40. const numeric = Number(value);
  41. if (!Number.isFinite(numeric)) {
  42. return Math.max(0, Math.floor(Number(fallback) || 0));
  43. }
  44. return Math.min(20, Math.max(0, Math.floor(numeric)));
  45. }
  46. function getLegacyVerificationResendCountDefault(step, options = {}) {
  47. const requestFreshCodeFirst = Boolean(options.requestFreshCodeFirst);
  48. const legacyMaxRounds = Math.max(1, Math.floor(Number(VERIFICATION_POLL_MAX_ROUNDS) || 1));
  49. if (step === 4 && requestFreshCodeFirst) {
  50. return legacyMaxRounds;
  51. }
  52. return Math.max(0, legacyMaxRounds - 1);
  53. }
  54. function getConfiguredVerificationResendCount(step, state, options = {}) {
  55. const stateKey = getVerificationResendStateKey(step);
  56. const configuredValue = state?.[stateKey] !== undefined
  57. ? state[stateKey]
  58. : (state?.signupVerificationResendCount ?? state?.loginVerificationResendCount);
  59. return normalizeVerificationResendCount(
  60. configuredValue,
  61. getLegacyVerificationResendCountDefault(step, options)
  62. );
  63. }
  64. function resolveMaxResendRequests(pollOverrides = {}) {
  65. if (pollOverrides.maxResendRequests !== undefined) {
  66. return normalizeVerificationResendCount(pollOverrides.maxResendRequests, 0);
  67. }
  68. const legacyMaxRounds = Number(pollOverrides.maxRounds);
  69. if (Number.isFinite(legacyMaxRounds)) {
  70. return Math.max(0, Math.floor(legacyMaxRounds) - 1);
  71. }
  72. return Math.max(0, Math.floor(Number(VERIFICATION_POLL_MAX_ROUNDS) || 1) - 1);
  73. }
  74. async function confirmCustomVerificationStepBypass(step) {
  75. const verificationLabel = getVerificationCodeLabel(step);
  76. await addLog(`步骤 ${step}:当前为自定义邮箱模式,请手动在页面中输入${verificationLabel}验证码并进入下一页面。`, 'warn');
  77. let response = null;
  78. try {
  79. response = await confirmCustomVerificationStepBypassRequest(step);
  80. } catch {
  81. throw new Error(`步骤 ${step}:无法打开确认弹窗,请先保持侧边栏打开后重试。`);
  82. }
  83. if (response?.error) {
  84. throw new Error(response.error);
  85. }
  86. if (!response?.confirmed) {
  87. throw new Error(`步骤 ${step}:已取消手动${verificationLabel}验证码确认。`);
  88. }
  89. await setState({
  90. lastEmailTimestamp: null,
  91. signupVerificationRequestedAt: null,
  92. loginVerificationRequestedAt: null,
  93. });
  94. await deps.setStepStatus(step, 'skipped');
  95. await addLog(`步骤 ${step}:已确认手动完成${verificationLabel}验证码输入,当前步骤已跳过。`, 'warn');
  96. }
  97. function getVerificationPollPayload(step, state, overrides = {}) {
  98. const is2925Provider = state?.mailProvider === '2925';
  99. if (step === 4) {
  100. return {
  101. filterAfterTimestamp: getHotmailVerificationRequestTimestamp(4, state),
  102. senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'],
  103. subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm'],
  104. targetEmail: state.email,
  105. maxAttempts: is2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
  106. intervalMs: is2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
  107. ...overrides,
  108. };
  109. }
  110. return {
  111. filterAfterTimestamp: getHotmailVerificationRequestTimestamp(8, state),
  112. senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
  113. subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
  114. targetEmail: state.email,
  115. maxAttempts: is2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
  116. intervalMs: is2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
  117. ...overrides,
  118. };
  119. }
  120. async function requestVerificationCodeResend(step) {
  121. throwIfStopped();
  122. const signupTabId = await getTabId('signup-page');
  123. if (!signupTabId) {
  124. throw new Error('认证页面标签页已关闭,无法重新请求验证码。');
  125. }
  126. throwIfStopped();
  127. await chrome.tabs.update(signupTabId, { active: true });
  128. throwIfStopped();
  129. const result = await sendToContentScript('signup-page', {
  130. type: 'RESEND_VERIFICATION_CODE',
  131. step,
  132. source: 'background',
  133. payload: {},
  134. });
  135. if (result && result.error) {
  136. throw new Error(result.error);
  137. }
  138. await addLog(`步骤 ${step}:已请求新的${getVerificationCodeLabel(step)}验证码。`, 'warn');
  139. const requestedAt = Date.now();
  140. if (step === 4) {
  141. await setState({ signupVerificationRequestedAt: requestedAt });
  142. }
  143. if (step === 8) {
  144. await setState({ loginVerificationRequestedAt: requestedAt });
  145. }
  146. const currentState = await getState();
  147. if (currentState.mailProvider === '2925') {
  148. const mailTabId = await getTabId('mail-2925');
  149. if (mailTabId) {
  150. await chrome.tabs.update(mailTabId, { active: true });
  151. await addLog(`步骤 ${step}:已切换到 2925 邮箱标签页等待新邮件。`, 'info');
  152. }
  153. }
  154. return requestedAt;
  155. }
  156. async function pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides = {}) {
  157. const stateKey = getVerificationCodeStateKey(step);
  158. const rejectedCodes = new Set();
  159. if (state[stateKey]) {
  160. rejectedCodes.add(state[stateKey]);
  161. }
  162. for (const code of (pollOverrides.excludeCodes || [])) {
  163. if (code) rejectedCodes.add(code);
  164. }
  165. const {
  166. maxRounds: _ignoredMaxRounds,
  167. maxResendRequests: _ignoredMaxResendRequests,
  168. resendIntervalMs: _ignoredResendIntervalMs,
  169. lastResendAt: _ignoredLastResendAt,
  170. onResendRequestedAt: _ignoredOnResendRequestedAt,
  171. ...payloadOverrides
  172. } = pollOverrides;
  173. const onResendRequestedAt = typeof pollOverrides.onResendRequestedAt === 'function'
  174. ? pollOverrides.onResendRequestedAt
  175. : null;
  176. let lastError = null;
  177. let filterAfterTimestamp = payloadOverrides.filterAfterTimestamp ?? getVerificationPollPayload(step, state).filterAfterTimestamp;
  178. const maxResendRequests = resolveMaxResendRequests(pollOverrides);
  179. const totalRounds = maxResendRequests + 1;
  180. const maxRounds = totalRounds;
  181. const resendIntervalMs = Math.max(0, Number(pollOverrides.resendIntervalMs) || 0);
  182. let lastResendAt = Number(pollOverrides.lastResendAt) || 0;
  183. let usedResendRequests = 0;
  184. for (let round = 1; round <= totalRounds; round++) {
  185. throwIfStopped();
  186. if (round > 1) {
  187. lastResendAt = await requestVerificationCodeResend(step);
  188. usedResendRequests += 1;
  189. if (onResendRequestedAt) {
  190. const nextFilterAfterTimestamp = await onResendRequestedAt(lastResendAt);
  191. if (nextFilterAfterTimestamp !== undefined) {
  192. filterAfterTimestamp = nextFilterAfterTimestamp;
  193. }
  194. }
  195. }
  196. while (true) {
  197. throwIfStopped();
  198. const payload = getVerificationPollPayload(step, state, {
  199. ...payloadOverrides,
  200. filterAfterTimestamp,
  201. excludeCodes: [...rejectedCodes],
  202. });
  203. if (lastResendAt > 0) {
  204. const remainingBeforeResendMs = Math.max(0, resendIntervalMs - (Date.now() - lastResendAt));
  205. const baseMaxAttempts = Math.max(1, Number(payload.maxAttempts) || 5);
  206. const intervalMs = Math.max(1, Number(payload.intervalMs) || 3000);
  207. payload.maxAttempts = Math.max(1, Math.min(baseMaxAttempts, Math.floor(remainingBeforeResendMs / intervalMs) + 1));
  208. }
  209. try {
  210. const result = await sendToMailContentScriptResilient(
  211. mail,
  212. {
  213. type: 'POLL_EMAIL',
  214. step,
  215. source: 'background',
  216. payload,
  217. },
  218. {
  219. timeoutMs: 45000,
  220. maxRecoveryAttempts: 2,
  221. }
  222. );
  223. if (result && result.error) {
  224. throw new Error(result.error);
  225. }
  226. if (!result || !result.code) {
  227. throw new Error(`步骤 ${step}:邮箱轮询结束,但未获取到验证码。`);
  228. }
  229. if (rejectedCodes.has(result.code)) {
  230. throw new Error(`步骤 ${step}:再次收到了相同的${getVerificationCodeLabel(step)}验证码:${result.code}`);
  231. }
  232. return {
  233. ...result,
  234. lastResendAt,
  235. remainingResendRequests: Math.max(0, maxResendRequests - usedResendRequests),
  236. };
  237. } catch (err) {
  238. if (isStopError(err)) {
  239. throw err;
  240. }
  241. lastError = err;
  242. await addLog(`步骤 ${step}:${err.message}`, 'warn');
  243. }
  244. const remainingBeforeResendMs = lastResendAt > 0
  245. ? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
  246. : 0;
  247. if (remainingBeforeResendMs > 0) {
  248. await addLog(
  249. `步骤 ${step}:距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,继续刷新邮箱(第 ${round}/${maxRounds} 轮)...`,
  250. 'info'
  251. );
  252. continue;
  253. }
  254. if (round < maxRounds) {
  255. await addLog(`步骤 ${step}:已到 25 秒重发间隔,准备重新发送验证码(第 ${round + 1}/${maxRounds} 轮)...`, 'warn');
  256. }
  257. break;
  258. }
  259. }
  260. throw lastError || new Error(`步骤 ${step}:无法获取新的${getVerificationCodeLabel(step)}验证码。`);
  261. }
  262. async function pollFreshVerificationCode(step, state, mail, pollOverrides = {}) {
  263. const {
  264. onResendRequestedAt,
  265. maxRounds: _ignoredMaxRounds,
  266. maxResendRequests: _ignoredMaxResendRequests,
  267. ...cleanPollOverrides
  268. } = pollOverrides;
  269. if (mail.provider === HOTMAIL_PROVIDER) {
  270. const hotmailPollConfig = getHotmailVerificationPollConfig(step);
  271. return pollHotmailVerificationCode(step, state, {
  272. ...getVerificationPollPayload(step, state),
  273. ...hotmailPollConfig,
  274. ...cleanPollOverrides,
  275. });
  276. }
  277. if (mail.provider === LUCKMAIL_PROVIDER) {
  278. return pollLuckmailVerificationCode(step, state, {
  279. ...getVerificationPollPayload(step, state),
  280. ...cleanPollOverrides,
  281. });
  282. }
  283. if (mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
  284. return pollCloudflareTempEmailVerificationCode(step, state, {
  285. ...getVerificationPollPayload(step, state),
  286. ...cleanPollOverrides,
  287. });
  288. }
  289. if (Number(pollOverrides.resendIntervalMs) > 0) {
  290. return pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides);
  291. }
  292. const stateKey = getVerificationCodeStateKey(step);
  293. const rejectedCodes = new Set();
  294. if (state[stateKey]) {
  295. rejectedCodes.add(state[stateKey]);
  296. }
  297. for (const code of (pollOverrides.excludeCodes || [])) {
  298. if (code) rejectedCodes.add(code);
  299. }
  300. let lastError = null;
  301. let filterAfterTimestamp = cleanPollOverrides.filterAfterTimestamp ?? getVerificationPollPayload(step, state).filterAfterTimestamp;
  302. const maxResendRequests = resolveMaxResendRequests(pollOverrides);
  303. const maxRounds = maxResendRequests + 1;
  304. let usedResendRequests = 0;
  305. for (let round = 1; round <= maxRounds; round++) {
  306. throwIfStopped();
  307. if (round > 1) {
  308. const requestedAt = await requestVerificationCodeResend(step);
  309. usedResendRequests += 1;
  310. if (typeof onResendRequestedAt === 'function') {
  311. const nextFilterAfterTimestamp = await onResendRequestedAt(requestedAt);
  312. if (nextFilterAfterTimestamp !== undefined) {
  313. filterAfterTimestamp = nextFilterAfterTimestamp;
  314. }
  315. }
  316. }
  317. const payload = getVerificationPollPayload(step, state, {
  318. ...cleanPollOverrides,
  319. filterAfterTimestamp,
  320. excludeCodes: [...rejectedCodes],
  321. });
  322. try {
  323. const result = await sendToMailContentScriptResilient(
  324. mail,
  325. {
  326. type: 'POLL_EMAIL',
  327. step,
  328. source: 'background',
  329. payload,
  330. },
  331. {
  332. timeoutMs: 45000,
  333. maxRecoveryAttempts: 2,
  334. }
  335. );
  336. if (result && result.error) {
  337. throw new Error(result.error);
  338. }
  339. if (!result || !result.code) {
  340. throw new Error(`步骤 ${step}:邮箱轮询结束,但未获取到验证码。`);
  341. }
  342. if (rejectedCodes.has(result.code)) {
  343. throw new Error(`步骤 ${step}:再次收到了相同的${getVerificationCodeLabel(step)}验证码:${result.code}`);
  344. }
  345. return {
  346. ...result,
  347. remainingResendRequests: Math.max(0, maxResendRequests - usedResendRequests),
  348. };
  349. } catch (err) {
  350. if (isStopError(err)) {
  351. throw err;
  352. }
  353. lastError = err;
  354. await addLog(`步骤 ${step}:${err.message}`, 'warn');
  355. if (round < maxRounds) {
  356. await addLog(`步骤 ${step}:将重新发送验证码后重试(${round + 1}/${maxRounds})...`, 'warn');
  357. }
  358. }
  359. }
  360. throw lastError || new Error(`步骤 ${step}:无法获取新的${getVerificationCodeLabel(step)}验证码。`);
  361. }
  362. async function submitVerificationCode(step, code) {
  363. const signupTabId = await getTabId('signup-page');
  364. if (!signupTabId) {
  365. throw new Error('认证页面标签页已关闭,无法填写验证码。');
  366. }
  367. await chrome.tabs.update(signupTabId, { active: true });
  368. const result = await sendToContentScript('signup-page', {
  369. type: 'FILL_CODE',
  370. step,
  371. source: 'background',
  372. payload: { code },
  373. });
  374. if (result && result.error) {
  375. throw new Error(result.error);
  376. }
  377. return result || {};
  378. }
  379. async function resolveVerificationStep(step, state, mail, options = {}) {
  380. const stateKey = getVerificationCodeStateKey(step);
  381. const rejectedCodes = new Set();
  382. const hotmailPollConfig = mail.provider === HOTMAIL_PROVIDER
  383. ? getHotmailVerificationPollConfig(step)
  384. : null;
  385. const beforeSubmit = typeof options.beforeSubmit === 'function'
  386. ? options.beforeSubmit
  387. : null;
  388. const ignorePersistedLastCode = Boolean(hotmailPollConfig?.ignorePersistedLastCode);
  389. if (state[stateKey] && !ignorePersistedLastCode) {
  390. rejectedCodes.add(state[stateKey]);
  391. }
  392. let nextFilterAfterTimestamp = options.filterAfterTimestamp ?? null;
  393. const requestFreshCodeFirst = options.requestFreshCodeFirst !== undefined
  394. ? Boolean(options.requestFreshCodeFirst)
  395. : (hotmailPollConfig?.requestFreshCodeFirst ?? false);
  396. let remainingAutomaticResendCount = options.maxResendRequests !== undefined
  397. ? normalizeVerificationResendCount(
  398. options.maxResendRequests,
  399. getLegacyVerificationResendCountDefault(step, { requestFreshCodeFirst })
  400. )
  401. : getConfiguredVerificationResendCount(step, state, { requestFreshCodeFirst });
  402. const maxSubmitAttempts = 3;
  403. const resendIntervalMs = Math.max(0, Number(options.resendIntervalMs) || 0);
  404. let lastResendAt = Number(options.lastResendAt) || 0;
  405. const updateFilterAfterTimestampForVerificationStep = async (requestedAt) => {
  406. if ((step !== 4 && step !== 8) || !requestedAt) {
  407. return nextFilterAfterTimestamp;
  408. }
  409. if (mail.provider === HOTMAIL_PROVIDER) {
  410. nextFilterAfterTimestamp = getHotmailVerificationRequestTimestamp(step, {
  411. ...state,
  412. ...(step === 4
  413. ? { signupVerificationRequestedAt: requestedAt }
  414. : { loginVerificationRequestedAt: requestedAt }),
  415. });
  416. } else {
  417. nextFilterAfterTimestamp = Math.max(0, Number(requestedAt) - 60000);
  418. }
  419. return nextFilterAfterTimestamp;
  420. };
  421. if (requestFreshCodeFirst) {
  422. if (remainingAutomaticResendCount <= 0) {
  423. await addLog(`步骤 ${step}:当前自动重新发送验证码次数为 0,将直接使用当前时间窗口轮询邮箱。`, 'info');
  424. } else {
  425. try {
  426. lastResendAt = await requestVerificationCodeResend(step);
  427. remainingAutomaticResendCount -= 1;
  428. await updateFilterAfterTimestampForVerificationStep(lastResendAt);
  429. await addLog(`步骤 ${step}:已先请求一封新的${getVerificationCodeLabel(step)}验证码,再开始轮询邮箱。`, 'warn');
  430. } catch (err) {
  431. if (isStopError(err)) {
  432. throw err;
  433. }
  434. await addLog(`步骤 ${step}:首次重新获取验证码失败:${err.message},将继续使用当前时间窗口轮询。`, 'warn');
  435. }
  436. }
  437. }
  438. if (mail.provider === HOTMAIL_PROVIDER) {
  439. const initialDelayMs = Number(options.initialDelayMs ?? hotmailPollConfig.initialDelayMs) || 0;
  440. if (initialDelayMs > 0) {
  441. await addLog(`步骤 ${step}:等待 ${Math.round(initialDelayMs / 1000)} 秒,让 Hotmail 验证码邮件先到达...`, 'info');
  442. await sleepWithStop(initialDelayMs);
  443. }
  444. }
  445. for (let attempt = 1; attempt <= maxSubmitAttempts; attempt++) {
  446. const pollOptions = {
  447. excludeCodes: [...rejectedCodes],
  448. maxResendRequests: remainingAutomaticResendCount,
  449. resendIntervalMs,
  450. lastResendAt,
  451. onResendRequestedAt: updateFilterAfterTimestampForVerificationStep,
  452. };
  453. if (nextFilterAfterTimestamp !== null && nextFilterAfterTimestamp !== undefined) {
  454. pollOptions.filterAfterTimestamp = nextFilterAfterTimestamp;
  455. }
  456. const result = await pollFreshVerificationCode(step, state, mail, pollOptions);
  457. lastResendAt = Number(result?.lastResendAt) || lastResendAt;
  458. remainingAutomaticResendCount = normalizeVerificationResendCount(
  459. result?.remainingResendRequests,
  460. remainingAutomaticResendCount
  461. );
  462. throwIfStopped();
  463. await addLog(`步骤 ${step}:已获取${getVerificationCodeLabel(step)}验证码:${result.code}`);
  464. if (beforeSubmit) {
  465. await beforeSubmit(result, {
  466. attempt,
  467. rejectedCodes: new Set(rejectedCodes),
  468. filterAfterTimestamp: nextFilterAfterTimestamp ?? undefined,
  469. lastResendAt,
  470. });
  471. }
  472. throwIfStopped();
  473. const submitResult = await submitVerificationCode(step, result.code);
  474. if (submitResult.invalidCode) {
  475. rejectedCodes.add(result.code);
  476. await addLog(`步骤 ${step}:验证码被页面拒绝:${submitResult.errorText || result.code}`, 'warn');
  477. if (attempt >= maxSubmitAttempts) {
  478. throw new Error(`步骤 ${step}:验证码连续失败,已达到 ${maxSubmitAttempts} 次重试上限。`);
  479. }
  480. const remainingBeforeResendMs = resendIntervalMs > 0 && lastResendAt > 0
  481. ? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
  482. : 0;
  483. if (remainingBeforeResendMs > 0) {
  484. await addLog(
  485. `步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts})...`,
  486. 'warn'
  487. );
  488. continue;
  489. }
  490. if (remainingAutomaticResendCount <= 0) {
  491. await addLog(`步骤 ${step}:已达到自动重新发送验证码次数上限,将直接使用当前时间窗口继续重试。`, 'warn');
  492. continue;
  493. }
  494. lastResendAt = await requestVerificationCodeResend(step);
  495. remainingAutomaticResendCount -= 1;
  496. await updateFilterAfterTimestampForVerificationStep(lastResendAt);
  497. await addLog(`步骤 ${step}:提交失败后已请求新验证码(${attempt + 1}/${maxSubmitAttempts})...`, 'warn');
  498. continue;
  499. }
  500. await setState({
  501. lastEmailTimestamp: result.emailTimestamp,
  502. [stateKey]: result.code,
  503. });
  504. await completeStepFromBackground(step, {
  505. emailTimestamp: result.emailTimestamp,
  506. code: result.code,
  507. });
  508. return;
  509. }
  510. }
  511. return {
  512. confirmCustomVerificationStepBypass,
  513. getVerificationCodeLabel,
  514. getVerificationCodeStateKey,
  515. getVerificationPollPayload,
  516. pollFreshVerificationCode,
  517. pollFreshVerificationCodeWithResendInterval,
  518. requestVerificationCodeResend,
  519. resolveVerificationStep,
  520. submitVerificationCode,
  521. };
  522. }
  523. return {
  524. createVerificationFlowHelpers,
  525. };
  526. });