confirm-oauth.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. (function attachBackgroundStep9(root, factory) {
  2. root.MultiPageBackgroundStep9 = factory();
  3. })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep9Module() {
  4. function createStep9Executor(deps = {}) {
  5. const {
  6. addLog,
  7. chrome,
  8. cleanupStep8NavigationListeners,
  9. clickWithDebugger,
  10. completeStepFromBackground,
  11. ensureStep8SignupPageReady,
  12. getOAuthFlowStepTimeoutMs,
  13. getStep8CallbackUrlFromNavigation,
  14. getStep8CallbackUrlFromTabUpdate,
  15. getStep8EffectLabel,
  16. getTabId,
  17. isTabAlive,
  18. prepareStep8DebuggerClick,
  19. reloadStep8ConsentPage,
  20. reuseOrCreateTab,
  21. sleepWithStop,
  22. STEP8_CLICK_RETRY_DELAY_MS,
  23. STEP8_MAX_ROUNDS,
  24. STEP8_READY_WAIT_TIMEOUT_MS,
  25. STEP8_STRATEGIES,
  26. throwIfStep8SettledOrStopped,
  27. triggerStep8ContentStrategy,
  28. waitForStep8ClickEffect,
  29. waitForStep8Ready,
  30. setWebNavListener,
  31. setWebNavCommittedListener,
  32. setStep8PendingReject,
  33. setStep8TabUpdatedListener,
  34. } = deps;
  35. async function executeStep9(state) {
  36. if (!state.oauthUrl) {
  37. throw new Error('缺少登录用 OAuth 链接,请先完成步骤 7。');
  38. }
  39. await addLog('步骤 9:正在监听 localhost 回调地址...');
  40. const callbackTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
  41. ? await getOAuthFlowStepTimeoutMs(120000, {
  42. step: 9,
  43. actionLabel: 'OAuth localhost 回调',
  44. })
  45. : 120000;
  46. return new Promise((resolve, reject) => {
  47. let resolved = false;
  48. let signupTabId = null;
  49. const cleanupListener = () => {
  50. cleanupStep8NavigationListeners();
  51. setStep8PendingReject(null);
  52. };
  53. const rejectStep9 = (error) => {
  54. if (resolved) return;
  55. resolved = true;
  56. clearTimeout(timeout);
  57. cleanupListener();
  58. reject(error);
  59. };
  60. const finalizeStep9Callback = (callbackUrl) => {
  61. if (resolved || !callbackUrl) return;
  62. resolved = true;
  63. cleanupListener();
  64. clearTimeout(timeout);
  65. addLog(`步骤 9:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
  66. return completeStepFromBackground(9, { localhostUrl: callbackUrl });
  67. }).then(() => {
  68. resolve();
  69. }).catch((err) => {
  70. reject(err);
  71. });
  72. };
  73. const timeout = setTimeout(() => {
  74. rejectStep9(new Error('120 秒内未捕获到 localhost 回调跳转,步骤 9 的点击可能被拦截了。'));
  75. }, callbackTimeoutMs);
  76. setStep8PendingReject((error) => {
  77. rejectStep9(error);
  78. });
  79. setWebNavListener((details) => {
  80. const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
  81. finalizeStep9Callback(callbackUrl);
  82. });
  83. setWebNavCommittedListener((details) => {
  84. const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
  85. finalizeStep9Callback(callbackUrl);
  86. });
  87. setStep8TabUpdatedListener((tabId, changeInfo, tab) => {
  88. const callbackUrl = getStep8CallbackUrlFromTabUpdate(tabId, changeInfo, tab, signupTabId);
  89. finalizeStep9Callback(callbackUrl);
  90. });
  91. (async () => {
  92. try {
  93. throwIfStep8SettledOrStopped(resolved);
  94. signupTabId = await getTabId('signup-page');
  95. throwIfStep8SettledOrStopped(resolved);
  96. if (signupTabId && await isTabAlive('signup-page')) {
  97. await chrome.tabs.update(signupTabId, { active: true });
  98. await addLog('步骤 9:已切回认证页,正在准备调试器点击...');
  99. } else {
  100. signupTabId = await reuseOrCreateTab('signup-page', state.oauthUrl);
  101. await addLog('步骤 9:已重新打开认证页,正在准备调试器点击...');
  102. }
  103. throwIfStep8SettledOrStopped(resolved);
  104. chrome.webNavigation.onBeforeNavigate.addListener(deps.getWebNavListener());
  105. chrome.webNavigation.onCommitted.addListener(deps.getWebNavCommittedListener());
  106. chrome.tabs.onUpdated.addListener(deps.getStep8TabUpdatedListener());
  107. await ensureStep8SignupPageReady(signupTabId, {
  108. timeoutMs: typeof getOAuthFlowStepTimeoutMs === 'function'
  109. ? await getOAuthFlowStepTimeoutMs(15000, {
  110. step: 9,
  111. actionLabel: '等待 OAuth 同意页内容脚本就绪',
  112. })
  113. : 15000,
  114. logMessage: '步骤 9:认证页内容脚本尚未就绪,正在等待页面恢复...',
  115. });
  116. for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) {
  117. throwIfStep8SettledOrStopped(resolved);
  118. const pageState = await waitForStep8Ready(
  119. signupTabId,
  120. typeof getOAuthFlowStepTimeoutMs === 'function'
  121. ? await getOAuthFlowStepTimeoutMs(STEP8_READY_WAIT_TIMEOUT_MS, {
  122. step: 9,
  123. actionLabel: '等待 OAuth 同意页出现',
  124. })
  125. : STEP8_READY_WAIT_TIMEOUT_MS
  126. );
  127. if (!pageState?.consentReady) {
  128. await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
  129. continue;
  130. }
  131. const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)];
  132. await addLog(`步骤 9:第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label})...`);
  133. if (strategy.mode === 'debugger') {
  134. const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
  135. ? await getOAuthFlowStepTimeoutMs(15000, {
  136. step: 9,
  137. actionLabel: '定位 OAuth 同意页继续按钮',
  138. })
  139. : 15000;
  140. const clickTarget = await prepareStep8DebuggerClick(signupTabId, {
  141. timeoutMs: clickActionTimeoutMs,
  142. responseTimeoutMs: clickActionTimeoutMs,
  143. });
  144. throwIfStep8SettledOrStopped(resolved);
  145. await clickWithDebugger(signupTabId, clickTarget?.rect);
  146. } else {
  147. const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
  148. ? await getOAuthFlowStepTimeoutMs(15000, {
  149. step: 9,
  150. actionLabel: '点击 OAuth 同意页继续按钮',
  151. })
  152. : 15000;
  153. await triggerStep8ContentStrategy(signupTabId, strategy.strategy, {
  154. timeoutMs: clickActionTimeoutMs,
  155. responseTimeoutMs: clickActionTimeoutMs,
  156. });
  157. }
  158. if (resolved) {
  159. return;
  160. }
  161. const effect = await waitForStep8ClickEffect(
  162. signupTabId,
  163. pageState.url,
  164. typeof getOAuthFlowStepTimeoutMs === 'function'
  165. ? await getOAuthFlowStepTimeoutMs(15000, {
  166. step: 9,
  167. actionLabel: '等待 OAuth 同意页点击生效',
  168. })
  169. : 15000
  170. );
  171. if (resolved) {
  172. return;
  173. }
  174. if (effect.progressed) {
  175. await addLog(`步骤 9:检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
  176. break;
  177. }
  178. if (effect.restartCurrentStep) {
  179. await addLog(`步骤 9:${getStep8EffectLabel(effect)},准备重新定位“继续”按钮并重试...`, 'warn');
  180. await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
  181. continue;
  182. }
  183. if (round >= STEP8_MAX_ROUNDS) {
  184. throw new Error(`步骤 9:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`);
  185. }
  186. await addLog(`步骤 9:${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS})...`, 'warn');
  187. await reloadStep8ConsentPage(
  188. signupTabId,
  189. typeof getOAuthFlowStepTimeoutMs === 'function'
  190. ? await getOAuthFlowStepTimeoutMs(30000, {
  191. step: 9,
  192. actionLabel: '刷新 OAuth 同意页',
  193. })
  194. : 30000
  195. );
  196. await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
  197. }
  198. } catch (err) {
  199. rejectStep9(err);
  200. }
  201. })();
  202. });
  203. }
  204. return { executeStep9 };
  205. }
  206. return { createStep9Executor };
  207. });