@detiq/crawler 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (412) hide show
  1. package/Dockerfile +49 -0
  2. package/README.md +11 -0
  3. package/dist/accessibility-auditor.d.ts +29 -0
  4. package/dist/accessibility-auditor.js +57 -0
  5. package/dist/accessibility-auditor.js.map +7 -0
  6. package/dist/ai-login.d.ts +32 -0
  7. package/dist/ai-login.js +177 -0
  8. package/dist/ai-login.js.map +7 -0
  9. package/dist/auth-token-rotator.d.ts +47 -0
  10. package/dist/auth-token-rotator.js +58 -0
  11. package/dist/auth-token-rotator.js.map +7 -0
  12. package/dist/auth.d.ts +20 -0
  13. package/dist/auth.js +329 -0
  14. package/dist/auth.js.map +7 -0
  15. package/dist/browser-extension.d.ts +37 -0
  16. package/dist/browser-extension.js +113 -0
  17. package/dist/browser-extension.js.map +7 -0
  18. package/dist/captcha-solver.d.ts +16 -0
  19. package/dist/captcha-solver.js +105 -0
  20. package/dist/captcha-solver.js.map +7 -0
  21. package/dist/color-contrast.d.ts +30 -0
  22. package/dist/color-contrast.js +78 -0
  23. package/dist/color-contrast.js.map +7 -0
  24. package/dist/content-chunker.d.ts +18 -0
  25. package/dist/content-chunker.js +152 -0
  26. package/dist/content-chunker.js.map +7 -0
  27. package/dist/content-dedup.d.ts +23 -0
  28. package/dist/content-dedup.js +60 -0
  29. package/dist/content-dedup.js.map +7 -0
  30. package/dist/cookie-consent.d.ts +25 -0
  31. package/dist/cookie-consent.js +78 -0
  32. package/dist/cookie-consent.js.map +7 -0
  33. package/dist/cookie-jar.d.ts +20 -0
  34. package/dist/cookie-jar.js +61 -0
  35. package/dist/cookie-jar.js.map +7 -0
  36. package/dist/crawler.d.ts +179 -0
  37. package/dist/crawler.js +3672 -0
  38. package/dist/crawler.js.map +7 -0
  39. package/dist/custom-js-injector.d.ts +42 -0
  40. package/dist/custom-js-injector.js +50 -0
  41. package/dist/custom-js-injector.js.map +7 -0
  42. package/dist/dark-mode-capture.d.ts +23 -0
  43. package/dist/dark-mode-capture.js +75 -0
  44. package/dist/dark-mode-capture.js.map +7 -0
  45. package/dist/dead-link-detector.d.ts +37 -0
  46. package/dist/dead-link-detector.js +50 -0
  47. package/dist/dead-link-detector.js.map +7 -0
  48. package/dist/element-hash.d.ts +9 -0
  49. package/dist/element-hash.js +9 -0
  50. package/dist/element-hash.js.map +7 -0
  51. package/dist/email-otp.d.ts +42 -0
  52. package/dist/email-otp.js +177 -0
  53. package/dist/email-otp.js.map +7 -0
  54. package/dist/engine-router.d.ts +47 -0
  55. package/dist/engine-router.js +94 -0
  56. package/dist/engine-router.js.map +7 -0
  57. package/dist/export/bulk-export.d.ts +26 -0
  58. package/dist/export/bulk-export.js +76 -0
  59. package/dist/export/bulk-export.js.map +7 -0
  60. package/dist/export/vector-export.d.ts +76 -0
  61. package/dist/export/vector-export.js +85 -0
  62. package/dist/export/vector-export.js.map +7 -0
  63. package/dist/extractors/advanced.d.ts +54 -0
  64. package/dist/extractors/advanced.js +245 -0
  65. package/dist/extractors/advanced.js.map +7 -0
  66. package/dist/extractors/artifact-envelope.d.ts +63 -0
  67. package/dist/extractors/artifact-envelope.js +63 -0
  68. package/dist/extractors/artifact-envelope.js.map +7 -0
  69. package/dist/failure-analysis-agent.d.ts +16 -0
  70. package/dist/failure-analysis-agent.js +40 -0
  71. package/dist/failure-analysis-agent.js.map +7 -0
  72. package/dist/feature-flag-detector.d.ts +30 -0
  73. package/dist/feature-flag-detector.js +55 -0
  74. package/dist/feature-flag-detector.js.map +7 -0
  75. package/dist/form-detector.d.ts +34 -0
  76. package/dist/form-detector.js +58 -0
  77. package/dist/form-detector.js.map +7 -0
  78. package/dist/frontier.d.ts +32 -0
  79. package/dist/frontier.js +83 -0
  80. package/dist/frontier.js.map +7 -0
  81. package/dist/gap-pass.d.ts +35 -0
  82. package/dist/gap-pass.js +81 -0
  83. package/dist/gap-pass.js.map +7 -0
  84. package/dist/graphql-detector.d.ts +39 -0
  85. package/dist/graphql-detector.js +86 -0
  86. package/dist/graphql-detector.js.map +7 -0
  87. package/dist/http-engine.d.ts +80 -0
  88. package/dist/http-engine.js +304 -0
  89. package/dist/http-engine.js.map +7 -0
  90. package/dist/incremental-diff.d.ts +59 -0
  91. package/dist/incremental-diff.js +61 -0
  92. package/dist/incremental-diff.js.map +7 -0
  93. package/dist/index.d.ts +35 -0
  94. package/dist/index.js +61 -0
  95. package/dist/index.js.map +7 -0
  96. package/dist/input-fuzzer.d.ts +30 -0
  97. package/dist/input-fuzzer.js +105 -0
  98. package/dist/input-fuzzer.js.map +7 -0
  99. package/dist/interactive-probe.d.ts +104 -0
  100. package/dist/interactive-probe.js +442 -0
  101. package/dist/interactive-probe.js.map +7 -0
  102. package/dist/job-queue.d.ts +171 -0
  103. package/dist/job-queue.js +2256 -0
  104. package/dist/job-queue.js.map +7 -0
  105. package/dist/js-coverage.d.ts +29 -0
  106. package/dist/js-coverage.js +56 -0
  107. package/dist/js-coverage.js.map +7 -0
  108. package/dist/js-router-detector.d.ts +17 -0
  109. package/dist/js-router-detector.js +90 -0
  110. package/dist/js-router-detector.js.map +7 -0
  111. package/dist/language-detector.d.ts +22 -0
  112. package/dist/language-detector.js +57 -0
  113. package/dist/language-detector.js.map +7 -0
  114. package/dist/link-classifier.d.ts +23 -0
  115. package/dist/link-classifier.js +40 -0
  116. package/dist/link-classifier.js.map +7 -0
  117. package/dist/live-view.d.ts +36 -0
  118. package/dist/live-view.js +100 -0
  119. package/dist/live-view.js.map +7 -0
  120. package/dist/logger.d.ts +2 -0
  121. package/dist/logger.js +10 -0
  122. package/dist/logger.js.map +7 -0
  123. package/dist/markdown-extract.d.ts +8 -0
  124. package/dist/markdown-extract.js +54 -0
  125. package/dist/markdown-extract.js.map +7 -0
  126. package/dist/native-crawler.d.ts +69 -0
  127. package/dist/native-crawler.js +839 -0
  128. package/dist/native-crawler.js.map +7 -0
  129. package/dist/network-observer.d.ts +49 -0
  130. package/dist/network-observer.js +149 -0
  131. package/dist/network-observer.js.map +7 -0
  132. package/dist/network-throttle.d.ts +25 -0
  133. package/dist/network-throttle.js +34 -0
  134. package/dist/network-throttle.js.map +7 -0
  135. package/dist/openapi-generator.d.ts +49 -0
  136. package/dist/openapi-generator.js +89 -0
  137. package/dist/openapi-generator.js.map +7 -0
  138. package/dist/pdf-extractor.d.ts +28 -0
  139. package/dist/pdf-extractor.js +81 -0
  140. package/dist/pdf-extractor.js.map +7 -0
  141. package/dist/performance-metrics.d.ts +53 -0
  142. package/dist/performance-metrics.js +83 -0
  143. package/dist/performance-metrics.js.map +7 -0
  144. package/dist/phash.d.ts +38 -0
  145. package/dist/phash.js +115 -0
  146. package/dist/phash.js.map +7 -0
  147. package/dist/pii-scrubber.d.ts +21 -0
  148. package/dist/pii-scrubber.js +75 -0
  149. package/dist/pii-scrubber.js.map +7 -0
  150. package/dist/playwright-recorder.d.ts +39 -0
  151. package/dist/playwright-recorder.js +123 -0
  152. package/dist/playwright-recorder.js.map +7 -0
  153. package/dist/policies/crawl-safety.d.ts +9 -0
  154. package/dist/policies/crawl-safety.js +37 -0
  155. package/dist/policies/crawl-safety.js.map +7 -0
  156. package/dist/profiles/domain-profile.d.ts +19 -0
  157. package/dist/profiles/domain-profile.js +140 -0
  158. package/dist/profiles/domain-profile.js.map +7 -0
  159. package/dist/proxy.d.ts +50 -0
  160. package/dist/proxy.js +165 -0
  161. package/dist/proxy.js.map +7 -0
  162. package/dist/rate-limiter.d.ts +31 -0
  163. package/dist/rate-limiter.js +64 -0
  164. package/dist/rate-limiter.js.map +7 -0
  165. package/dist/redirect-tracker.d.ts +37 -0
  166. package/dist/redirect-tracker.js +65 -0
  167. package/dist/redirect-tracker.js.map +7 -0
  168. package/dist/redis-frontier.d.ts +48 -0
  169. package/dist/redis-frontier.js +126 -0
  170. package/dist/redis-frontier.js.map +7 -0
  171. package/dist/redis-queue.d.ts +48 -0
  172. package/dist/redis-queue.js +165 -0
  173. package/dist/redis-queue.js.map +7 -0
  174. package/dist/response-cache.d.ts +38 -0
  175. package/dist/response-cache.js +140 -0
  176. package/dist/response-cache.js.map +7 -0
  177. package/dist/responsive-detector.d.ts +29 -0
  178. package/dist/responsive-detector.js +46 -0
  179. package/dist/responsive-detector.js.map +7 -0
  180. package/dist/retry-policy.d.ts +20 -0
  181. package/dist/retry-policy.js +54 -0
  182. package/dist/retry-policy.js.map +7 -0
  183. package/dist/robots-txt.d.ts +9 -0
  184. package/dist/robots-txt.js +77 -0
  185. package/dist/robots-txt.js.map +7 -0
  186. package/dist/screen-reader.d.ts +16 -0
  187. package/dist/screen-reader.js +46 -0
  188. package/dist/screen-reader.js.map +7 -0
  189. package/dist/screenshot-masker.d.ts +28 -0
  190. package/dist/screenshot-masker.js +62 -0
  191. package/dist/screenshot-masker.js.map +7 -0
  192. package/dist/seo-checker.d.ts +33 -0
  193. package/dist/seo-checker.js +91 -0
  194. package/dist/seo-checker.js.map +7 -0
  195. package/dist/session-capture.d.ts +35 -0
  196. package/dist/session-capture.js +267 -0
  197. package/dist/session-capture.js.map +7 -0
  198. package/dist/session-export.d.ts +49 -0
  199. package/dist/session-export.js +117 -0
  200. package/dist/session-export.js.map +7 -0
  201. package/dist/sitemap-writer.d.ts +21 -0
  202. package/dist/sitemap-writer.js +47 -0
  203. package/dist/sitemap-writer.js.map +7 -0
  204. package/dist/sitemap.d.ts +51 -0
  205. package/dist/sitemap.js +152 -0
  206. package/dist/sitemap.js.map +7 -0
  207. package/dist/skyvern.d.ts +56 -0
  208. package/dist/skyvern.js +129 -0
  209. package/dist/skyvern.js.map +7 -0
  210. package/dist/sqs-queue.d.ts +2 -0
  211. package/dist/sqs-queue.js +47 -0
  212. package/dist/sqs-queue.js.map +7 -0
  213. package/dist/sso-token.d.ts +29 -0
  214. package/dist/sso-token.js +76 -0
  215. package/dist/sso-token.js.map +7 -0
  216. package/dist/stealth/human-behavior.d.ts +20 -0
  217. package/dist/stealth/human-behavior.js +168 -0
  218. package/dist/stealth/human-behavior.js.map +7 -0
  219. package/dist/telemetry.d.ts +34 -0
  220. package/dist/telemetry.js +77 -0
  221. package/dist/telemetry.js.map +7 -0
  222. package/dist/test-executor.d.ts +67 -0
  223. package/dist/test-executor.js +311 -0
  224. package/dist/test-executor.js.map +7 -0
  225. package/dist/tls-fingerprint.d.ts +50 -0
  226. package/dist/tls-fingerprint.js +81 -0
  227. package/dist/tls-fingerprint.js.map +7 -0
  228. package/dist/url-pattern-completer.d.ts +28 -0
  229. package/dist/url-pattern-completer.js +87 -0
  230. package/dist/url-pattern-completer.js.map +7 -0
  231. package/dist/visual-diff-verdict.d.ts +40 -0
  232. package/dist/visual-diff-verdict.js +25 -0
  233. package/dist/visual-diff-verdict.js.map +7 -0
  234. package/dist/webhook-notifier.d.ts +30 -0
  235. package/dist/webhook-notifier.js +76 -0
  236. package/dist/webhook-notifier.js.map +7 -0
  237. package/dist/websocket-detector.d.ts +24 -0
  238. package/dist/websocket-detector.js +60 -0
  239. package/dist/websocket-detector.js.map +7 -0
  240. package/dist/worker.d.ts +1 -0
  241. package/dist/worker.js +33 -0
  242. package/dist/worker.js.map +7 -0
  243. package/package.json +45 -0
  244. package/src/accessibility-auditor.ts +102 -0
  245. package/src/ai-login.ts +218 -0
  246. package/src/auth-token-rotator.ts +89 -0
  247. package/src/auth.ts +396 -0
  248. package/src/browser-extension.ts +186 -0
  249. package/src/captcha-solver.ts +143 -0
  250. package/src/color-contrast.ts +116 -0
  251. package/src/content-chunker.ts +170 -0
  252. package/src/content-dedup.ts +99 -0
  253. package/src/cookie-consent.ts +106 -0
  254. package/src/cookie-jar.ts +91 -0
  255. package/src/crawler.ts +4305 -0
  256. package/src/custom-js-injector.ts +93 -0
  257. package/src/dark-mode-capture.ts +110 -0
  258. package/src/dead-link-detector.ts +76 -0
  259. package/src/element-hash.ts +16 -0
  260. package/src/email-otp.ts +252 -0
  261. package/src/engine-router.ts +141 -0
  262. package/src/export/bulk-export.ts +85 -0
  263. package/src/export/vector-export.ts +160 -0
  264. package/src/extractors/advanced.ts +283 -0
  265. package/src/extractors/artifact-envelope.ts +111 -0
  266. package/src/failure-analysis-agent.ts +49 -0
  267. package/src/feature-flag-detector.ts +97 -0
  268. package/src/form-detector.ts +103 -0
  269. package/src/frontier.test.ts +64 -0
  270. package/src/frontier.ts +112 -0
  271. package/src/gap-pass.ts +122 -0
  272. package/src/graphql-detector.ts +129 -0
  273. package/src/http-engine.ts +433 -0
  274. package/src/incremental-diff.ts +121 -0
  275. package/src/index.ts +35 -0
  276. package/src/input-fuzzer.ts +129 -0
  277. package/src/interactive-probe.ts +623 -0
  278. package/src/job-queue.test.ts +120 -0
  279. package/src/job-queue.ts +2701 -0
  280. package/src/js-coverage.ts +87 -0
  281. package/src/js-router-detector.ts +112 -0
  282. package/src/language-detector.ts +82 -0
  283. package/src/link-classifier.ts +65 -0
  284. package/src/live-view.ts +141 -0
  285. package/src/logger.ts +9 -0
  286. package/src/markdown-extract.test.ts +73 -0
  287. package/src/markdown-extract.ts +76 -0
  288. package/src/native-crawler.ts +1106 -0
  289. package/src/network-observer.ts +204 -0
  290. package/src/network-throttle.ts +69 -0
  291. package/src/openapi-generator.ts +135 -0
  292. package/src/pdf-extractor.ts +123 -0
  293. package/src/performance-metrics.ts +134 -0
  294. package/src/phash.ts +151 -0
  295. package/src/pii-scrubber.ts +99 -0
  296. package/src/playwright-recorder.ts +170 -0
  297. package/src/policies/crawl-safety.ts +46 -0
  298. package/src/profiles/domain-profile.ts +167 -0
  299. package/src/proxy.ts +238 -0
  300. package/src/rate-limiter.ts +87 -0
  301. package/src/redirect-tracker.ts +97 -0
  302. package/src/redis-frontier.ts +143 -0
  303. package/src/redis-queue.ts +208 -0
  304. package/src/response-cache.ts +150 -0
  305. package/src/responsive-detector.ts +92 -0
  306. package/src/retry-policy.ts +87 -0
  307. package/src/robots-txt.ts +96 -0
  308. package/src/screen-reader.ts +89 -0
  309. package/src/screenshot-masker.ts +98 -0
  310. package/src/seo-checker.ts +134 -0
  311. package/src/session-capture.ts +325 -0
  312. package/src/session-export.test.ts +75 -0
  313. package/src/session-export.ts +172 -0
  314. package/src/sitemap-writer.ts +65 -0
  315. package/src/sitemap.test.ts +131 -0
  316. package/src/sitemap.ts +224 -0
  317. package/src/skyvern.ts +199 -0
  318. package/src/sqs-queue.ts +49 -0
  319. package/src/sso-token.ts +120 -0
  320. package/src/stealth/human-behavior.ts +191 -0
  321. package/src/telemetry.ts +122 -0
  322. package/src/test-executor.test.ts +72 -0
  323. package/src/test-executor.ts +424 -0
  324. package/src/tls-fingerprint.ts +111 -0
  325. package/src/url-pattern-completer.ts +114 -0
  326. package/src/visual-diff-verdict.test.ts +80 -0
  327. package/src/visual-diff-verdict.ts +57 -0
  328. package/src/webhook-notifier.ts +109 -0
  329. package/src/websocket-detector.ts +83 -0
  330. package/src/worker.ts +48 -0
  331. package/tsconfig.json +13 -0
  332. package/types/accessibility-auditor.d.ts +29 -0
  333. package/types/ai-login.d.ts +32 -0
  334. package/types/auth-token-rotator.d.ts +47 -0
  335. package/types/auth.d.ts +20 -0
  336. package/types/browser-extension.d.ts +37 -0
  337. package/types/bull-worker.d.ts +6 -0
  338. package/types/captcha-solver.d.ts +16 -0
  339. package/types/color-contrast.d.ts +30 -0
  340. package/types/content-chunker.d.ts +18 -0
  341. package/types/content-dedup.d.ts +23 -0
  342. package/types/cookie-consent.d.ts +25 -0
  343. package/types/cookie-jar.d.ts +20 -0
  344. package/types/crawler.d.ts +179 -0
  345. package/types/custom-js-injector.d.ts +42 -0
  346. package/types/dark-mode-capture.d.ts +23 -0
  347. package/types/dead-link-detector.d.ts +37 -0
  348. package/types/element-hash.d.ts +9 -0
  349. package/types/email-otp.d.ts +42 -0
  350. package/types/engine-router.d.ts +47 -0
  351. package/types/export/bulk-export.d.ts +26 -0
  352. package/types/export/vector-export.d.ts +76 -0
  353. package/types/extractors/advanced.d.ts +54 -0
  354. package/types/extractors/artifact-envelope.d.ts +63 -0
  355. package/types/failure-analysis-agent.d.ts +16 -0
  356. package/types/feature-flag-detector.d.ts +30 -0
  357. package/types/form-detector.d.ts +34 -0
  358. package/types/frontier.d.ts +32 -0
  359. package/types/gap-pass.d.ts +35 -0
  360. package/types/graphql-detector.d.ts +39 -0
  361. package/types/http-engine.d.ts +80 -0
  362. package/types/incremental-diff.d.ts +59 -0
  363. package/types/index.d.ts +35 -0
  364. package/types/input-fuzzer.d.ts +30 -0
  365. package/types/interactive-probe.d.ts +104 -0
  366. package/types/job-queue.d.ts +171 -0
  367. package/types/js-coverage.d.ts +29 -0
  368. package/types/js-router-detector.d.ts +17 -0
  369. package/types/language-detector.d.ts +22 -0
  370. package/types/link-classifier.d.ts +23 -0
  371. package/types/live-view.d.ts +36 -0
  372. package/types/logger.d.ts +2 -0
  373. package/types/markdown-extract.d.ts +8 -0
  374. package/types/native-crawler.d.ts +69 -0
  375. package/types/network-observer.d.ts +49 -0
  376. package/types/network-throttle.d.ts +25 -0
  377. package/types/openapi-generator.d.ts +49 -0
  378. package/types/pdf-extractor.d.ts +28 -0
  379. package/types/performance-metrics.d.ts +53 -0
  380. package/types/phash.d.ts +38 -0
  381. package/types/pii-scrubber.d.ts +21 -0
  382. package/types/playwright-recorder.d.ts +39 -0
  383. package/types/policies/crawl-safety.d.ts +9 -0
  384. package/types/profiles/domain-profile.d.ts +19 -0
  385. package/types/proxy.d.ts +50 -0
  386. package/types/rate-limiter.d.ts +31 -0
  387. package/types/redirect-tracker.d.ts +37 -0
  388. package/types/redis-frontier.d.ts +48 -0
  389. package/types/redis-queue.d.ts +48 -0
  390. package/types/response-cache.d.ts +38 -0
  391. package/types/responsive-detector.d.ts +29 -0
  392. package/types/retry-policy.d.ts +20 -0
  393. package/types/robots-txt.d.ts +9 -0
  394. package/types/screen-reader.d.ts +16 -0
  395. package/types/screenshot-masker.d.ts +28 -0
  396. package/types/seo-checker.d.ts +33 -0
  397. package/types/session-capture.d.ts +38 -0
  398. package/types/session-export.d.ts +49 -0
  399. package/types/sitemap-writer.d.ts +21 -0
  400. package/types/sitemap.d.ts +51 -0
  401. package/types/skyvern.d.ts +56 -0
  402. package/types/sqs-queue.d.ts +2 -0
  403. package/types/sso-token.d.ts +29 -0
  404. package/types/stealth/human-behavior.d.ts +20 -0
  405. package/types/telemetry.d.ts +34 -0
  406. package/types/test-executor.d.ts +67 -0
  407. package/types/tls-fingerprint.d.ts +50 -0
  408. package/types/url-pattern-completer.d.ts +28 -0
  409. package/types/visual-diff-verdict.d.ts +40 -0
  410. package/types/webhook-notifier.d.ts +30 -0
  411. package/types/websocket-detector.d.ts +24 -0
  412. package/types/worker.d.ts +1 -0
