@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,267 @@
1
+ import { chromium } from "playwright";
2
+ import { login } from "./auth.js";
3
+ import { generateTOTP } from "./job-queue.js";
4
+ const activeSessions = /* @__PURE__ */ new Map();
5
+ async function startSessionCapture(appUrl) {
6
+ const inDocker = process.env.PLAYWRIGHT_IN_DOCKER === "true";
7
+ const noDisplay = process.platform === "linux" && !process.env.DISPLAY;
8
+ if (inDocker || noDisplay) {
9
+ throw new Error("HEADLESS_ENV");
10
+ }
11
+ const id = Math.random().toString(36).slice(2) + Date.now().toString(36);
12
+ const browser = await chromium.launch({ headless: false });
13
+ const context = await browser.newContext();
14
+ const page = await context.newPage();
15
+ await page.goto(appUrl, { waitUntil: "domcontentloaded" }).catch(() => {
16
+ });
17
+ const timer = setTimeout(() => cancelSessionCapture(id).catch(() => {
18
+ }), 10 * 60 * 1e3);
19
+ activeSessions.set(id, { browser, context, timer });
20
+ return id;
21
+ }
22
+ async function completeSessionCapture(captureId) {
23
+ const session = activeSessions.get(captureId);
24
+ if (!session) throw new Error("Capture session not found or expired.");
25
+ clearTimeout(session.timer);
26
+ const state = await session.context.storageState();
27
+ await session.browser.close().catch(() => {
28
+ });
29
+ activeSessions.delete(captureId);
30
+ return JSON.stringify(state);
31
+ }
32
+ async function cancelSessionCapture(captureId) {
33
+ const session = activeSessions.get(captureId);
34
+ if (!session) return;
35
+ clearTimeout(session.timer);
36
+ await session.browser.close().catch(() => {
37
+ });
38
+ activeSessions.delete(captureId);
39
+ }
40
+ async function exploreAppAfterLogin(page) {
41
+ const sleep = (ms) => page.waitForTimeout(ms).catch(() => {
42
+ });
43
+ const isVisible = `
44
+ function isVisible(el) {
45
+ const s = window.getComputedStyle(el);
46
+ if (s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) < 0.1) return false;
47
+ const r = el.getBoundingClientRect();
48
+ return r.width > 4 && r.height > 4;
49
+ }
50
+ `;
51
+ try {
52
+ const origin = await page.evaluate(() => location.origin).catch(() => "");
53
+ const startUrl = await page.evaluate(() => location.href).catch(() => "");
54
+ await page.evaluate(new Function(`
55
+ ${isVisible}
56
+ const inBadCtx = el => {
57
+ let p = el.parentElement;
58
+ while (p) {
59
+ const role = p.getAttribute('role') ?? '';
60
+ if (['dialog','alertdialog','combobox','listbox'].includes(role)) return true;
61
+ p = p.parentElement;
62
+ }
63
+ return false;
64
+ };
65
+ const targets = new Set();
66
+ document.querySelectorAll('[aria-haspopup]:not([aria-expanded="true"])').forEach(el => {
67
+ if (isVisible(el) && !inBadCtx(el)) targets.add(el);
68
+ });
69
+ document.querySelectorAll('[aria-expanded="false"]').forEach(el => {
70
+ if ((el.tagName === 'BUTTON' || el.getAttribute('role') === 'button') && isVisible(el) && !inBadCtx(el)) targets.add(el);
71
+ });
72
+ document.querySelectorAll('[data-state="closed"],[data-headlessui-state="closed"]').forEach(el => {
73
+ if ((el.tagName === 'BUTTON' || el.getAttribute('role') === 'button') && isVisible(el) && !inBadCtx(el)) targets.add(el);
74
+ });
75
+ targets.forEach(el => { try { el.click(); } catch {} });
76
+ `)).catch(() => {
77
+ });
78
+ await sleep(600);
79
+ const navLinks = await page.evaluate(new Function(`
80
+ ${isVisible}
81
+ const links = new Set();
82
+ const navEls = document.querySelectorAll('nav a, [role="navigation"] a, aside a, header a');
83
+ navEls.forEach(a => {
84
+ const href = a.getAttribute('href');
85
+ if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) return;
86
+ if (!isVisible(a)) return;
87
+ try {
88
+ const url = new URL(href, location.href);
89
+ if (url.origin === location.origin) links.add(url.href);
90
+ } catch {}
91
+ });
92
+ return [...links].slice(0, 10); // cap at 10 nav pages
93
+ `)).catch(() => []);
94
+ await page.keyboard?.press("Escape").catch(() => {
95
+ });
96
+ await sleep(200);
97
+ for (const link of navLinks) {
98
+ try {
99
+ if (link === startUrl) continue;
100
+ await page.goto(link, { waitUntil: "networkidle", timeout: 15e3 }).catch(() => {
101
+ });
102
+ await sleep(400);
103
+ await interactWithCurrentPage(page, isVisible, sleep);
104
+ } catch {
105
+ }
106
+ }
107
+ if (navLinks.length > 0) {
108
+ await page.goto(startUrl, { waitUntil: "networkidle", timeout: 15e3 }).catch(() => {
109
+ });
110
+ await sleep(400);
111
+ }
112
+ await interactWithCurrentPage(page, isVisible, sleep);
113
+ await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => {
114
+ });
115
+ } catch {
116
+ }
117
+ }
118
+ async function interactWithCurrentPage(page, isVisible, sleep) {
119
+ await page.evaluate(new Function(`
120
+ ${isVisible}
121
+ const tabs = [...document.querySelectorAll('[role="tab"]')].filter(isVisible);
122
+ tabs.forEach(tab => { try { tab.click(); } catch {} });
123
+ `)).catch(() => {
124
+ });
125
+ await sleep(400);
126
+ await page.evaluate(new Function(`
127
+ ${isVisible}
128
+ const nextBtns = [...document.querySelectorAll(
129
+ 'button[aria-label*="next" i], button[aria-label*="forward" i], button[aria-label*="right" i], ' +
130
+ '[class*="carousel" i] button, [class*="slider" i] button, [data-direction="next"]'
131
+ )].filter(isVisible);
132
+ // Click next up to 3 times per carousel
133
+ for (let i = 0; i < 3; i++) {
134
+ nextBtns.forEach(btn => { try { btn.click(); } catch {} });
135
+ }
136
+ `)).catch(() => {
137
+ });
138
+ await sleep(400);
139
+ await page.evaluate(new Function(`
140
+ ${isVisible}
141
+ document.querySelectorAll(
142
+ '[data-state="closed"] button, [aria-expanded="false"], ' +
143
+ 'details:not([open]) > summary, [class*="accordion" i] button'
144
+ ).forEach(el => {
145
+ if (isVisible(el)) { try { el.click(); } catch {} }
146
+ });
147
+ `)).catch(() => {
148
+ });
149
+ await sleep(300);
150
+ await page.evaluate(() => {
151
+ window.scrollTo({ top: document.body.scrollHeight, behavior: "instant" });
152
+ }).catch(() => {
153
+ });
154
+ await sleep(300);
155
+ await page.evaluate(() => {
156
+ window.scrollTo({ top: 0, behavior: "instant" });
157
+ }).catch(() => {
158
+ });
159
+ }
160
+ async function validateAndCaptureSession(appUrl, creds, llmConfig, captchaOpts) {
161
+ const browser = await chromium.launch({ headless: true });
162
+ try {
163
+ const contextOpts = {};
164
+ if (creds.httpBasicUsername && creds.httpBasicPassword) {
165
+ contextOpts.httpCredentials = { username: creds.httpBasicUsername, password: creds.httpBasicPassword };
166
+ }
167
+ const context = await browser.newContext(contextOpts);
168
+ const page = await context.newPage();
169
+ const getOneTimeCode = creds.totpSecret ? async () => generateTOTP(creds.totpSecret) : void 0;
170
+ let loggedIn;
171
+ try {
172
+ loggedIn = await login(page, appUrl, {
173
+ username: creds.username,
174
+ password: creds.password,
175
+ getOneTimeCode
176
+ });
177
+ } catch (loginErr) {
178
+ const shotBuf = await page.screenshot({ type: "jpeg", quality: 60, fullPage: false }).catch(() => null);
179
+ throw Object.assign(loginErr, {
180
+ failureScreenshot: shotBuf ? shotBuf.toString("base64") : null
181
+ });
182
+ }
183
+ if (!loggedIn && llmConfig) {
184
+ let sh = null;
185
+ try {
186
+ const { Stagehand } = await import("@browserbasehq/stagehand");
187
+ const { loginWithStagehand } = await import("./ai-login.js");
188
+ sh = new Stagehand({
189
+ env: "LOCAL",
190
+ model: { modelName: llmConfig.modelName, apiKey: llmConfig.apiKey },
191
+ serverCache: false,
192
+ verbose: 0,
193
+ localBrowserLaunchOptions: { headless: true }
194
+ });
195
+ await sh.init();
196
+ const shPage = await sh.resolvePage();
197
+ await shPage.goto(appUrl, { waitUntil: "networkidle", timeout: 3e4 }).catch(() => {
198
+ });
199
+ const shLoggedIn = await loginWithStagehand(
200
+ sh,
201
+ { username: creds.username, password: creds.password, getOneTimeCode },
202
+ appUrl,
203
+ captchaOpts
204
+ );
205
+ if (shLoggedIn) {
206
+ const shCtx = sh.context ?? sh.browserContext;
207
+ if (shCtx) {
208
+ const shState = await shCtx.storageState();
209
+ const shCookies = shState.cookies?.length ?? 0;
210
+ const shLocalStorage = shState.origins?.reduce((s, o) => s + (o.localStorage?.length ?? 0), 0) ?? 0;
211
+ if (shCookies > 0 || shLocalStorage > 0) {
212
+ await sh.close().catch(() => {
213
+ });
214
+ sh = null;
215
+ return { storageState: JSON.stringify(shState), cookies: shCookies, localStorage: shLocalStorage };
216
+ }
217
+ }
218
+ loggedIn = true;
219
+ }
220
+ } catch (shErr) {
221
+ const msg = String(shErr?.message ?? shErr);
222
+ if (msg.includes("CAPTCHA") || shErr?.isSSOError) throw shErr;
223
+ } finally {
224
+ if (sh) await sh.close().catch(() => {
225
+ });
226
+ }
227
+ }
228
+ if (!loggedIn) {
229
+ const shotBuf = await page.screenshot({ type: "jpeg", quality: 60, fullPage: false }).catch(() => null);
230
+ const challenge = await page.evaluate(() => {
231
+ const text = (document.body?.innerText ?? "").toLowerCase();
232
+ const hasCloudflare = /verify you are human|checking your browser|cloudflare|cf-challenge|turnstile/.test(text) || !!document.querySelector('iframe[src*="challenges.cloudflare.com"], .cf-turnstile, [data-sitekey]');
233
+ const hasCaptcha = hasCloudflare || /captcha|recaptcha|hcaptcha/.test(text) || !!document.querySelector('iframe[src*="recaptcha"], iframe[src*="hcaptcha"], #captcha, .captcha, [id*="captcha"], [class*="captcha"]');
234
+ return { hasCloudflare, hasCaptcha };
235
+ }).catch(() => ({ hasCloudflare: false, hasCaptcha: false }));
236
+ const message = challenge.hasCloudflare ? "Cloudflare human verification blocked headless validation. This cannot be bypassed reliably in code; use Live Browser or manual session capture, configure a CAPTCHA solver, or allowlist the crawler IP." : challenge.hasCaptcha ? "A CAPTCHA blocked headless validation. Configure CAPTCHA Auto-Solve or use Live Browser/manual session capture." : "Login failed \u2014 credentials rejected or the login flow requires a manual/SSO step. Check credentials, saved login instructions, or use Live Browser session capture.";
237
+ const err = Object.assign(new Error(message), {
238
+ failureScreenshot: shotBuf ? shotBuf.toString("base64") : null,
239
+ reason: challenge.hasCloudflare ? "CLOUDFLARE_CHALLENGE" : challenge.hasCaptcha ? "CAPTCHA_CHALLENGE" : "LOGIN_NOT_CONFIRMED"
240
+ });
241
+ throw err;
242
+ }
243
+ await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => {
244
+ });
245
+ await exploreAppAfterLogin(page);
246
+ const state = await context.storageState();
247
+ const cookies = state.cookies?.length ?? 0;
248
+ const localStorage = state.origins?.reduce(
249
+ (sum, o) => sum + (o.localStorage?.length ?? 0),
250
+ 0
251
+ ) ?? 0;
252
+ if (cookies === 0 && localStorage === 0) {
253
+ throw new Error("Login appeared to succeed but no session data was captured \u2014 try manual session capture instead");
254
+ }
255
+ return { storageState: JSON.stringify(state), cookies, localStorage };
256
+ } finally {
257
+ await browser.close().catch(() => {
258
+ });
259
+ }
260
+ }
261
+ export {
262
+ cancelSessionCapture,
263
+ completeSessionCapture,
264
+ startSessionCapture,
265
+ validateAndCaptureSession
266
+ };
267
+ //# sourceMappingURL=session-capture.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/session-capture.ts"],
4
+ "sourcesContent": ["import { chromium } from 'playwright';\nimport { login } from './auth.js';\nimport { generateTOTP } from './job-queue.js';\n\ninterface CaptureSession {\n browser: any;\n context: any;\n timer: ReturnType<typeof setTimeout>;\n}\n\nconst activeSessions = new Map<string, CaptureSession>();\n\n/**\n * Opens a headed (visible) browser at appUrl and returns a captureId.\n * The user logs in manually, then calls completeSessionCapture() to harvest storageState.\n * Sessions expire after 10 minutes if not completed.\n *\n * Throws 'HEADLESS_ENV' if running in Docker/Linux without a display server.\n * The API route surfaces this as a 503 so the frontend can switch to manual JSON paste.\n */\nexport async function startSessionCapture(appUrl: string): Promise<string> {\n const inDocker = process.env.PLAYWRIGHT_IN_DOCKER === 'true';\n const noDisplay = process.platform === 'linux' && !process.env.DISPLAY;\n if (inDocker || noDisplay) {\n throw new Error('HEADLESS_ENV');\n }\n\n const id = Math.random().toString(36).slice(2) + Date.now().toString(36);\n const browser = await chromium.launch({ headless: false });\n const context = await browser.newContext();\n const page = await context.newPage();\n await page.goto(appUrl, { waitUntil: 'domcontentloaded' }).catch(() => {});\n const timer = setTimeout(() => cancelSessionCapture(id).catch(() => {}), 10 * 60 * 1000);\n activeSessions.set(id, { browser, context, timer });\n return id;\n}\n\n/** Captures storageState from the open browser, closes it, and returns the JSON string. */\nexport async function completeSessionCapture(captureId: string): Promise<string> {\n const session = activeSessions.get(captureId);\n if (!session) throw new Error('Capture session not found or expired.');\n clearTimeout(session.timer);\n const state = await session.context.storageState();\n await session.browser.close().catch(() => {});\n activeSessions.delete(captureId);\n return JSON.stringify(state);\n}\n\n/** Closes the browser without capturing \u2014 called on Cancel. */\nexport async function cancelSessionCapture(captureId: string): Promise<void> {\n const session = activeSessions.get(captureId);\n if (!session) return;\n clearTimeout(session.timer);\n await session.browser.close().catch(() => {});\n activeSessions.delete(captureId);\n}\n\n/**\n * Post-login exploration: navigates nav links, cycles tabs, advances carousels,\n * opens accordions, scrolls \u2014 so the browser accumulates full session state before capture.\n * All best-effort: never throws, never fails the capture.\n */\nasync function exploreAppAfterLogin(page: any): Promise<void> {\n const sleep = (ms: number) => page.waitForTimeout(ms).catch(() => {});\n\n const isVisible = `\n function isVisible(el) {\n const s = window.getComputedStyle(el);\n if (s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) < 0.1) return false;\n const r = el.getBoundingClientRect();\n return r.width > 4 && r.height > 4;\n }\n `;\n\n try {\n const origin = await page.evaluate(() => location.origin).catch(() => '');\n const startUrl = await page.evaluate(() => location.href).catch(() => '');\n\n // \u2500\u2500 1. Expand collapsed nav menus / hamburgers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await page.evaluate(new Function(`\n ${isVisible}\n const inBadCtx = el => {\n let p = el.parentElement;\n while (p) {\n const role = p.getAttribute('role') ?? '';\n if (['dialog','alertdialog','combobox','listbox'].includes(role)) return true;\n p = p.parentElement;\n }\n return false;\n };\n const targets = new Set();\n document.querySelectorAll('[aria-haspopup]:not([aria-expanded=\"true\"])').forEach(el => {\n if (isVisible(el) && !inBadCtx(el)) targets.add(el);\n });\n document.querySelectorAll('[aria-expanded=\"false\"]').forEach(el => {\n if ((el.tagName === 'BUTTON' || el.getAttribute('role') === 'button') && isVisible(el) && !inBadCtx(el)) targets.add(el);\n });\n document.querySelectorAll('[data-state=\"closed\"],[data-headlessui-state=\"closed\"]').forEach(el => {\n if ((el.tagName === 'BUTTON' || el.getAttribute('role') === 'button') && isVisible(el) && !inBadCtx(el)) targets.add(el);\n });\n targets.forEach(el => { try { el.click(); } catch {} });\n `) as () => void).catch(() => {});\n await sleep(600);\n\n // \u2500\u2500 2. Collect nav links from the expanded DOM \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const navLinks: string[] = await page.evaluate(new Function(`\n ${isVisible}\n const links = new Set();\n const navEls = document.querySelectorAll('nav a, [role=\"navigation\"] a, aside a, header a');\n navEls.forEach(a => {\n const href = a.getAttribute('href');\n if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) return;\n if (!isVisible(a)) return;\n try {\n const url = new URL(href, location.href);\n if (url.origin === location.origin) links.add(url.href);\n } catch {}\n });\n return [...links].slice(0, 10); // cap at 10 nav pages\n `) as () => string[]).catch(() => []);\n\n // Dismiss opened menus before navigating\n await page.keyboard?.press('Escape').catch(() => {});\n await sleep(200);\n\n // \u2500\u2500 3. Visit each nav link, interact, return \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n for (const link of navLinks) {\n try {\n if (link === startUrl) continue;\n await page.goto(link, { waitUntil: 'networkidle', timeout: 15_000 }).catch(() => {});\n await sleep(400);\n await interactWithCurrentPage(page, isVisible, sleep);\n } catch { /* skip broken page */ }\n }\n\n // Return to start page\n if (navLinks.length > 0) {\n await page.goto(startUrl, { waitUntil: 'networkidle', timeout: 15_000 }).catch(() => {});\n await sleep(400);\n }\n\n // \u2500\u2500 4. Final interaction pass on landing page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await interactWithCurrentPage(page, isVisible, sleep);\n\n await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => {});\n } catch {\n // Exploration is best-effort\n }\n}\n\n/** Interacts with tabs, carousels, accordions, and scrolls on the current page. */\nasync function interactWithCurrentPage(page: any, isVisible: string, sleep: (ms: number) => Promise<void>): Promise<void> {\n // Cycle through tabs\n await page.evaluate(new Function(`\n ${isVisible}\n const tabs = [...document.querySelectorAll('[role=\"tab\"]')].filter(isVisible);\n tabs.forEach(tab => { try { tab.click(); } catch {} });\n `) as () => void).catch(() => {});\n await sleep(400);\n\n // Advance carousels (next buttons)\n await page.evaluate(new Function(`\n ${isVisible}\n const nextBtns = [...document.querySelectorAll(\n 'button[aria-label*=\"next\" i], button[aria-label*=\"forward\" i], button[aria-label*=\"right\" i], ' +\n '[class*=\"carousel\" i] button, [class*=\"slider\" i] button, [data-direction=\"next\"]'\n )].filter(isVisible);\n // Click next up to 3 times per carousel\n for (let i = 0; i < 3; i++) {\n nextBtns.forEach(btn => { try { btn.click(); } catch {} });\n }\n `) as () => void).catch(() => {});\n await sleep(400);\n\n // Open closed accordions / disclosure panels\n await page.evaluate(new Function(`\n ${isVisible}\n document.querySelectorAll(\n '[data-state=\"closed\"] button, [aria-expanded=\"false\"], ' +\n 'details:not([open]) > summary, [class*=\"accordion\" i] button'\n ).forEach(el => {\n if (isVisible(el)) { try { el.click(); } catch {} }\n });\n `) as () => void).catch(() => {});\n await sleep(300);\n\n // Scroll to bottom then back to top\n await page.evaluate(() => { window.scrollTo({ top: document.body.scrollHeight, behavior: 'instant' }); }).catch(() => {});\n await sleep(300);\n await page.evaluate(() => { window.scrollTo({ top: 0, behavior: 'instant' }); }).catch(() => {});\n}\n\n/**\n * Headless login + session capture \u2014 works on Docker/cloud (no display needed).\n * Tries DOM-based login first (fast, free). If it fails and llmConfig is provided,\n * falls back to Stagehand AI-guided login (handles modals, multi-step, marketing pages).\n *\n * Returns { storageState, cookies, localStorage } on success.\n * Throws a human-readable error on failure.\n */\nexport async function validateAndCaptureSession(\n appUrl: string,\n creds: {\n username: string; password: string; totpSecret?: string;\n httpBasicUsername?: string; httpBasicPassword?: string;\n },\n llmConfig?: { modelName: string; apiKey: string },\n captchaOpts?: { apiKey?: string; provider?: '2captcha' | 'capsolver' },\n): Promise<{ storageState: string; cookies: number; localStorage: number }> {\n const browser = await chromium.launch({ headless: true });\n try {\n const contextOpts: any = {};\n if (creds.httpBasicUsername && creds.httpBasicPassword) {\n contextOpts.httpCredentials = { username: creds.httpBasicUsername, password: creds.httpBasicPassword };\n }\n const context = await browser.newContext(contextOpts);\n const page = await context.newPage();\n\n const getOneTimeCode = creds.totpSecret\n ? async () => generateTOTP(creds.totpSecret!)\n : undefined;\n\n let loggedIn: boolean;\n try {\n loggedIn = await login(page, appUrl, {\n username: creds.username,\n password: creds.password,\n getOneTimeCode,\n });\n } catch (loginErr: any) {\n // login() throws when it can read a real error message from the page\n const shotBuf = await page.screenshot({ type: 'jpeg', quality: 60, fullPage: false }).catch(() => null);\n throw Object.assign(loginErr, {\n failureScreenshot: shotBuf ? shotBuf.toString('base64') : null,\n });\n }\n\n // Stagehand AI fallback when DOM login fails and LLM config is available\n if (!loggedIn && llmConfig) {\n let sh: any = null;\n try {\n const { Stagehand } = await import('@browserbasehq/stagehand');\n const { loginWithStagehand } = await import('./ai-login.js');\n sh = new Stagehand({\n env: 'LOCAL',\n model: { modelName: llmConfig.modelName as any, apiKey: llmConfig.apiKey },\n serverCache: false,\n verbose: 0,\n localBrowserLaunchOptions: { headless: true },\n });\n await sh.init();\n const shPage = await (sh as any).resolvePage();\n await shPage.goto(appUrl, { waitUntil: 'networkidle', timeout: 30_000 }).catch(() => {});\n const shLoggedIn = await loginWithStagehand(\n sh,\n { username: creds.username, password: creds.password, getOneTimeCode },\n appUrl,\n captchaOpts,\n );\n if (shLoggedIn) {\n const shCtx = (sh as any).context ?? (sh as any).browserContext;\n if (shCtx) {\n const shState = await shCtx.storageState();\n const shCookies = shState.cookies?.length ?? 0;\n const shLocalStorage = shState.origins?.reduce((s: number, o: any) => s + (o.localStorage?.length ?? 0), 0) ?? 0;\n if (shCookies > 0 || shLocalStorage > 0) {\n await sh.close().catch(() => {});\n sh = null;\n return { storageState: JSON.stringify(shState), cookies: shCookies, localStorage: shLocalStorage };\n }\n }\n loggedIn = true; // Stagehand succeeded but we'll capture from the plain browser context below\n }\n } catch (shErr: any) {\n const msg = String(shErr?.message ?? shErr);\n if (msg.includes('CAPTCHA') || shErr?.isSSOError) throw shErr;\n // Stagehand failed silently \u2014 fall through to \"credentials rejected\"\n } finally {\n if (sh) await sh.close().catch(() => {});\n }\n }\n\n if (!loggedIn) {\n const shotBuf = await page.screenshot({ type: 'jpeg', quality: 60, fullPage: false }).catch(() => null);\n const challenge = await page.evaluate(() => {\n const text = (document.body?.innerText ?? '').toLowerCase();\n const hasCloudflare = /verify you are human|checking your browser|cloudflare|cf-challenge|turnstile/.test(text) ||\n !!document.querySelector('iframe[src*=\"challenges.cloudflare.com\"], .cf-turnstile, [data-sitekey]');\n const hasCaptcha = hasCloudflare || /captcha|recaptcha|hcaptcha/.test(text) ||\n !!document.querySelector('iframe[src*=\"recaptcha\"], iframe[src*=\"hcaptcha\"], #captcha, .captcha, [id*=\"captcha\"], [class*=\"captcha\"]');\n return { hasCloudflare, hasCaptcha };\n }).catch(() => ({ hasCloudflare: false, hasCaptcha: false }));\n const message = challenge.hasCloudflare\n ? 'Cloudflare human verification blocked headless validation. This cannot be bypassed reliably in code; use Live Browser or manual session capture, configure a CAPTCHA solver, or allowlist the crawler IP.'\n : challenge.hasCaptcha\n ? 'A CAPTCHA blocked headless validation. Configure CAPTCHA Auto-Solve or use Live Browser/manual session capture.'\n : 'Login failed \u2014 credentials rejected or the login flow requires a manual/SSO step. Check credentials, saved login instructions, or use Live Browser session capture.';\n const err = Object.assign(new Error(message), {\n failureScreenshot: shotBuf ? shotBuf.toString('base64') : null,\n reason: challenge.hasCloudflare ? 'CLOUDFLARE_CHALLENGE' : challenge.hasCaptcha ? 'CAPTCHA_CHALLENGE' : 'LOGIN_NOT_CONFIRMED',\n });\n throw err;\n }\n\n // Wait for post-login navigation to settle before capturing session\n await page.waitForLoadState('networkidle', { timeout: 8_000 }).catch(() => {});\n\n // Post-login exploration: navigate tabs, expand menus, scroll to trigger lazy-loaded state\n await exploreAppAfterLogin(page);\n\n const state = await context.storageState();\n const cookies = state.cookies?.length ?? 0;\n const localStorage = state.origins?.reduce(\n (sum, o) => sum + (o.localStorage?.length ?? 0), 0\n ) ?? 0;\n\n if (cookies === 0 && localStorage === 0) {\n throw new Error('Login appeared to succeed but no session data was captured \u2014 try manual session capture instead');\n }\n\n return { storageState: JSON.stringify(state), cookies, localStorage };\n } finally {\n await browser.close().catch(() => {});\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Converts a Playwright storageState (the format ZeTa already captures via
3
+ * session-capture.ts/live-view.ts and stores per-project) into snippets for
4
+ * other automation frameworks. Playwright's storageState is the de-facto
5
+ * standard for browser session export — see docs/UNIVERSAL-SESSION-CAPTURE-
6
+ * STRATEGY.md — so every other framework's import mechanism is a small,
7
+ * mechanical conversion away, not a new capture flow.
8
+ *
9
+ * Pure, dependency-free functions — no I/O, no network, fully unit-testable.
10
+ */
11
+ export interface StorageStateCookie {
12
+ name: string;
13
+ value: string;
14
+ domain: string;
15
+ path: string;
16
+ expires?: number;
17
+ httpOnly?: boolean;
18
+ secure?: boolean;
19
+ sameSite?: 'Strict' | 'Lax' | 'None';
20
+ }
21
+ export interface StorageStateOrigin {
22
+ origin: string;
23
+ localStorage: Array<{
24
+ name: string;
25
+ value: string;
26
+ }>;
27
+ }
28
+ export interface StorageState {
29
+ cookies: StorageStateCookie[];
30
+ origins: StorageStateOrigin[];
31
+ }
32
+ export type SessionExportFormat = 'cypress' | 'selenium-python' | 'webdriverio';
33
+ /** cy.setCookie() + localStorage.setItem() inside a reusable custom command,
34
+ * following Cypress's documented cy.session() pattern (see references in
35
+ * docs/UNIVERSAL-SESSION-CAPTURE-STRATEGY.md). */
36
+ export declare function toCypressSnippet(state: StorageState): string;
37
+ /** driver.add_cookie() (Selenium requires the browser already be on the
38
+ * cookie's domain before adding it) + execute_script() for localStorage,
39
+ * per Selenium's documented Cookie API. */
40
+ export declare function toSeleniumPythonDict(state: StorageState): string;
41
+ /** browser.setCookies() accepts an array shape very close to Playwright's own
42
+ * cookie objects (per WebdriverIO's documented API) — closest 1:1 mapping
43
+ * of the three target formats. */
44
+ export declare function toWebdriverIOSnippet(state: StorageState): string;
45
+ export declare function exportSessionAs(format: SessionExportFormat, state: StorageState): {
46
+ content: string;
47
+ filename: string;
48
+ mimeType: string;
49
+ };
@@ -0,0 +1,117 @@
1
+ function jsStringLiteral(s) {
2
+ return JSON.stringify(s);
3
+ }
4
+ function pythonDictLiteral(obj) {
5
+ const parts = Object.entries(obj).map(([k, v]) => {
6
+ let repr;
7
+ if (typeof v === "boolean") repr = v ? "True" : "False";
8
+ else if (v === null || v === void 0) repr = "None";
9
+ else if (typeof v === "number") repr = String(v);
10
+ else repr = JSON.stringify(v);
11
+ return `${JSON.stringify(k)}: ${repr}`;
12
+ });
13
+ return `{${parts.join(", ")}}`;
14
+ }
15
+ function toCypressSnippet(state) {
16
+ const cookieLines = state.cookies.map((c) => {
17
+ const opts = [`domain: ${jsStringLiteral(c.domain)}`, `path: ${jsStringLiteral(c.path)}`];
18
+ if (c.secure) opts.push("secure: true");
19
+ if (c.httpOnly) opts.push("httpOnly: true");
20
+ if (c.sameSite) opts.push(`sameSite: ${jsStringLiteral(c.sameSite.toLowerCase())}`);
21
+ return ` cy.setCookie(${jsStringLiteral(c.name)}, ${jsStringLiteral(c.value)}, { ${opts.join(", ")} });`;
22
+ });
23
+ const localStorageBlocks = state.origins.filter((o) => o.localStorage.length > 0).map((o) => {
24
+ const sets = o.localStorage.map((kv) => ` window.localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`).join("\n");
25
+ return ` cy.visit(${jsStringLiteral(o.origin)});
26
+ cy.window().then((window) => {
27
+ ${sets}
28
+ });`;
29
+ });
30
+ return [
31
+ "// Generated by ZeTa-AI \u2014 restores a captured browser session in Cypress.",
32
+ "// Usage: import this file and call restoreZetaSession() inside cy.session(), e.g.:",
33
+ "// cy.session('zeta-session', restoreZetaSession)",
34
+ "Cypress.Commands.add('restoreZetaSession', () => {",
35
+ ...cookieLines,
36
+ ...localStorageBlocks,
37
+ "});"
38
+ ].join("\n");
39
+ }
40
+ function toSeleniumPythonDict(state) {
41
+ const byDomain = /* @__PURE__ */ new Map();
42
+ for (const c of state.cookies) {
43
+ const list = byDomain.get(c.domain) ?? [];
44
+ list.push(c);
45
+ byDomain.set(c.domain, list);
46
+ }
47
+ const lines = [
48
+ "# Generated by ZeTa-AI \u2014 restores a captured browser session in Selenium.",
49
+ "# Selenium requires the driver to already be on a matching domain before",
50
+ "# add_cookie() will accept a cookie for it \u2014 navigate first, then restore.",
51
+ "def restore_zeta_session(driver):"
52
+ ];
53
+ for (const [domain, cookies] of byDomain) {
54
+ lines.push(` driver.get(${JSON.stringify(`https://${domain.replace(/^\./, "")}`)})`);
55
+ for (const c of cookies) {
56
+ const cookieDict = { name: c.name, value: c.value, path: c.path };
57
+ if (c.expires && c.expires > 0) cookieDict.expiry = Math.floor(c.expires);
58
+ if (c.secure) cookieDict.secure = true;
59
+ lines.push(` driver.add_cookie(${pythonDictLiteral(cookieDict)})`);
60
+ }
61
+ }
62
+ for (const o of state.origins) {
63
+ if (o.localStorage.length === 0) continue;
64
+ lines.push(` driver.get(${JSON.stringify(o.origin)})`);
65
+ for (const kv of o.localStorage) {
66
+ lines.push(
67
+ ` driver.execute_script(${JSON.stringify(`window.localStorage.setItem(arguments[0], arguments[1]);`)}, ${JSON.stringify(kv.name)}, ${JSON.stringify(kv.value)})`
68
+ );
69
+ }
70
+ }
71
+ return lines.join("\n");
72
+ }
73
+ function toWebdriverIOSnippet(state) {
74
+ const cookies = state.cookies.map((c) => ({
75
+ name: c.name,
76
+ value: c.value,
77
+ domain: c.domain,
78
+ path: c.path,
79
+ secure: !!c.secure,
80
+ httpOnly: !!c.httpOnly,
81
+ ...c.expires && c.expires > 0 ? { expiry: Math.floor(c.expires) } : {}
82
+ }));
83
+ const localStorageSets = state.origins.filter((o) => o.localStorage.length > 0).map((o) => {
84
+ const sets = o.localStorage.map((kv) => ` localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`).join("\n");
85
+ return ` await browser.url(${jsStringLiteral(o.origin)});
86
+ await browser.execute(() => {
87
+ ${sets}
88
+ });`;
89
+ });
90
+ return [
91
+ "// Generated by ZeTa-AI \u2014 restores a captured browser session in WebdriverIO.",
92
+ "async function restoreZetaSession(browser) {",
93
+ ` await browser.setCookies(${JSON.stringify(cookies, null, 2).split("\n").join("\n ")});`,
94
+ ...localStorageSets,
95
+ "}",
96
+ "module.exports = { restoreZetaSession };"
97
+ ].join("\n");
98
+ }
99
+ function exportSessionAs(format, state) {
100
+ switch (format) {
101
+ case "cypress":
102
+ return { content: toCypressSnippet(state), filename: "zeta-session.cy-commands.js", mimeType: "application/javascript" };
103
+ case "selenium-python":
104
+ return { content: toSeleniumPythonDict(state), filename: "zeta_session.py", mimeType: "text/x-python" };
105
+ case "webdriverio":
106
+ return { content: toWebdriverIOSnippet(state), filename: "zeta-session.wdio.js", mimeType: "application/javascript" };
107
+ default:
108
+ throw new Error(`Unknown session export format: ${format}`);
109
+ }
110
+ }
111
+ export {
112
+ exportSessionAs,
113
+ toCypressSnippet,
114
+ toSeleniumPythonDict,
115
+ toWebdriverIOSnippet
116
+ };
117
+ //# sourceMappingURL=session-export.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/session-export.ts"],
4
+ "sourcesContent": ["/**\n * Converts a Playwright storageState (the format ZeTa already captures via\n * session-capture.ts/live-view.ts and stores per-project) into snippets for\n * other automation frameworks. Playwright's storageState is the de-facto\n * standard for browser session export \u2014 see docs/UNIVERSAL-SESSION-CAPTURE-\n * STRATEGY.md \u2014 so every other framework's import mechanism is a small,\n * mechanical conversion away, not a new capture flow.\n *\n * Pure, dependency-free functions \u2014 no I/O, no network, fully unit-testable.\n */\n\nexport interface StorageStateCookie {\n name: string;\n value: string;\n domain: string;\n path: string;\n expires?: number;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'Strict' | 'Lax' | 'None';\n}\n\nexport interface StorageStateOrigin {\n origin: string;\n localStorage: Array<{ name: string; value: string }>;\n}\n\nexport interface StorageState {\n cookies: StorageStateCookie[];\n origins: StorageStateOrigin[];\n}\n\nexport type SessionExportFormat = 'cypress' | 'selenium-python' | 'webdriverio';\n\nfunction jsStringLiteral(s: string): string {\n return JSON.stringify(s);\n}\n\n/** Python dict-literal repr \u2014 JSON.stringify's `true`/`false`/`null` aren't\n * valid Python (Python needs `True`/`False`/`None`), so a plain\n * JSON.stringify would emit syntactically invalid Python for any cookie\n * with a boolean field. */\nfunction pythonDictLiteral(obj: Record<string, unknown>): string {\n const parts = Object.entries(obj).map(([k, v]) => {\n let repr: string;\n if (typeof v === 'boolean') repr = v ? 'True' : 'False';\n else if (v === null || v === undefined) repr = 'None';\n else if (typeof v === 'number') repr = String(v);\n else repr = JSON.stringify(v);\n return `${JSON.stringify(k)}: ${repr}`;\n });\n return `{${parts.join(', ')}}`;\n}\n\n/** cy.setCookie() + localStorage.setItem() inside a reusable custom command,\n * following Cypress's documented cy.session() pattern (see references in\n * docs/UNIVERSAL-SESSION-CAPTURE-STRATEGY.md). */\nexport function toCypressSnippet(state: StorageState): string {\n const cookieLines = state.cookies.map((c) => {\n const opts: string[] = [`domain: ${jsStringLiteral(c.domain)}`, `path: ${jsStringLiteral(c.path)}`];\n if (c.secure) opts.push('secure: true');\n if (c.httpOnly) opts.push('httpOnly: true');\n if (c.sameSite) opts.push(`sameSite: ${jsStringLiteral(c.sameSite.toLowerCase())}`);\n return ` cy.setCookie(${jsStringLiteral(c.name)}, ${jsStringLiteral(c.value)}, { ${opts.join(', ')} });`;\n });\n\n const localStorageBlocks = state.origins\n .filter((o) => o.localStorage.length > 0)\n .map((o) => {\n const sets = o.localStorage\n .map((kv) => ` window.localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`)\n .join('\\n');\n return ` cy.visit(${jsStringLiteral(o.origin)});\\n cy.window().then((window) => {\\n${sets}\\n });`;\n });\n\n return [\n '// Generated by ZeTa-AI \u2014 restores a captured browser session in Cypress.',\n '// Usage: import this file and call restoreZetaSession() inside cy.session(), e.g.:',\n \"// cy.session('zeta-session', restoreZetaSession)\",\n \"Cypress.Commands.add('restoreZetaSession', () => {\",\n ...cookieLines,\n ...localStorageBlocks,\n '});',\n ].join('\\n');\n}\n\n/** driver.add_cookie() (Selenium requires the browser already be on the\n * cookie's domain before adding it) + execute_script() for localStorage,\n * per Selenium's documented Cookie API. */\nexport function toSeleniumPythonDict(state: StorageState): string {\n const byDomain = new Map<string, StorageStateCookie[]>();\n for (const c of state.cookies) {\n const list = byDomain.get(c.domain) ?? [];\n list.push(c);\n byDomain.set(c.domain, list);\n }\n\n const lines: string[] = [\n '# Generated by ZeTa-AI \u2014 restores a captured browser session in Selenium.',\n '# Selenium requires the driver to already be on a matching domain before',\n '# add_cookie() will accept a cookie for it \u2014 navigate first, then restore.',\n 'def restore_zeta_session(driver):',\n ];\n\n for (const [domain, cookies] of byDomain) {\n lines.push(` driver.get(${JSON.stringify(`https://${domain.replace(/^\\./, '')}`)})`);\n for (const c of cookies) {\n const cookieDict: Record<string, unknown> = { name: c.name, value: c.value, path: c.path };\n if (c.expires && c.expires > 0) cookieDict.expiry = Math.floor(c.expires);\n if (c.secure) cookieDict.secure = true;\n lines.push(` driver.add_cookie(${pythonDictLiteral(cookieDict)})`);\n }\n }\n\n for (const o of state.origins) {\n if (o.localStorage.length === 0) continue;\n lines.push(` driver.get(${JSON.stringify(o.origin)})`);\n for (const kv of o.localStorage) {\n lines.push(\n ` driver.execute_script(${JSON.stringify(`window.localStorage.setItem(arguments[0], arguments[1]);`)}, ${JSON.stringify(kv.name)}, ${JSON.stringify(kv.value)})`,\n );\n }\n }\n\n return lines.join('\\n');\n}\n\n/** browser.setCookies() accepts an array shape very close to Playwright's own\n * cookie objects (per WebdriverIO's documented API) \u2014 closest 1:1 mapping\n * of the three target formats. */\nexport function toWebdriverIOSnippet(state: StorageState): string {\n const cookies = state.cookies.map((c) => ({\n name: c.name,\n value: c.value,\n domain: c.domain,\n path: c.path,\n secure: !!c.secure,\n httpOnly: !!c.httpOnly,\n ...(c.expires && c.expires > 0 ? { expiry: Math.floor(c.expires) } : {}),\n }));\n\n const localStorageSets = state.origins\n .filter((o) => o.localStorage.length > 0)\n .map((o) => {\n const sets = o.localStorage\n .map((kv) => ` localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`)\n .join('\\n');\n return ` await browser.url(${jsStringLiteral(o.origin)});\\n await browser.execute(() => {\\n${sets}\\n });`;\n });\n\n return [\n '// Generated by ZeTa-AI \u2014 restores a captured browser session in WebdriverIO.',\n 'async function restoreZetaSession(browser) {',\n ` await browser.setCookies(${JSON.stringify(cookies, null, 2).split('\\n').join('\\n ')});`,\n ...localStorageSets,\n '}',\n 'module.exports = { restoreZetaSession };',\n ].join('\\n');\n}\n\nexport function exportSessionAs(format: SessionExportFormat, state: StorageState): { content: string; filename: string; mimeType: string } {\n switch (format) {\n case 'cypress':\n return { content: toCypressSnippet(state), filename: 'zeta-session.cy-commands.js', mimeType: 'application/javascript' };\n case 'selenium-python':\n return { content: toSeleniumPythonDict(state), filename: 'zeta_session.py', mimeType: 'text/x-python' };\n case 'webdriverio':\n return { content: toWebdriverIOSnippet(state), filename: 'zeta-session.wdio.js', mimeType: 'application/javascript' };\n default:\n throw new Error(`Unknown session export format: ${format}`);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Sitemap writer — generate XML sitemap from crawled screens.
3
+ *
4
+ * Compliant with sitemaps.org protocol (sitemap index + individual sitemap files).
5
+ * Splits into 50k-URL files per the spec when needed.
6
+ * Used for: SEO audit export, future crawl seed, and crawler/robots.txt comparison.
7
+ */
8
+ export interface SitemapUrl {
9
+ loc: string;
10
+ lastmod?: string;
11
+ changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
12
+ priority?: number;
13
+ }
14
+ export declare function writeSitemap(urls: SitemapUrl[], outputPath: string): Promise<void>;
15
+ /** Convert crawled screen records to SitemapUrl objects */
16
+ export declare function screensToSitemapUrls(screens: Array<{
17
+ url: string;
18
+ updatedAt?: Date;
19
+ depth?: number;
20
+ }>): SitemapUrl[];
21
+ export declare function buildSitemapString(urls: SitemapUrl[]): string;
@@ -0,0 +1,47 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n';
4
+ const SITEMAP_NS = 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
5
+ function escape(s) {
6
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
7
+ }
8
+ function buildSitemapXml(urls) {
9
+ const entries = urls.map((u) => [
10
+ " <url>",
11
+ ` <loc>${escape(u.loc)}</loc>`,
12
+ u.lastmod ? ` <lastmod>${u.lastmod}</lastmod>` : "",
13
+ u.changefreq ? ` <changefreq>${u.changefreq}</changefreq>` : "",
14
+ u.priority !== void 0 ? ` <priority>${u.priority.toFixed(1)}</priority>` : "",
15
+ " </url>"
16
+ ].filter(Boolean).join("\n")).join("\n");
17
+ return `${XML_HEADER}<urlset ${SITEMAP_NS}>
18
+ ${entries}
19
+ </urlset>`;
20
+ }
21
+ async function writeSitemap(urls, outputPath) {
22
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
23
+ await fs.writeFile(outputPath, buildSitemapXml(urls), "utf8");
24
+ }
25
+ function screensToSitemapUrls(screens) {
26
+ return screens.filter((s) => {
27
+ try {
28
+ return new URL(s.url).protocol.startsWith("http");
29
+ } catch {
30
+ return false;
31
+ }
32
+ }).map((s) => ({
33
+ loc: s.url,
34
+ lastmod: s.updatedAt ? s.updatedAt.toISOString().split("T")[0] : void 0,
35
+ changefreq: "weekly",
36
+ priority: s.depth === 0 ? 1 : s.depth === 1 ? 0.8 : 0.6
37
+ }));
38
+ }
39
+ function buildSitemapString(urls) {
40
+ return buildSitemapXml(urls);
41
+ }
42
+ export {
43
+ buildSitemapString,
44
+ screensToSitemapUrls,
45
+ writeSitemap
46
+ };
47
+ //# sourceMappingURL=sitemap-writer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/sitemap-writer.ts"],
4
+ "sourcesContent": ["/**\n * Sitemap writer \u2014 generate XML sitemap from crawled screens.\n *\n * Compliant with sitemaps.org protocol (sitemap index + individual sitemap files).\n * Splits into 50k-URL files per the spec when needed.\n * Used for: SEO audit export, future crawl seed, and crawler/robots.txt comparison.\n */\n\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport interface SitemapUrl {\n loc: string;\n lastmod?: string;\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number; // 0.0\u20131.0\n}\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\nconst SITEMAP_NS = 'xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"';\n\nfunction escape(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&apos;');\n}\n\nfunction buildSitemapXml(urls: SitemapUrl[]): string {\n const entries = urls.map(u => [\n ' <url>',\n ` <loc>${escape(u.loc)}</loc>`,\n u.lastmod ? ` <lastmod>${u.lastmod}</lastmod>` : '',\n u.changefreq ? ` <changefreq>${u.changefreq}</changefreq>` : '',\n u.priority !== undefined ? ` <priority>${u.priority.toFixed(1)}</priority>` : '',\n ' </url>',\n ].filter(Boolean).join('\\n')).join('\\n');\n\n return `${XML_HEADER}<urlset ${SITEMAP_NS}>\\n${entries}\\n</urlset>`;\n}\n\nexport async function writeSitemap(\n urls: SitemapUrl[],\n outputPath: string,\n): Promise<void> {\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, buildSitemapXml(urls), 'utf8');\n}\n\n/** Convert crawled screen records to SitemapUrl objects */\nexport function screensToSitemapUrls(\n screens: Array<{ url: string; updatedAt?: Date; depth?: number }>,\n): SitemapUrl[] {\n return screens\n .filter(s => {\n try { return new URL(s.url).protocol.startsWith('http'); } catch { return false; }\n })\n .map(s => ({\n loc: s.url,\n lastmod: s.updatedAt ? s.updatedAt.toISOString().split('T')[0] : undefined,\n changefreq: 'weekly' as const,\n priority: s.depth === 0 ? 1.0 : s.depth === 1 ? 0.8 : 0.6,\n }));\n}\n\nexport function buildSitemapString(urls: SitemapUrl[]): string {\n return buildSitemapXml(urls);\n}\n"],
5
+ "mappings": "AAQA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAStB,MAAM,aAAa;AACnB,MAAM,aAAa;AAEnB,SAAS,OAAO,GAAmB;AACjC,SAAO,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ,EAAE,QAAQ,MAAM,QAAQ;AAC5H;AAEA,SAAS,gBAAgB,MAA4B;AACnD,QAAM,UAAU,KAAK,IAAI,OAAK;AAAA,IAC5B;AAAA,IACA,YAAY,OAAO,EAAE,GAAG,CAAC;AAAA,IACzB,EAAE,UAAU,gBAAgB,EAAE,OAAO,eAAe;AAAA,IACpD,EAAE,aAAa,mBAAmB,EAAE,UAAU,kBAAkB;AAAA,IAChE,EAAE,aAAa,SAAY,iBAAiB,EAAE,SAAS,QAAQ,CAAC,CAAC,gBAAgB;AAAA,IACjF;AAAA,EACF,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI;AAEvC,SAAO,GAAG,UAAU,WAAW,UAAU;AAAA,EAAM,OAAO;AAAA;AACxD;AAEA,eAAsB,aACpB,MACA,YACe;AACf,QAAM,GAAG,MAAM,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAM,GAAG,UAAU,YAAY,gBAAgB,IAAI,GAAG,MAAM;AAC9D;AAGO,SAAS,qBACd,SACc;AACd,SAAO,QACJ,OAAO,OAAK;AACX,QAAI;AAAE,aAAO,IAAI,IAAI,EAAE,GAAG,EAAE,SAAS,WAAW,MAAM;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAO;AAAA,EACnF,CAAC,EACA,IAAI,QAAM;AAAA,IACT,KAAK,EAAE;AAAA,IACP,SAAS,EAAE,YAAY,EAAE,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI;AAAA,IACjE,YAAY;AAAA,IACZ,UAAU,EAAE,UAAU,IAAI,IAAM,EAAE,UAAU,IAAI,MAAM;AAAA,EACxD,EAAE;AACN;AAEO,SAAS,mBAAmB,MAA4B;AAC7D,SAAO,gBAAgB,IAAI;AAC7B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,51 @@
1
+ import { ProxyAgent } from 'undici';
2
+ /**
3
+ * Same-site check that tolerates www/non-www and http/https differences — a sitemap on
4
+ * `vercel.com` is valid for a project pointed at `www.vercel.com` (and vice versa).
5
+ */
6
+ export declare function sameSite(candidateUrl: string, siteOrigin: string): boolean;
7
+ /**
8
+ * Pulls page URLs and sub-sitemap URLs out of an already-XML-parsed sitemap
9
+ * document (either a <urlset> or a <sitemapindex>), filtered to same-origin
10
+ * and deduped. Pure function — no I/O — for direct unit testing without a
11
+ * real HTTP fetch.
12
+ */
13
+ export declare function extractUrlsFromParsedSitemap(parsed: any, origin: string): {
14
+ pageUrls: string[];
15
+ subSitemapUrls: string[];
16
+ };
17
+ /**
18
+ * Fetches and parses a sitemap (or sitemap index, recursed up to a small
19
+ * depth) into a flat, deduped list of same-origin URLs.
20
+ */
21
+ export declare function fetchSitemapUrls(sitemapUrl: string, origin: string, depth?: number, dispatcher?: any): Promise<string[]>;
22
+ /**
23
+ * Discovers a project's sitemap (default `${origin}/sitemap.xml`, or an
24
+ * explicit override) and returns the URLs it lists. Never throws.
25
+ */
26
+ export declare function discoverSitemapUrls(appUrl: string, sitemapUrl?: string): Promise<string[]>;
27
+ /**
28
+ * Build an undici ProxyAgent from a proxy URL. Parses credentials manually because
29
+ * DataImpulse-style usernames contain commas (country codes) that break new URL().
30
+ * Returns undefined for no/auto proxy — the caller then fetches directly.
31
+ */
32
+ export declare function buildProxyDispatcher(proxyUrl?: string): ProxyAgent | undefined;
33
+ /** Extract same-origin `Sitemap:` directives from a robots.txt body. */
34
+ export declare function parseRobotsSitemaps(robotsTxt: string, origin: string): string[];
35
+ export interface UrlCandidate {
36
+ url: string;
37
+ source: 'sitemap' | 'robots' | 'link';
38
+ }
39
+ /**
40
+ * Pre-crawl URL discovery for the review-before-crawl flow: reads robots.txt Sitemap
41
+ * directives and sitemap.xml (recursed, same-origin), proxy-aware. Returns a deduped
42
+ * candidate list the user can review and seed before launching the Playwright crawl.
43
+ * Never throws — bot-protected sites (Akamai/Cloudflare) simply yield an empty list.
44
+ */
45
+ export declare function discoverProjectUrls(appUrl: string, opts?: {
46
+ sitemapUrl?: string;
47
+ proxyUrl?: string;
48
+ }): Promise<{
49
+ candidates: UrlCandidate[];
50
+ sitemapFound: boolean;
51
+ }>;