auth-page-recovery.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. (function authPageRecoveryModule(root, factory) {
  2. if (typeof module !== 'undefined' && module.exports) {
  3. module.exports = factory();
  4. return;
  5. }
  6. root.MultiPageAuthPageRecovery = factory();
  7. })(typeof self !== 'undefined' ? self : globalThis, function createAuthPageRecoveryModule() {
  8. function createAuthPageRecovery(deps = {}) {
  9. const {
  10. detailPattern = null,
  11. getActionText,
  12. getPageTextSnapshot,
  13. humanPause,
  14. isActionEnabled,
  15. isVisibleElement,
  16. log,
  17. simulateClick,
  18. sleep,
  19. throwIfStopped,
  20. titlePattern = null,
  21. } = deps;
  22. function matchesPathPatterns(pathname, pathPatterns = []) {
  23. if (!Array.isArray(pathPatterns) || !pathPatterns.length) {
  24. return true;
  25. }
  26. return pathPatterns.some((pattern) => pattern instanceof RegExp && pattern.test(pathname));
  27. }
  28. function getAuthRetryButton(options = {}) {
  29. const { allowDisabled = false } = options;
  30. const direct = document.querySelector('button[data-dd-action-name="Try again"]');
  31. if (direct && isVisibleElement(direct) && (allowDisabled || isActionEnabled(direct))) {
  32. return direct;
  33. }
  34. const candidates = document.querySelectorAll('button, [role="button"]');
  35. return Array.from(candidates).find((element) => {
  36. if (!isVisibleElement(element) || (!allowDisabled && !isActionEnabled(element))) {
  37. return false;
  38. }
  39. const text = typeof getActionText === 'function' ? getActionText(element) : '';
  40. return /重试|try\s+again/i.test(text);
  41. }) || null;
  42. }
  43. function getAuthTimeoutErrorPageState(options = {}) {
  44. const { pathPatterns = [] } = options;
  45. const pathname = location.pathname || '';
  46. if (!matchesPathPatterns(pathname, pathPatterns)) {
  47. return null;
  48. }
  49. const retryButton = getAuthRetryButton({ allowDisabled: true });
  50. if (!retryButton) {
  51. return null;
  52. }
  53. const text = typeof getPageTextSnapshot === 'function' ? getPageTextSnapshot() : '';
  54. const title = typeof document !== 'undefined' ? String(document.title || '') : '';
  55. const titleMatched = titlePattern instanceof RegExp
  56. ? titlePattern.test(text) || titlePattern.test(title)
  57. : false;
  58. const detailMatched = detailPattern instanceof RegExp
  59. ? detailPattern.test(text)
  60. : false;
  61. if (!titleMatched && !detailMatched) {
  62. return null;
  63. }
  64. return {
  65. path: pathname,
  66. url: location.href,
  67. retryButton,
  68. retryEnabled: isActionEnabled(retryButton),
  69. titleMatched,
  70. detailMatched,
  71. };
  72. }
  73. async function recoverAuthRetryPage(options = {}) {
  74. const {
  75. logLabel = '',
  76. pathPatterns = [],
  77. pollIntervalMs = 250,
  78. step = null,
  79. timeoutMs = 12000,
  80. waitAfterClickMs = 1200,
  81. } = options;
  82. const start = Date.now();
  83. let clickCount = 0;
  84. while (Date.now() - start < timeoutMs) {
  85. if (typeof throwIfStopped === 'function') {
  86. throwIfStopped();
  87. }
  88. const retryState = getAuthTimeoutErrorPageState({ pathPatterns });
  89. if (!retryState) {
  90. return {
  91. recovered: clickCount > 0,
  92. clickCount,
  93. url: location.href,
  94. };
  95. }
  96. if (retryState.retryButton && retryState.retryEnabled) {
  97. clickCount += 1;
  98. if (typeof log === 'function') {
  99. const prefix = logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`;
  100. log(`${prefix}(第 ${clickCount} 次)...`, 'warn');
  101. }
  102. if (typeof humanPause === 'function') {
  103. await humanPause(300, 800);
  104. }
  105. simulateClick(retryState.retryButton);
  106. await sleep(waitAfterClickMs);
  107. continue;
  108. }
  109. await sleep(pollIntervalMs);
  110. }
  111. throw new Error(
  112. `${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时。URL: ${location.href}`
  113. );
  114. }
  115. return {
  116. getAuthRetryButton,
  117. getAuthTimeoutErrorPageState,
  118. recoverAuthRetryPage,
  119. };
  120. }
  121. return {
  122. createAuthPageRecovery,
  123. };
  124. });