signup-flow-helpers.js 7.4 KB

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