@@ -0,0 +1,218 @@
1
+ import { z } from 'zod';
2
+ import type { Stagehand } from '@browserbasehq/stagehand';
3
+ import { AppBrain } from '@detiq/app-brain';
4
+ import { solveCaptcha } from './captcha-solver.js';
5
+ import { waitForEmailContent } from './email-otp.js';
6
+ import { logger } from './logger.js';
7
+ import type { Credentials } from './auth.js';
8
+ import { simulateMouseMovement } from './stealth/human-behavior.js';
9
+
10
+ const CLICK_INTENT = /\b(click|tap|press|submit|select|choose|toggle)\b/i;
11
+
12
+ /** Simulate human mouse movement before click-type actions, then call stagehand.act(). */
13
+ async function actWithMouseSim(stagehand: Stagehand, instruction: string): Promise<any> {
14
+ if (CLICK_INTENT.test(instruction)) {
15
+ try {
16
+ const pg = await (stagehand as any).resolvePage();
17
+ const viewport = pg.viewportSize() ?? { width: 1280, height: 720 };
18
+ // Move mouse toward center of page with some randomness before Stagehand clicks
19
+ const targetX = viewport.width * 0.3 + Math.random() * viewport.width * 0.4;
20
+ const targetY = viewport.height * 0.2 + Math.random() * viewport.height * 0.5;
21
+ await simulateMouseMovement(pg, targetX, targetY);
22
+ } catch { /* non-fatal — don't block the action */ }
23
+ }
24
+ return stagehand.act(instruction);
25
+ }
26
+
27
+ /**
28
+ * Recorded login step from the Chrome extension's step recorder.
29
+ * {username} and {password} in `value` are substituted at replay time.
30
+ */
31
+ export interface LoginStep {
32
+ action: 'fill' | 'click' | 'wait';
33
+ selector: string;
34
+ value?: string;
35
+ ms?: number;
36
+ }
37
+
38
+ const TwoFASchema = z.object({
39
+ has2FA: z.boolean(),
40
+ fieldLabel: z.string().optional(),
41
+ });
42
+
43
+ const CAPTCHA_SELECTORS = [
44
+ 'iframe[src*="recaptcha"]',
45
+ 'iframe[src*="hcaptcha"]',
46
+ 'iframe[src*="challenges.cloudflare.com"]',
47
+ '.cf-turnstile',
48
+ '[data-sitekey]',
49
+ '#captcha',
50
+ '.captcha',
51
+ '[id*="captcha"]',
52
+ '[class*="captcha"]',
53
+ ];
54
+
55
+ /**
56
+ * AI-guided login using Stagehand act/extract.
57
+ *
58
+ * @param loginInstructions - Optional natural language description of the login flow.
59
+ * When provided, an initial stagehand.act() is called with these instructions to
60
+ * navigate the UI (e.g. "click Sign In in the nav, fill email, click Continue").
61
+ * The standard 3-step credential fill follows immediately after.
62
+ * @param opts - Optional tenant/project/job IDs for writing AUTH_STEP CrawlEvidence.
63
+ */
64
+ export async function loginWithStagehand(
65
+ stagehand: Stagehand,
66
+ creds: Credentials,
67
+ startUrl: string,
68
+ captchaOpts?: { apiKey?: string; provider?: '2captcha' | 'capsolver' },
69
+ emailOtpOpts?: { apiKey?: string; inboxId?: string },
70
+ loginInstructions?: string,
71
+ opts?: { tenantId?: string; projectId?: string; jobId?: string },
72
+ ): Promise<boolean> {
73
+ async function logStep(step: string, fn: () => Promise<any>): Promise<{ success: boolean; detail: string }> {
74
+ const t0 = Date.now();
75
+ try {
76
+ const r = await fn();
77
+ const result = { success: true, detail: r?.description ?? r?.message ?? step };
78
+ if (opts?.tenantId && opts?.projectId) {
79
+ AppBrain.saveCrawlEvidence(opts.tenantId, opts.projectId, {
80
+ jobId: opts?.jobId,
81
+ type: 'AUTH_STEP',
82
+ payload: { step, ...result, ms: Date.now() - t0 },
83
+ }).catch(() => {});
84
+ }
85
+ return result;
86
+ } catch (e: any) {
87
+ const result = { success: false, detail: e.message ?? 'failed' };
88
+ if (opts?.tenantId && opts?.projectId) {
89
+ AppBrain.saveCrawlEvidence(opts.tenantId, opts.projectId, {
90
+ jobId: opts?.jobId,
91
+ type: 'AUTH_STEP',
92
+ payload: { step, ...result, ms: Date.now() - t0 },
93
+ }).catch(() => {});
94
+ }
95
+ throw e;
96
+ }
97
+ }
98
+
99
+ try {
100
+ const pg = await (stagehand as any).resolvePage();
101
+
102
+ // CAPTCHA detection
103
+ for (const sel of CAPTCHA_SELECTORS) {
104
+ const count = await pg.locator(sel).count().catch(() => 0);
105
+ if (count > 0) {
106
+ logger.warn({ selector: sel }, '[crawler] CAPTCHA detected on login page');
107
+ if (captchaOpts?.apiKey) {
108
+ logger.info({ provider: captchaOpts.provider ?? '2captcha' }, '[crawler] Attempting auto-solve');
109
+ try {
110
+ const result = await solveCaptcha(pg, startUrl, captchaOpts.apiKey, captchaOpts.provider ?? '2captcha');
111
+ if (result) {
112
+ logger.info({ provider: result.provider }, '[crawler] CAPTCHA solved — proceeding with login');
113
+ break;
114
+ }
115
+ } catch (solveErr) {
116
+ logger.warn({ err: solveErr }, '[crawler] CAPTCHA solve failed — falling back to manual error');
117
+ throw new Error(`CAPTCHA_DETECTED: Auto-solve failed (${String(solveErr)}). Use session capture instead.`);
118
+ }
119
+ } else {
120
+ throw new Error(
121
+ 'CAPTCHA_DETECTED: Login page has a CAPTCHA. Use "Session Capture → Launch Browser" to log in manually, then capture the session. Alternatively, disable CAPTCHA for the ZeTa crawler IP.'
122
+ );
123
+ }
124
+ break;
125
+ }
126
+ }
127
+
128
+ // If caller provided natural language login instructions, use them to navigate to the login form
129
+ if (loginInstructions) {
130
+ await logStep('custom_recipe', () => actWithMouseSim(stagehand, loginInstructions));
131
+ await pg.waitForLoadState('networkidle').catch(() => pg.waitForTimeout(1500));
132
+ }
133
+
134
+ await logStep('fill_username', () => actWithMouseSim(stagehand, `Type "${creds.username}" into the username or email input field`));
135
+ await logStep('fill_password', () => actWithMouseSim(stagehand, `Type "${creds.password}" into the password input field`));
136
+ await logStep('click_submit', () => actWithMouseSim(stagehand, 'Click the sign in, log in, login, or continue button to submit credentials'));
137
+ await pg.waitForLoadState('networkidle').catch(() => pg.waitForTimeout(2000));
138
+
139
+ const twoFA = await stagehand.extract(
140
+ 'Is there a two-factor authentication, OTP, or verification code input visible right now?',
141
+ TwoFASchema as any
142
+ );
143
+
144
+ if (twoFA.has2FA) {
145
+ if (opts?.tenantId && opts?.projectId) {
146
+ AppBrain.saveCrawlEvidence(opts.tenantId, opts.projectId, {
147
+ jobId: opts?.jobId,
148
+ type: 'AUTH_STEP',
149
+ payload: { step: '2fa_detect', success: true, detail: twoFA.fieldLabel ?? '2FA detected', ms: 0 },
150
+ }).catch(() => {});
151
+ }
152
+
153
+ let code = creds.oneTimeCode ?? await creds.getOneTimeCode?.();
154
+
155
+ if (!code && emailOtpOpts?.apiKey && emailOtpOpts?.inboxId) {
156
+ const since = new Date().toISOString();
157
+ logger.info('[crawler] 2FA detected — waiting for email OTP or magic link via MailSlurp');
158
+ const emailContent = await waitForEmailContent(
159
+ { provider: 'mailslurp', apiKey: emailOtpOpts.apiKey, inboxId: emailOtpOpts.inboxId },
160
+ since
161
+ );
162
+ if (emailContent?.magicLink) {
163
+ logger.info({ url: emailContent.magicLink }, '[crawler] Magic link received — navigating');
164
+ await pg.goto(emailContent.magicLink, { waitUntil: 'domcontentloaded' });
165
+ await pg.waitForLoadState('networkidle').catch(() => {});
166
+ return true; // magic link navigation = login complete
167
+ }
168
+ code = emailContent?.otp ?? undefined;
169
+ if (code) {
170
+ logger.info('[crawler] Email OTP received');
171
+ } else {
172
+ logger.warn('[crawler] Email OTP timed out after 60s');
173
+ }
174
+ }
175
+
176
+ if (!code) {
177
+ console.warn('[crawler] 2FA detected but no oneTimeCode provided');
178
+ return false;
179
+ }
180
+ await logStep('2fa_inject', () => actWithMouseSim(stagehand, `Enter "${code}" into the ${twoFA.fieldLabel ?? 'verification code'} field`));
181
+ await logStep('2fa_submit', () => actWithMouseSim(stagehand, 'Click verify or confirm to submit the verification code'));
182
+ await pg.waitForLoadState('networkidle').catch(() => pg.waitForTimeout(2000));
183
+ }
184
+
185
+ // URL-based success check
186
+ const currentUrl = pg.url();
187
+ const loginPathPattern = /\/(login|signin|sign-in|auth|authenticate|log-in|account\/login)(\?|$|\/)/i;
188
+ try {
189
+ const { pathname } = new URL(currentUrl);
190
+ const wasRedirected = currentUrl !== startUrl;
191
+ const stillOnLoginPath = loginPathPattern.test(pathname);
192
+ if (wasRedirected && !stillOnLoginPath) {
193
+ logger.info({ url: currentUrl }, 'Login confirmed via URL change');
194
+ return true;
195
+ }
196
+ } catch { /* invalid URL — fall through */ }
197
+
198
+ const passwordCount = await pg.locator('input[type="password"]').count().catch(() => 1);
199
+ const bodyText = await pg.evaluate(() => (document as any).body?.innerText?.trim()?.length ?? 0).catch(() => 0);
200
+ if (passwordCount === 0 && bodyText > 30) {
201
+ logger.info('Login confirmed: password field no longer visible');
202
+ return true;
203
+ }
204
+
205
+ logger.warn({ url: currentUrl }, 'Login appears failed');
206
+ try {
207
+ const screenshotPath = `/tmp/zeta-auth-failure-${Date.now()}.jpg`;
208
+ await pg.screenshot({ path: screenshotPath, type: 'jpeg', quality: 70, fullPage: false });
209
+ logger.warn({ screenshotPath }, '[crawler] Auth failure screenshot saved');
210
+ } catch { /* non-fatal */ }
211
+ return false;
212
+ } catch (err) {
213
+ const msg = String((err as Error).message ?? err);
214
+ if (msg.includes('CAPTCHA_DETECTED')) throw err;
215
+ logger.error({ err }, 'Login error');
216
+ return false;
217
+ }
218
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Auth token rotator — manage a pool of authenticated sessions for multi-account crawls.
3
+ *
4
+ * Use case: crawl the same app as admin+user+guest in one crawl job to discover
5
+ * role-specific screens that only appear when authenticated with different privileges.
6
+ * Each session is a Playwright storageState JSON string.
7
+ *
8
+ * Sessions are checked out (locked) before use and returned after the page is done.
9
+ * Round-robin assignment with stale-session eviction after maxAgeMs.
10
+ */
11
+
12
+ export interface SessionEntry {
13
+ id: string;
14
+ role: string; // 'admin' | 'user' | 'guest' | custom label
15
+ storageState: string; // Playwright StorageState JSON
16
+ createdAt: number; // Date.now()
17
+ checkedOutAt: number | null;
18
+ requestCount: number;
19
+ }
20
+
21
+ export interface SessionPoolOpts {
22
+ sessions: Array<{ role: string; storageState: string }>;
23
+ maxAgeMs?: number; // evict sessions older than this (default: 4h)
24
+ maxRequestsPerSession?: number; // rotate after N requests (default: 100)
25
+ }
26
+
27
+ export class AuthTokenRotator {
28
+ private pool: SessionEntry[];
29
+ private readonly maxAgeMs: number;
30
+ private readonly maxRequests: number;
31
+
32
+ constructor(opts: SessionPoolOpts) {
33
+ this.maxAgeMs = opts.maxAgeMs ?? 4 * 60 * 60 * 1000;
34
+ this.maxRequests = opts.maxRequestsPerSession ?? 100;
35
+ this.pool = opts.sessions.map((s, i) => ({
36
+ id: `session-${i}-${s.role}`,
37
+ role: s.role,
38
+ storageState: s.storageState,
39
+ createdAt: Date.now(),
40
+ checkedOutAt: null,
41
+ requestCount: 0,
42
+ }));
43
+ }
44
+
45
+ /** Get the next available session. Returns null if all are checked out. */
46
+ checkout(): SessionEntry | null {
47
+ const now = Date.now();
48
+ const available = this.pool.filter(s =>
49
+ !s.checkedOutAt &&
50
+ now - s.createdAt < this.maxAgeMs &&
51
+ s.requestCount < this.maxRequests,
52
+ );
53
+ if (available.length === 0) return null;
54
+ // Round-robin: pick lowest requestCount
55
+ available.sort((a, b) => a.requestCount - b.requestCount);
56
+ const session = available[0];
57
+ session.checkedOutAt = now;
58
+ return session;
59
+ }
60
+
61
+ /** Return a session to the pool after use. */
62
+ checkin(sessionId: string, updatedStorageState?: string): void {
63
+ const s = this.pool.find(e => e.id === sessionId);
64
+ if (!s) return;
65
+ s.checkedOutAt = null;
66
+ s.requestCount++;
67
+ if (updatedStorageState) s.storageState = updatedStorageState;
68
+ }
69
+
70
+ /** Get all available sessions (for parallel crawl setup). */
71
+ getAll(): ReadonlyArray<SessionEntry> {
72
+ return this.pool;
73
+ }
74
+
75
+ /** Stats for monitoring. */
76
+ stats(): { total: number; available: number; checkedOut: number; stale: number } {
77
+ const now = Date.now();
78
+ const stale = this.pool.filter(s => now - s.createdAt >= this.maxAgeMs || s.requestCount >= this.maxRequests).length;
79
+ const checkedOut = this.pool.filter(s => !!s.checkedOutAt).length;
80
+ return { total: this.pool.length, available: this.pool.length - checkedOut - stale, checkedOut, stale };
81
+ }
82
+
83
+ /** Add a new session to the pool at runtime. */
84
+ addSession(role: string, storageState: string): string {
85
+ const id = `session-${this.pool.length}-${role}`;
86
+ this.pool.push({ id, role, storageState, createdAt: Date.now(), checkedOutAt: null, requestCount: 0 });
87
+ return id;
88
+ }
89
+ }