@detiq/crawler 1.1.9 → 1.1.11

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 (83) hide show
  1. package/dist/accessibility-auditor.js +54 -74
  2. package/dist/ai-login.js +227 -249
  3. package/dist/auth-token-rotator.js +57 -67
  4. package/dist/auth.js +299 -336
  5. package/dist/browser-extension.js +104 -142
  6. package/dist/captcha-solver.js +98 -125
  7. package/dist/color-contrast.js +77 -89
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +54 -71
  10. package/dist/cookie-consent.js +68 -83
  11. package/dist/cookie-jar.js +53 -60
  12. package/dist/crawler.js +4494 -5353
  13. package/dist/custom-js-injector.js +45 -61
  14. package/dist/dark-mode-capture.js +67 -81
  15. package/dist/dead-link-detector.js +47 -55
  16. package/dist/element-hash.js +8 -12
  17. package/dist/email-otp.js +154 -195
  18. package/dist/engine-router.js +87 -98
  19. package/dist/export/bulk-export.js +70 -55
  20. package/dist/export/vector-export.js +77 -105
  21. package/dist/extractors/advanced.js +249 -266
  22. package/dist/extractors/artifact-envelope.js +59 -58
  23. package/dist/failure-analysis-agent.js +37 -33
  24. package/dist/feature-flag-detector.js +51 -66
  25. package/dist/form-detector.js +55 -75
  26. package/dist/frontier.js +76 -101
  27. package/dist/gap-pass.js +68 -89
  28. package/dist/graphql-detector.js +62 -72
  29. package/dist/http-engine.js +282 -330
  30. package/dist/incremental-diff.js +54 -87
  31. package/dist/index.js +67 -28
  32. package/dist/input-fuzzer.js +93 -97
  33. package/dist/interactive-probe.js +715 -827
  34. package/dist/job-queue.js +2151 -2169
  35. package/dist/js-coverage.js +52 -56
  36. package/dist/js-router-detector.js +86 -117
  37. package/dist/language-detector.js +51 -63
  38. package/dist/link-classifier.js +32 -50
  39. package/dist/live-view.js +91 -108
  40. package/dist/locator-extractor.js +151 -187
  41. package/dist/logger.js +9 -7
  42. package/dist/markdown-extract.js +48 -61
  43. package/dist/native-crawler.js +780 -946
  44. package/dist/network-observer.js +135 -156
  45. package/dist/network-throttle.js +29 -40
  46. package/dist/openapi-generator.js +77 -92
  47. package/dist/pdf-extractor.js +72 -90
  48. package/dist/performance-metrics.js +80 -102
  49. package/dist/phash.js +125 -144
  50. package/dist/pii-scrubber.js +65 -68
  51. package/dist/playwright-recorder.js +70 -58
  52. package/dist/policies/crawl-safety.js +31 -34
  53. package/dist/profiles/domain-profile.js +130 -125
  54. package/dist/proxy.js +146 -172
  55. package/dist/rate-limiter.js +58 -65
  56. package/dist/redirect-tracker.js +60 -64
  57. package/dist/redis-frontier.js +116 -136
  58. package/dist/redis-queue.js +157 -144
  59. package/dist/response-cache.js +127 -140
  60. package/dist/responsive-detector.js +42 -46
  61. package/dist/retry-policy.js +46 -50
  62. package/dist/robots-txt.js +69 -83
  63. package/dist/screen-reader.js +45 -70
  64. package/dist/screenshot-masker.js +59 -77
  65. package/dist/seo-checker.js +88 -107
  66. package/dist/session-capture.js +192 -212
  67. package/dist/session-export.js +107 -133
  68. package/dist/sitemap-writer.js +38 -41
  69. package/dist/sitemap.js +141 -202
  70. package/dist/skyvern.js +123 -148
  71. package/dist/sqs-queue.js +42 -42
  72. package/dist/sso-token.js +74 -90
  73. package/dist/stealth/human-behavior.js +156 -173
  74. package/dist/telemetry.js +68 -82
  75. package/dist/test-executor.js +309 -335
  76. package/dist/tls-fingerprint.js +77 -88
  77. package/dist/url-pattern-completer.js +71 -90
  78. package/dist/visual-diff-verdict.js +23 -38
  79. package/dist/webhook-notifier.js +71 -69
  80. package/dist/websocket-detector.js +56 -68
  81. package/dist/worker.js +29 -38
  82. package/package.json +1 -1
  83. package/src/job-queue.ts +136 -0
