@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
package/dist/session-export.js
CHANGED
|
@@ -1,143 +1,117 @@
|
|
|
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
1
|
function jsStringLiteral(s) {
|
|
12
|
-
|
|
2
|
+
return JSON.stringify(s);
|
|
13
3
|
}
|
|
14
|
-
/** Python dict-literal repr — JSON.stringify's `true`/`false`/`null` aren't
|
|
15
|
-
* valid Python (Python needs `True`/`False`/`None`), so a plain
|
|
16
|
-
* JSON.stringify would emit syntactically invalid Python for any cookie
|
|
17
|
-
* with a boolean field. */
|
|
18
4
|
function pythonDictLiteral(obj) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
repr = JSON.stringify(v);
|
|
29
|
-
return `${JSON.stringify(k)}: ${repr}`;
|
|
30
|
-
});
|
|
31
|
-
return `{${parts.join(', ')}}`;
|
|
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(", ")}}`;
|
|
32
14
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
'// Usage: import this file and call restoreZetaSession() inside cy.session(), e.g.:',
|
|
58
|
-
"// cy.session('zeta-session', restoreZetaSession)",
|
|
59
|
-
"Cypress.Commands.add('restoreZetaSession', () => {",
|
|
60
|
-
...cookieLines,
|
|
61
|
-
...localStorageBlocks,
|
|
62
|
-
'});',
|
|
63
|
-
].join('\n');
|
|
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");
|
|
64
39
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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)})`);
|
|
74
60
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
for (const c of cookies) {
|
|
84
|
-
const cookieDict = { name: c.name, value: c.value, path: c.path };
|
|
85
|
-
if (c.expires && c.expires > 0)
|
|
86
|
-
cookieDict.expiry = Math.floor(c.expires);
|
|
87
|
-
if (c.secure)
|
|
88
|
-
cookieDict.secure = true;
|
|
89
|
-
lines.push(` driver.add_cookie(${pythonDictLiteral(cookieDict)})`);
|
|
90
|
-
}
|
|
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
|
+
);
|
|
91
69
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
continue;
|
|
95
|
-
lines.push(` driver.get(${JSON.stringify(o.origin)})`);
|
|
96
|
-
for (const kv of o.localStorage) {
|
|
97
|
-
lines.push(` driver.execute_script(${JSON.stringify(`window.localStorage.setItem(arguments[0], arguments[1]);`)}, ${JSON.stringify(kv.name)}, ${JSON.stringify(kv.value)})`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return lines.join('\n');
|
|
70
|
+
}
|
|
71
|
+
return lines.join("\n");
|
|
101
72
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
...localStorageSets,
|
|
128
|
-
'}',
|
|
129
|
-
'module.exports = { restoreZetaSession };',
|
|
130
|
-
].join('\n');
|
|
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");
|
|
131
98
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
110
|
}
|
|
111
|
+
export {
|
|
112
|
+
exportSessionAs,
|
|
113
|
+
toCypressSnippet,
|
|
114
|
+
toSeleniumPythonDict,
|
|
115
|
+
toWebdriverIOSnippet
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=session-export.js.map
|
package/dist/sitemap-writer.js
CHANGED
|
@@ -1,50 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
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
|
-
import * as fs from 'fs/promises';
|
|
9
|
-
import * as path from 'path';
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
10
3
|
const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
11
4
|
const SITEMAP_NS = 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
|
|
12
5
|
function escape(s) {
|
|
13
|
-
|
|
6
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
14
7
|
}
|
|
15
8
|
function buildSitemapXml(urls) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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>`;
|
|
25
20
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
async function writeSitemap(urls, outputPath) {
|
|
22
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
23
|
+
await fs.writeFile(outputPath, buildSitemapXml(urls), "utf8");
|
|
29
24
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
lastmod: s.updatedAt ? s.updatedAt.toISOString().split('T')[0] : undefined,
|
|
44
|
-
changefreq: 'weekly',
|
|
45
|
-
priority: s.depth === 0 ? 1.0 : s.depth === 1 ? 0.8 : 0.6,
|
|
46
|
-
}));
|
|
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
|
+
}));
|
|
47
38
|
}
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
function buildSitemapString(urls) {
|
|
40
|
+
return buildSitemapXml(urls);
|
|
50
41
|
}
|
|
42
|
+
export {
|
|
43
|
+
buildSitemapString,
|
|
44
|
+
screensToSitemapUrls,
|
|
45
|
+
writeSitemap
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=sitemap-writer.js.map
|