@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.
- package/dist/accessibility-auditor.js +54 -74
- package/dist/ai-login.js +227 -249
- package/dist/auth-token-rotator.js +57 -67
- package/dist/auth.js +299 -336
- package/dist/browser-extension.js +104 -142
- package/dist/captcha-solver.js +98 -125
- package/dist/color-contrast.js +77 -89
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +54 -71
- package/dist/cookie-consent.js +68 -83
- package/dist/cookie-jar.js +53 -60
- package/dist/crawler.js +4494 -5353
- package/dist/custom-js-injector.js +45 -61
- package/dist/dark-mode-capture.js +67 -81
- package/dist/dead-link-detector.js +47 -55
- package/dist/element-hash.js +8 -12
- package/dist/email-otp.js +154 -195
- package/dist/engine-router.js +87 -98
- package/dist/export/bulk-export.js +70 -55
- package/dist/export/vector-export.js +77 -105
- package/dist/extractors/advanced.js +249 -266
- package/dist/extractors/artifact-envelope.js +59 -58
- package/dist/failure-analysis-agent.js +37 -33
- package/dist/feature-flag-detector.js +51 -66
- package/dist/form-detector.js +55 -75
- package/dist/frontier.js +76 -101
- package/dist/gap-pass.js +68 -89
- package/dist/graphql-detector.js +62 -72
- package/dist/http-engine.js +282 -330
- package/dist/incremental-diff.js +54 -87
- package/dist/index.js +67 -28
- package/dist/input-fuzzer.js +93 -97
- package/dist/interactive-probe.js +715 -827
- package/dist/job-queue.js +2151 -2169
- package/dist/js-coverage.js +52 -56
- package/dist/js-router-detector.js +86 -117
- package/dist/language-detector.js +51 -63
- package/dist/link-classifier.js +32 -50
- package/dist/live-view.js +91 -108
- package/dist/locator-extractor.js +151 -187
- package/dist/logger.js +9 -7
- package/dist/markdown-extract.js +48 -61
- package/dist/native-crawler.js +780 -946
- package/dist/network-observer.js +135 -156
- package/dist/network-throttle.js +29 -40
- package/dist/openapi-generator.js +77 -92
- package/dist/pdf-extractor.js +72 -90
- package/dist/performance-metrics.js +80 -102
- package/dist/phash.js +125 -144
- package/dist/pii-scrubber.js +65 -68
- package/dist/playwright-recorder.js +70 -58
- package/dist/policies/crawl-safety.js +31 -34
- package/dist/profiles/domain-profile.js +130 -125
- package/dist/proxy.js +146 -172
- package/dist/rate-limiter.js +58 -65
- package/dist/redirect-tracker.js +60 -64
- package/dist/redis-frontier.js +116 -136
- package/dist/redis-queue.js +157 -144
- package/dist/response-cache.js +127 -140
- package/dist/responsive-detector.js +42 -46
- package/dist/retry-policy.js +46 -50
- package/dist/robots-txt.js +69 -83
- package/dist/screen-reader.js +45 -70
- package/dist/screenshot-masker.js +59 -77
- package/dist/seo-checker.js +88 -107
- package/dist/session-capture.js +192 -212
- package/dist/session-export.js +107 -133
- package/dist/sitemap-writer.js +38 -41
- package/dist/sitemap.js +141 -202
- package/dist/skyvern.js +123 -148
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +74 -90
- package/dist/stealth/human-behavior.js +156 -173
- package/dist/telemetry.js +68 -82
- package/dist/test-executor.js +309 -335
- package/dist/tls-fingerprint.js +77 -88
- package/dist/url-pattern-completer.js +71 -90
- package/dist/visual-diff-verdict.js +23 -38
- package/dist/webhook-notifier.js +71 -69
- package/dist/websocket-detector.js +56 -68
- package/dist/worker.js +29 -38
- package/package.json +1 -1
- package/src/job-queue.ts +136 -0
|
@@ -1,66 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
58
40
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
58
44
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
package/dist/element-hash.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { createHash } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|