signup-flow-helpers.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. (function attachSignupFlowHelpers(root, factory) {
  2. root.MultiPageSignupFlowHelpers = factory();
  3. })(typeof self !== 'undefined' ? self : globalThis, function createSignupFlowHelpersModule() {
  4. function createSignupFlowHelpers(deps = {}) {
  5. const {
  6. buildGeneratedAliasEmail,
  7. chrome,
  8. ensureContentScriptReadyOnTab,
  9. ensureHotmailAccountForFlow,
  10. ensureLuckmailPurchaseForFlow,
  11. isGeneratedAliasProvider,
  12. isReusableGeneratedAliasEmail,
  13. isHotmailProvider,
  14. isLuckmailProvider,
  15. isSignupEmailVerificationPageUrl,
  16. isSignupPasswordPageUrl,
  17. reuseOrCreateTab,
  18. sendToContentScriptResilient,
  19. setEmailState,
  20. SIGNUP_ENTRY_URL,
  21. SIGNUP_PAGE_INJECT_FILES,
  22. waitForTabUrlMatch,
  23. } = deps;
  24. async function openSignupEntryTab(step = 1) {
  25. const tabId = await reuseOrCreateTab('signup-page', SIGNUP_ENTRY_URL, {
  26. inject: SIGNUP_PAGE_INJECT_FILES,
  27. injectSource: 'signup-page',
  28. });
  29. await ensureContentScriptReadyOnTab('signup-page', tabId, {
  30. inject: SIGNUP_PAGE_INJECT_FILES,
  31. injectSource: 'signup-page',
  32. timeoutMs: 45000,
  33. retryDelayMs: 900,
  34. logMessage: `步骤 ${step}:ChatGPT 官网仍在加载,正在重试连接内容脚本...`,
  35. });
  36. return tabId;
  37. }
  38. async function ensureSignupEntryPageReady(step = 1) {
  39. const tabId = await openSignupEntryTab(step);
  40. const result = await sendToContentScriptResilient('signup-page', {
  41. type: 'ENSURE_SIGNUP_ENTRY_READY',
  42. step,
  43. source: 'background',
  44. payload: {},
  45. }, {
  46. timeoutMs: 20000,
  47. retryDelayMs: 700,
  48. logMessage: `步骤 ${step}:官网注册入口正在切换,等待页面恢复...`,
  49. });
  50. if (result?.error) {
  51. throw new Error(result.error);
  52. }
  53. return { tabId, result: result || {} };
  54. }
  55. function resolveSignupPostEmailState(rawUrl) {
  56. if (isSignupPasswordPageUrl(rawUrl)) {
  57. return 'password_page';
  58. }
  59. if (isSignupEmailVerificationPageUrl(rawUrl)) {
  60. return 'verification_page';
  61. }
  62. return '';
  63. }
  64. async function ensureSignupPostEmailPageReadyInTab(tabId, step = 2, options = {}) {
  65. const { skipUrlWait = false } = options;
  66. let landingUrl = '';
  67. let landingState = '';
  68. if (!skipUrlWait) {
  69. const matchedTab = await waitForTabUrlMatch(tabId, (url) => Boolean(resolveSignupPostEmailState(url)), {
  70. timeoutMs: 45000,
  71. retryDelayMs: 300,
  72. });
  73. if (!matchedTab) {
  74. throw new Error('等待邮箱提交后的页面跳转超时,请检查页面是否仍停留在邮箱输入页。');
  75. }
  76. landingUrl = matchedTab.url || '';
  77. landingState = resolveSignupPostEmailState(landingUrl);
  78. }
  79. if (!landingState) {
  80. try {
  81. const currentTab = await chrome.tabs.get(tabId);
  82. landingUrl = landingUrl || currentTab?.url || '';
  83. landingState = resolveSignupPostEmailState(landingUrl);
  84. } catch {
  85. landingUrl = landingUrl || '';
  86. }
  87. }
  88. if (!landingState) {
  89. throw new Error(`邮箱提交后未能识别当前页面,既不是密码页也不是邮箱验证码页。URL: ${landingUrl || 'unknown'}`);
  90. }
  91. await ensureContentScriptReadyOnTab('signup-page', tabId, {
  92. inject: SIGNUP_PAGE_INJECT_FILES,
  93. injectSource: 'signup-page',
  94. timeoutMs: 45000,
  95. retryDelayMs: 900,
  96. logMessage: landingState === 'verification_page'
  97. ? `步骤 ${step}:邮箱验证码页仍在加载,正在等待页面恢复...`
  98. : `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...`,
  99. });
  100. if (landingState === 'verification_page') {
  101. return {
  102. ready: true,
  103. state: landingState,
  104. url: landingUrl,
  105. };
  106. }
  107. const result = await sendToContentScriptResilient('signup-page', {
  108. type: 'ENSURE_SIGNUP_PASSWORD_PAGE_READY',
  109. step,
  110. source: 'background',
  111. payload: {},
  112. }, {
  113. timeoutMs: 20000,
  114. retryDelayMs: 700,
  115. logMessage: `步骤 ${step}:认证页正在切换,等待密码页重新就绪...`,
  116. });
  117. if (result?.error) {
  118. throw new Error(result.error);
  119. }
  120. return {
  121. ...(result || {}),
  122. ready: true,
  123. state: landingState,
  124. url: landingUrl,
  125. };
  126. }
  127. async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
  128. const result = await ensureSignupPostEmailPageReadyInTab(tabId, step, options);
  129. if (result.state !== 'password_page') {
  130. throw new Error(`当前页面不是密码页,实际落地为 ${result.state || 'unknown'}。URL: ${result.url || 'unknown'}`);
  131. }
  132. return result;
  133. }
  134. async function finalizeSignupPasswordSubmitInTab(tabId, password = '', step = 3) {
  135. if (!Number.isInteger(tabId)) {
  136. throw new Error(`认证页面标签页已关闭,无法完成步骤 ${step} 的提交后确认。`);
  137. }
  138. await ensureContentScriptReadyOnTab('signup-page', tabId, {
  139. inject: SIGNUP_PAGE_INJECT_FILES,
  140. injectSource: 'signup-page',
  141. timeoutMs: 45000,
  142. retryDelayMs: 900,
  143. logMessage: `步骤 ${step}:认证页仍在切换,正在等待页面恢复后继续确认提交流程...`,
  144. });
  145. const result = await sendToContentScriptResilient('signup-page', {
  146. type: 'PREPARE_SIGNUP_VERIFICATION',
  147. step,
  148. source: 'background',
  149. payload: {
  150. password: password || '',
  151. prepareSource: 'step3_finalize',
  152. prepareLogLabel: '步骤 3 收尾',
  153. },
  154. }, {
  155. timeoutMs: 30000,
  156. retryDelayMs: 700,
  157. logMessage: `步骤 ${step}:密码已提交,正在确认是否进入下一页面,必要时自动恢复重试页...`,
  158. });
  159. if (result?.error) {
  160. throw new Error(result.error);
  161. }
  162. return result || {};
  163. }
  164. async function resolveSignupEmailForFlow(state) {
  165. let resolvedEmail = state.email;
  166. if (isHotmailProvider(state)) {
  167. const account = await ensureHotmailAccountForFlow({
  168. allowAllocate: true,
  169. markUsed: true,
  170. preferredAccountId: state.currentHotmailAccountId || null,
  171. });
  172. resolvedEmail = account.email;
  173. } else if (isLuckmailProvider(state)) {
  174. const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
  175. resolvedEmail = purchase.email_address;
  176. } else if (isGeneratedAliasProvider(state)) {
  177. if (!isReusableGeneratedAliasEmail?.(state, resolvedEmail)) {
  178. resolvedEmail = buildGeneratedAliasEmail(state);
  179. }
  180. }
  181. if (!resolvedEmail) {
  182. throw new Error('缺少邮箱地址,请先在侧边栏粘贴邮箱。');
  183. }
  184. if (resolvedEmail !== state.email) {
  185. await setEmailState(resolvedEmail);
  186. }
  187. return resolvedEmail;
  188. }
  189. return {
  190. ensureSignupEntryPageReady,
  191. ensureSignupPostEmailPageReadyInTab,
  192. finalizeSignupPasswordSubmitInTab,
  193. ensureSignupPasswordPageReadyInTab,
  194. openSignupEntryTab,
  195. resolveSignupEmailForFlow,
  196. };
  197. }
  198. return {
  199. createSignupFlowHelpers,
  200. };
  201. });