@@ -1,66 +1,50 @@
1
- /**
2
- * Custom JS injector — run user-provided JavaScript in the context of each crawled page.
3
- *
4
- * Use cases:
5
- * - Extract custom data points not captured by standard extractors
6
- * - Set localStorage/sessionStorage values before crawl
7
- * - Override fetch() to intercept API calls
8
- * - Simulate user events (keydown, focus, etc.)
9
- * - Bypass specific anti-bot checks
10
- *
11
- * Security: scripts run in the page context (sandboxed by Chromium's renderer).
12
- * They cannot access the Node.js process or file system.
13
- * ZeTa validates scripts are strings before injection.
14
- */
15
- export async function injectCustomJs(page, opts) {
16
- const result = {
17
- initScriptApplied: false,
18
- postLoadResult: null,
19
- storageSet: false,
20
- cookiesSet: 0,
21
- };
22
- // Set cookies via Playwright context
23
- if (opts.cookies?.length) {
24
- try {
25
- await page.context().addCookies(opts.cookies.map(c => ({ name: c.name, value: c.value, domain: c.domain ?? '', path: c.path ?? '/', url: page.url() || undefined })));
26
- result.cookiesSet = opts.cookies.length;
27
- }
28
- catch (e) {
29
- result.error = `Cookie injection failed: ${e.message}`;
30
- }
1
+ async function injectCustomJs(page, opts) {
2
+ const result = {
3
+ initScriptApplied: false,
4
+ postLoadResult: null,
5
+ storageSet: false,
6
+ cookiesSet: 0
7
+ };
8
+ if (opts.cookies?.length) {
9
+ try {
10
+ await page.context().addCookies(
11
+ opts.cookies.map((c) => ({ name: c.name, value: c.value, domain: c.domain ?? "", path: c.path ?? "/", url: page.url() || void 0 }))
12
+ );
13
+ result.cookiesSet = opts.cookies.length;
14
+ } catch (e) {
15
+ result.error = `Cookie injection failed: ${e.message}`;
31
16
  }
32
- // Init script (runs before page load on every navigation)
33
- if (opts.initScript || opts.localStorage || opts.sessionStorage) {
34
- const storageSetup = [
35
- opts.localStorage ? `Object.entries(${JSON.stringify(opts.localStorage)}).forEach(([k,v])=>localStorage.setItem(k,v));` : '',
36
- opts.sessionStorage ? `Object.entries(${JSON.stringify(opts.sessionStorage)}).forEach(([k,v])=>sessionStorage.setItem(k,v));` : '',
37
- ].filter(Boolean).join('\n');
38
- const combinedInit = [storageSetup, opts.initScript ?? ''].filter(Boolean).join('\n');
39
- try {
40
- await page.addInitScript(combinedInit);
41
- result.initScriptApplied = true;
42
- result.storageSet = !!(opts.localStorage || opts.sessionStorage);
43
- }
44
- catch (e) {
45
- result.error = `Init script failed: ${e.message}`;
46
- }
17
+ }
18
+ if (opts.initScript || opts.localStorage || opts.sessionStorage) {
19
+ const storageSetup = [
20
+ opts.localStorage ? `Object.entries(${JSON.stringify(opts.localStorage)}).forEach(([k,v])=>localStorage.setItem(k,v));` : "",
21
+ opts.sessionStorage ? `Object.entries(${JSON.stringify(opts.sessionStorage)}).forEach(([k,v])=>sessionStorage.setItem(k,v));` : ""
22
+ ].filter(Boolean).join("\n");
23
+ const combinedInit = [storageSetup, opts.initScript ?? ""].filter(Boolean).join("\n");
24
+ try {
25
+ await page.addInitScript(combinedInit);
26
+ result.initScriptApplied = true;
27
+ result.storageSet = !!(opts.localStorage || opts.sessionStorage);
28
+ } catch (e) {
29
+ result.error = `Init script failed: ${e.message}`;
47
30
  }
48
- // Post-load script (runs after navigation)
49
- if (opts.postLoadScript) {
50
- try {
51
- result.postLoadResult = await page.evaluate(opts.postLoadScript);
52
- }
53
- catch (e) {
54
- result.error = `Post-load script failed: ${e.message}`;
55
- }
31
+ }
32
+ if (opts.postLoadScript) {
33
+ try {
34
+ result.postLoadResult = await page.evaluate(opts.postLoadScript);
35
+ } catch (e) {
36
+ result.error = `Post-load script failed: ${e.message}`;
56
37
  }
57
- return result;
38
+ }
39
+ return result;
58
40
  }
59
- /** Safe string validator — ensures script is a string, not an object/function reference */
60
- export function validateScript(script) {
61
- if (typeof script !== 'string')
62
- throw new Error('Script must be a string');
63
- if (script.length > 50_000)
64
- throw new Error('Script too large (>50KB)');
65
- return script;
41
+ function validateScript(script) {
42
+ if (typeof script !== "string") throw new Error("Script must be a string");
43
+ if (script.length > 5e4) throw new Error("Script too large (>50KB)");
44
+ return script;
66
45
  }
46
+ export {
47
+ injectCustomJs,
48
+ validateScript
49
+ };
50
+ //# sourceMappingURL=custom-js-injector.js.map
@@ -1,89 +1,75 @@
1
- /**
2
- * Dark mode capture — screenshot pages in both light and dark color schemes.
3
- *
4
- * Uses Playwright's page.emulateMedia({ colorScheme: 'dark' }) to force dark mode,
5
- * then captures a screenshot. Compares with light mode to determine if dark mode
6
- * is supported (significant visual difference).
7
- *
8
- * Also detects prefers-color-scheme media query support in CSS.
9
- */
10
1
  async function getBackgroundColor(page) {
11
- try {
12
- return await page.evaluate(() => {
13
- const body = document.body;
14
- return window.getComputedStyle(body).backgroundColor;
15
- });
16
- }
17
- catch {
18
- return 'unknown';
19
- }
2
+ try {
3
+ return await page.evaluate(() => {
4
+ const body = document.body;
5
+ return window.getComputedStyle(body).backgroundColor;
6
+ });
7
+ } catch {
8
+ return "unknown";
9
+ }
20
10
  }
21
11
  function colorsAreDifferent(a, b) {
22
- if (a === b)
23
- return false;
24
- if (a === 'unknown' || b === 'unknown')
25
- return false;
26
- // Parse rgb(r, g, b) and compare luminance
27
- const parse = (c) => {
28
- const m = c.match(/\d+/g);
29
- return m ? m.map(Number) : [0, 0, 0];
30
- };
31
- const [ar, ag, ab] = parse(a);
32
- const [br, bg, bb] = parse(b);
33
- const lumA = 0.299 * ar + 0.587 * ag + 0.114 * ab;
34
- const lumB = 0.299 * br + 0.587 * bg + 0.114 * bb;
35
- return Math.abs(lumA - lumB) > 30; // significant luminance difference
12
+ if (a === b) return false;
13
+ if (a === "unknown" || b === "unknown") return false;
14
+ const parse = (c) => {
15
+ const m = c.match(/\d+/g);
16
+ return m ? m.map(Number) : [0, 0, 0];
17
+ };
18
+ const [ar, ag, ab] = parse(a);
19
+ const [br, bg, bb] = parse(b);
20
+ const lumA = 0.299 * ar + 0.587 * ag + 0.114 * ab;
21
+ const lumB = 0.299 * br + 0.587 * bg + 0.114 * bb;
22
+ return Math.abs(lumA - lumB) > 30;
36
23
  }
37
24
  async function hasCssDarkModeSupport(page) {
38
- try {
39
- return await page.evaluate(() => {
40
- const sheets = Array.from(document.styleSheets);
41
- for (const sheet of sheets) {
42
- try {
43
- const rules = Array.from(sheet.cssRules ?? []);
44
- for (const rule of rules) {
45
- if (rule instanceof CSSMediaRule && rule.conditionText?.includes('prefers-color-scheme')) {
46
- return true;
47
- }
48
- }
49
- }
50
- catch { /* cross-origin stylesheet — skip */ }
25
+ try {
26
+ return await page.evaluate(() => {
27
+ const sheets = Array.from(document.styleSheets);
28
+ for (const sheet of sheets) {
29
+ try {
30
+ const rules = Array.from(sheet.cssRules ?? []);
31
+ for (const rule of rules) {
32
+ if (rule instanceof CSSMediaRule && rule.conditionText?.includes("prefers-color-scheme")) {
33
+ return true;
51
34
  }
52
- return false;
53
- });
54
- }
55
- catch {
56
- return false;
57
- }
35
+ }
36
+ } catch {
37
+ }
38
+ }
39
+ return false;
40
+ });
41
+ } catch {
42
+ return false;
43
+ }
58
44
  }
59
- export async function captureDarkMode(page, url, opts = {}) {
60
- const capturedAt = new Date().toISOString();
61
- const { takeScreenshots = false } = opts;
62
- // Light mode
63
- await page.emulateMedia({ colorScheme: 'light' }).catch(() => { });
64
- const backgroundColorLight = await getBackgroundColor(page);
65
- const hasCssDark = await hasCssDarkModeSupport(page);
66
- const lightScreenshot = takeScreenshots
67
- ? await page.screenshot({ type: 'jpeg', quality: 60 }).catch(() => undefined)
68
- : undefined;
69
- // Dark mode
70
- await page.emulateMedia({ colorScheme: 'dark' }).catch(() => { });
71
- await page.waitForTimeout(300); // allow CSS transitions
72
- const backgroundColorDark = await getBackgroundColor(page);
73
- const darkScreenshot = takeScreenshots
74
- ? await page.screenshot({ type: 'jpeg', quality: 60 }).catch(() => undefined)
75
- : undefined;
76
- // Restore light mode
77
- await page.emulateMedia({ colorScheme: 'light' }).catch(() => { });
78
- const hasDarkMode = colorsAreDifferent(backgroundColorLight, backgroundColorDark) || hasCssDark;
79
- return {
80
- url,
81
- lightScreenshot,
82
- darkScreenshot,
83
- hasDarkMode,
84
- hasCssDarkMode: hasCssDark,
85
- backgroundColorLight,
86
- backgroundColorDark,
87
- capturedAt,
88
- };
45
+ async function captureDarkMode(page, url, opts = {}) {
46
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
47
+ const { takeScreenshots = false } = opts;
48
+ await page.emulateMedia({ colorScheme: "light" }).catch(() => {
49
+ });
50
+ const backgroundColorLight = await getBackgroundColor(page);
51
+ const hasCssDark = await hasCssDarkModeSupport(page);
52
+ const lightScreenshot = takeScreenshots ? await page.screenshot({ type: "jpeg", quality: 60 }).catch(() => void 0) : void 0;
53
+ await page.emulateMedia({ colorScheme: "dark" }).catch(() => {
54
+ });
55
+ await page.waitForTimeout(300);
56
+ const backgroundColorDark = await getBackgroundColor(page);
57
+ const darkScreenshot = takeScreenshots ? await page.screenshot({ type: "jpeg", quality: 60 }).catch(() => void 0) : void 0;
58
+ await page.emulateMedia({ colorScheme: "light" }).catch(() => {
59
+ });
60
+ const hasDarkMode = colorsAreDifferent(backgroundColorLight, backgroundColorDark) || hasCssDark;
61
+ return {
62
+ url,
63
+ lightScreenshot,
64
+ darkScreenshot,
65
+ hasDarkMode,
66
+ hasCssDarkMode: hasCssDark,
67
+ backgroundColorLight,
68
+ backgroundColorDark,
69
+ capturedAt
70
+ };
89
71
  }
72
+ export {
73
+ captureDarkMode
74
+ };
75
+ //# sourceMappingURL=dark-mode-capture.js.map
@@ -1,58 +1,50 @@
1
- /**
2
- * Dead link detector — tracks broken links discovered during crawl.
3
- *
4
- * A "dead link" is a URL that:
5
- * - Returns 404, 410, or other 4xx/5xx responses
6
- * - Times out after maxRetries
7
- * - Redirects to an error page (detected by title heuristic)
8
- *
9
- * Results: list of broken links with source page, status code, and anchor text.
10
- * Used for: SEO audit export, test report enrichment.
11
- */
12
- export class DeadLinkTracker {
13
- deadLinks = [];
14
- checked = new Map(); // url → statusCode
15
- record(url, statusCode, source) {
16
- if (this.checked.has(url))
17
- return;
18
- this.checked.set(url, statusCode);
19
- if (statusCode >= 400) {
20
- this.deadLinks.push({
21
- url,
22
- statusCode,
23
- sourceUrl: source?.sourceUrl,
24
- anchorText: source?.anchorText,
25
- checkedAt: new Date().toISOString(),
26
- reason: statusCode === 0 ? 'timeout' : 'http-error',
27
- });
28
- }
1
+ class DeadLinkTracker {
2
+ deadLinks = [];
3
+ checked = /* @__PURE__ */ new Map();
4
+ // url → statusCode
5
+ record(url, statusCode, source) {
6
+ if (this.checked.has(url)) return;
7
+ this.checked.set(url, statusCode);
8
+ if (statusCode >= 400) {
9
+ this.deadLinks.push({
10
+ url,
11
+ statusCode,
12
+ sourceUrl: source?.sourceUrl,
13
+ anchorText: source?.anchorText,
14
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
15
+ reason: statusCode === 0 ? "timeout" : "http-error"
16
+ });
29
17
  }
30
- recordTimeout(url, sourceUrl) {
31
- if (this.checked.has(url))
32
- return;
33
- this.checked.set(url, 0);
34
- this.deadLinks.push({ url, statusCode: 0, sourceUrl, checkedAt: new Date().toISOString(), reason: 'timeout' });
35
- }
36
- getDeadLinks() {
37
- return [...this.deadLinks];
38
- }
39
- isKnownBroken(url) {
40
- const code = this.checked.get(url);
41
- return code !== undefined && code >= 400;
42
- }
43
- stats() {
44
- return {
45
- total: this.deadLinks.length,
46
- by404: this.deadLinks.filter(l => l.statusCode === 404).length,
47
- by5xx: this.deadLinks.filter(l => l.statusCode >= 500).length,
48
- byTimeout: this.deadLinks.filter(l => l.reason === 'timeout').length,
49
- };
50
- }
51
- toCsv() {
52
- const rows = [['URL', 'Status', 'Source URL', 'Anchor Text', 'Checked At']];
53
- for (const l of this.deadLinks) {
54
- rows.push([l.url, String(l.statusCode), l.sourceUrl ?? '', l.anchorText ?? '', l.checkedAt]);
55
- }
56
- return rows.map(r => r.map(c => `"${c.replace(/"/g, '""')}"`).join(',')).join('\n');
18
+ }
19
+ recordTimeout(url, sourceUrl) {
20
+ if (this.checked.has(url)) return;
21
+ this.checked.set(url, 0);
22
+ this.deadLinks.push({ url, statusCode: 0, sourceUrl, checkedAt: (/* @__PURE__ */ new Date()).toISOString(), reason: "timeout" });
23
+ }
24
+ getDeadLinks() {
25
+ return [...this.deadLinks];
26
+ }
27
+ isKnownBroken(url) {
28
+ const code = this.checked.get(url);
29
+ return code !== void 0 && code >= 400;
30
+ }
31
+ stats() {
32
+ return {
33
+ total: this.deadLinks.length,
34
+ by404: this.deadLinks.filter((l) => l.statusCode === 404).length,
35
+ by5xx: this.deadLinks.filter((l) => l.statusCode >= 500).length,
36
+ byTimeout: this.deadLinks.filter((l) => l.reason === "timeout").length
37
+ };
38
+ }
39
+ toCsv() {
40
+ const rows = [["URL", "Status", "Source URL", "Anchor Text", "Checked At"]];
41
+ for (const l of this.deadLinks) {
42
+ rows.push([l.url, String(l.statusCode), l.sourceUrl ?? "", l.anchorText ?? "", l.checkedAt]);
57
43
  }
44
+ return rows.map((r) => r.map((c) => `"${c.replace(/"/g, '""')}"`).join(",")).join("\n");
45
+ }
58
46
  }
47
+ export {
48
+ DeadLinkTracker
49
+ };
50
+ //# sourceMappingURL=dead-link-detector.js.map
@@ -1,13 +1,9 @@
1
- import { createHash } from 'node:crypto';
2
- /**
3
- * Compute a stable fingerprint of a screen's element set.
4
- * Sorted so insertion-order changes (React re-renders) don't produce false positives.
5
- * Returns first 16 hex chars of SHA-256.
6
- */
7
- export function computeElementHash(elements) {
8
- const pairs = elements
9
- .map((e) => `${e.role.toLowerCase()}:${e.meaning.toLowerCase().trim()}`)
10
- .sort()
11
- .join('|');
12
- return createHash('sha256').update(pairs).digest('hex').slice(0, 16);
1
+ import { createHash } from "node:crypto";
2
+ function computeElementHash(elements) {
3
+ const pairs = elements.map((e) => `${e.role.toLowerCase()}:${e.meaning.toLowerCase().trim()}`).sort().join("|");
4
+ return createHash("sha256").update(pairs).digest("hex").slice(0, 16);
13
5
  }
6
+ export {
7
+ computeElementHash
8
+ };
9
+ //# sourceMappingURL=element-hash.js.map