@detiq/crawler 1.1.14 → 1.1.15
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/Dockerfile +1 -1
- package/dist/accessibility-tree-crawler.js +156 -0
- package/dist/accessibility-tree-crawler.js.map +7 -0
- package/dist/advanced-ui-probes.js +310 -0
- package/dist/advanced-ui-probes.js.map +7 -0
- package/dist/content-chunker.js +2 -2
- package/dist/content-chunker.js.map +2 -2
- package/dist/crawl-session-manager.js +145 -0
- package/dist/crawl-session-manager.js.map +7 -0
- package/dist/crawler.js +1142 -263
- package/dist/crawler.js.map +2 -2
- package/dist/crud-interaction.js +264 -0
- package/dist/crud-interaction.js.map +7 -0
- package/dist/dropdown-enumerator.js +230 -0
- package/dist/dropdown-enumerator.js.map +7 -0
- package/dist/filter-pagination-crawler.js +299 -0
- package/dist/filter-pagination-crawler.js.map +7 -0
- package/dist/frontier.js +5 -3
- package/dist/frontier.js.map +2 -2
- package/dist/gap-pass.js +47 -14
- package/dist/gap-pass.js.map +2 -2
- package/dist/http-engine.js +28 -15
- package/dist/http-engine.js.map +2 -2
- package/dist/index.js +15 -1
- package/dist/index.js.map +2 -2
- package/dist/job-queue.js +12 -5
- package/dist/job-queue.js.map +2 -2
- package/dist/locator-extractor.js +23 -0
- package/dist/locator-extractor.js.map +2 -2
- package/dist/multi-context-handler.js +182 -0
- package/dist/multi-context-handler.js.map +7 -0
- package/dist/nested-window-crawler.js +172 -0
- package/dist/nested-window-crawler.js.map +7 -0
- package/dist/network-action-correlator.js +209 -0
- package/dist/network-action-correlator.js.map +7 -0
- package/dist/performance-metrics-collector.js +155 -0
- package/dist/performance-metrics-collector.js.map +7 -0
- package/dist/performance-metrics.js.map +2 -2
- package/dist/policies/crawl-safety.js +5 -1
- package/dist/policies/crawl-safety.js.map +2 -2
- package/dist/profiles/domain-profile.js +21 -1
- package/dist/profiles/domain-profile.js.map +2 -2
- package/dist/rate-limiter.js +2 -2
- package/dist/rate-limiter.js.map +2 -2
- package/dist/screen-reader.js +1 -0
- package/dist/screen-reader.js.map +2 -2
- package/dist/service-worker-interceptor.js +136 -0
- package/dist/service-worker-interceptor.js.map +7 -0
- package/dist/session-capture.js +4 -2
- package/dist/session-capture.js.map +2 -2
- package/dist/shadow-dom-crawler.js +132 -0
- package/dist/shadow-dom-crawler.js.map +7 -0
- package/dist/sitemap.js +16 -10
- package/dist/sitemap.js.map +2 -2
- package/dist/state-graph.js +193 -0
- package/dist/state-graph.js.map +7 -0
- package/dist/stealth/human-behavior.js +36 -3
- package/dist/stealth/human-behavior.js.map +2 -2
- package/dist/url-pattern-completer.js +48 -14
- package/dist/url-pattern-completer.js.map +2 -2
- package/dist/visual-diff-verdict.js +3 -1
- package/dist/visual-diff-verdict.js.map +2 -2
- package/package.json +2 -1
- package/src/accessibility-tree-crawler.ts +246 -0
- package/src/advanced-ui-probes.ts +470 -0
- package/src/crawl-session-manager.ts +231 -0
- package/src/crawler.ts +1050 -134
- package/src/crud-interaction.ts +386 -0
- package/src/dropdown-enumerator.ts +312 -0
- package/src/filter-pagination-crawler.ts +500 -0
- package/src/gap-pass.ts +58 -16
- package/src/index.ts +14 -1
- package/src/locator-extractor.ts +45 -0
- package/src/multi-context-handler.ts +296 -0
- package/src/nested-window-crawler.ts +310 -0
- package/src/network-action-correlator.ts +320 -0
- package/src/performance-metrics-collector.ts +243 -0
- package/src/performance-metrics.ts +1 -1
- package/src/screen-reader.ts +4 -1
- package/src/service-worker-interceptor.ts +185 -0
- package/src/session-capture.ts +3 -0
- package/src/shadow-dom-crawler.ts +198 -0
- package/src/state-graph.ts +280 -0
- package/src/stealth/human-behavior.ts +38 -4
- package/src/url-pattern-completer.ts +61 -15
- package/tsconfig.json +1 -1
- package/types/accessibility-tree-crawler.d.ts +44 -0
- package/types/advanced-ui-probes.d.ts +47 -0
- package/types/crawl-session-manager.d.ts +44 -0
- package/types/crawler.d.ts +3 -1
- package/types/crud-interaction.d.ts +31 -0
- package/types/dropdown-enumerator.d.ts +20 -0
- package/types/filter-pagination-crawler.d.ts +52 -0
- package/types/gap-pass.d.ts +1 -0
- package/types/index.d.ts +14 -1
- package/types/job-queue.d.ts +18 -9
- package/types/locator-extractor.d.ts +7 -0
- package/types/multi-context-handler.d.ts +55 -0
- package/types/nested-window-crawler.d.ts +41 -0
- package/types/network-action-correlator.d.ts +47 -0
- package/types/performance-metrics-collector.d.ts +50 -0
- package/types/performance-metrics.d.ts +1 -2
- package/types/screen-reader.d.ts +1 -2
- package/types/service-worker-interceptor.d.ts +36 -0
- package/types/session-capture.d.ts +1 -1
- package/types/shadow-dom-crawler.d.ts +35 -0
- package/types/sitemap.d.ts +2 -2
- package/types/state-graph.d.ts +59 -0
- package/types/stealth/human-behavior.d.ts +1 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const LOGIN_PATH_PATTERNS = [
|
|
2
|
+
"/login",
|
|
3
|
+
"/sign-in",
|
|
4
|
+
"/signin",
|
|
5
|
+
"/auth/login",
|
|
6
|
+
"/auth/sign-in"
|
|
7
|
+
];
|
|
8
|
+
function createSessionManager(config) {
|
|
9
|
+
let healthy = true;
|
|
10
|
+
let refreshAttempts = 0;
|
|
11
|
+
let authFailure = false;
|
|
12
|
+
const events = [];
|
|
13
|
+
const maxAttempts = config.maxRefreshAttempts ?? 3;
|
|
14
|
+
function onResponse(url, status) {
|
|
15
|
+
if ((status === 401 || status === 403) && healthy) {
|
|
16
|
+
healthy = false;
|
|
17
|
+
events.push({ type: "EXPIRY_DETECTED", isoTimestamp: (/* @__PURE__ */ new Date()).toISOString(), url });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function onNavigation(url) {
|
|
21
|
+
const isLoginPage = LOGIN_PATH_PATTERNS.some((pattern) => url.includes(pattern));
|
|
22
|
+
if (isLoginPage && healthy) {
|
|
23
|
+
healthy = false;
|
|
24
|
+
events.push({ type: "EXPIRY_DETECTED", isoTimestamp: (/* @__PURE__ */ new Date()).toISOString(), url });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isHealthy() {
|
|
28
|
+
return healthy;
|
|
29
|
+
}
|
|
30
|
+
function needsRefresh() {
|
|
31
|
+
return !healthy && !authFailure && refreshAttempts < maxAttempts;
|
|
32
|
+
}
|
|
33
|
+
function markHealthy(isoTimestamp) {
|
|
34
|
+
healthy = true;
|
|
35
|
+
events.push({ type: "HEALTHY", isoTimestamp });
|
|
36
|
+
}
|
|
37
|
+
async function tryRefreshEndpoint(page, isoTimestamp) {
|
|
38
|
+
if (!config.refreshEndpoint) return false;
|
|
39
|
+
events.push({
|
|
40
|
+
type: "REFRESH_ATTEMPT",
|
|
41
|
+
isoTimestamp,
|
|
42
|
+
strategy: "refreshEndpoint",
|
|
43
|
+
attemptCount: refreshAttempts + 1
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
const result = await page.evaluate(async (endpoint) => {
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(endpoint, { method: "POST", credentials: "include" });
|
|
49
|
+
if (res.ok) {
|
|
50
|
+
const data = await res.json().catch(() => ({}));
|
|
51
|
+
return { ok: true, token: data.access_token || data.token || null };
|
|
52
|
+
}
|
|
53
|
+
return { ok: false, token: null };
|
|
54
|
+
} catch {
|
|
55
|
+
return { ok: false, token: null };
|
|
56
|
+
}
|
|
57
|
+
}, config.refreshEndpoint);
|
|
58
|
+
if (result.ok) {
|
|
59
|
+
if (result.token && config.tokenLocalStorageKey) {
|
|
60
|
+
await page.evaluate(
|
|
61
|
+
([key, val]) => localStorage.setItem(key, val),
|
|
62
|
+
[config.tokenLocalStorageKey, result.token]
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
refreshAttempts++;
|
|
66
|
+
events.push({ type: "REFRESH_SUCCESS", isoTimestamp, strategy: "refreshEndpoint" });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
async function tryReLogin(page, isoTimestamp) {
|
|
74
|
+
if (!config.loginUrl || !config.email || !config.password) return false;
|
|
75
|
+
events.push({
|
|
76
|
+
type: "REFRESH_ATTEMPT",
|
|
77
|
+
isoTimestamp,
|
|
78
|
+
strategy: "reLogin",
|
|
79
|
+
attemptCount: refreshAttempts + 1
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
await page.goto(config.loginUrl, { waitUntil: "domcontentloaded" });
|
|
83
|
+
await page.locator('input[type="email"], input[name="email"], input[name="username"]').first().fill(config.email);
|
|
84
|
+
await page.locator('input[type="password"]').first().fill(config.password);
|
|
85
|
+
await page.locator(
|
|
86
|
+
'button[type="submit"], button:has-text("Sign In"), button:has-text("Log In"), button:has-text("Login")'
|
|
87
|
+
).first().click();
|
|
88
|
+
await page.waitForFunction(
|
|
89
|
+
(loginUrl) => !location.href.includes(loginUrl),
|
|
90
|
+
config.loginUrl,
|
|
91
|
+
{ timeout: 1e4 }
|
|
92
|
+
);
|
|
93
|
+
events.push({ type: "REFRESH_SUCCESS", isoTimestamp, strategy: "reLogin" });
|
|
94
|
+
return true;
|
|
95
|
+
} catch {
|
|
96
|
+
events.push({ type: "REFRESH_FAILED", isoTimestamp, strategy: "reLogin" });
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function refresh(page, isoTimestamp) {
|
|
101
|
+
if (config.refreshEndpoint) {
|
|
102
|
+
const ok = await tryRefreshEndpoint(page, isoTimestamp);
|
|
103
|
+
if (ok) return true;
|
|
104
|
+
}
|
|
105
|
+
if (config.loginUrl && config.email && config.password) {
|
|
106
|
+
const ok = await tryReLogin(page, isoTimestamp);
|
|
107
|
+
if (ok) return true;
|
|
108
|
+
}
|
|
109
|
+
refreshAttempts++;
|
|
110
|
+
events.push({ type: "REFRESH_FAILED", isoTimestamp, attemptCount: refreshAttempts });
|
|
111
|
+
if (refreshAttempts >= maxAttempts) {
|
|
112
|
+
authFailure = true;
|
|
113
|
+
events.push({ type: "AUTH_FAILURE", isoTimestamp, attemptCount: refreshAttempts });
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
function getEvents() {
|
|
118
|
+
return [...events];
|
|
119
|
+
}
|
|
120
|
+
function getStats() {
|
|
121
|
+
return { healthy, refreshAttempts, authFailure };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
onResponse,
|
|
125
|
+
onNavigation,
|
|
126
|
+
isHealthy,
|
|
127
|
+
needsRefresh,
|
|
128
|
+
markHealthy,
|
|
129
|
+
refresh,
|
|
130
|
+
getEvents,
|
|
131
|
+
getStats
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function attachSessionWatcher(page, manager) {
|
|
135
|
+
const onResponse = (res) => {
|
|
136
|
+
manager.onResponse(res.url(), res.status());
|
|
137
|
+
};
|
|
138
|
+
page.on("response", onResponse);
|
|
139
|
+
return () => page.off("response", onResponse);
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
attachSessionWatcher,
|
|
143
|
+
createSessionManager
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=crawl-session-manager.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/crawl-session-manager.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Session management for authenticated crawl sessions.\n * Detects auth expiry via HTTP status codes and login-page redirects,\n * then attempts recovery via a token refresh endpoint or re-login flow.\n * Designed to keep long-running crawls alive without manual intervention.\n */\n\nimport type { Page } from 'playwright';\n\nexport interface SessionConfig {\n loginUrl?: string;\n email?: string;\n password?: string;\n healthCheckUrl?: string;\n refreshEndpoint?: string;\n tokenLocalStorageKey?: string;\n maxRefreshAttempts?: number;\n}\n\nexport type SessionEventType =\n | 'HEALTHY'\n | 'EXPIRY_DETECTED'\n | 'REFRESH_ATTEMPT'\n | 'REFRESH_SUCCESS'\n | 'REFRESH_FAILED'\n | 'AUTH_FAILURE';\n\nexport interface SessionEvent {\n type: SessionEventType;\n isoTimestamp: string;\n url?: string;\n strategy?: string;\n attemptCount?: number;\n}\n\nexport interface SessionManager {\n onResponse(url: string, status: number): void;\n onNavigation(url: string): void;\n isHealthy(): boolean;\n needsRefresh(): boolean;\n markHealthy(isoTimestamp: string): void;\n refresh(page: Page, isoTimestamp: string): Promise<boolean>;\n getEvents(): SessionEvent[];\n getStats(): { healthy: boolean; refreshAttempts: number; authFailure: boolean };\n}\n\nconst LOGIN_PATH_PATTERNS = [\n '/login',\n '/sign-in',\n '/signin',\n '/auth/login',\n '/auth/sign-in',\n];\n\nexport function createSessionManager(config: SessionConfig): SessionManager {\n let healthy = true;\n let refreshAttempts = 0;\n let authFailure = false;\n const events: SessionEvent[] = [];\n const maxAttempts = config.maxRefreshAttempts ?? 3;\n\n function onResponse(url: string, status: number): void {\n if ((status === 401 || status === 403) && healthy) {\n healthy = false;\n events.push({ type: 'EXPIRY_DETECTED', isoTimestamp: new Date().toISOString(), url });\n }\n }\n\n function onNavigation(url: string): void {\n const isLoginPage = LOGIN_PATH_PATTERNS.some((pattern) => url.includes(pattern));\n if (isLoginPage && healthy) {\n healthy = false;\n events.push({ type: 'EXPIRY_DETECTED', isoTimestamp: new Date().toISOString(), url });\n }\n }\n\n function isHealthy(): boolean {\n return healthy;\n }\n\n function needsRefresh(): boolean {\n return !healthy && !authFailure && refreshAttempts < maxAttempts;\n }\n\n function markHealthy(isoTimestamp: string): void {\n healthy = true;\n events.push({ type: 'HEALTHY', isoTimestamp });\n }\n\n async function tryRefreshEndpoint(page: Page, isoTimestamp: string): Promise<boolean> {\n if (!config.refreshEndpoint) return false;\n\n events.push({\n type: 'REFRESH_ATTEMPT',\n isoTimestamp,\n strategy: 'refreshEndpoint',\n attemptCount: refreshAttempts + 1,\n });\n\n try {\n const result = await page.evaluate(async (endpoint) => {\n try {\n const res = await fetch(endpoint, { method: 'POST', credentials: 'include' });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return { ok: true, token: (data as any).access_token || (data as any).token || null };\n }\n return { ok: false, token: null };\n } catch {\n return { ok: false, token: null };\n }\n }, config.refreshEndpoint);\n\n if (result.ok) {\n if (result.token && config.tokenLocalStorageKey) {\n await page.evaluate(\n ([key, val]) => localStorage.setItem(key, val),\n [config.tokenLocalStorageKey, result.token] as [string, string],\n );\n }\n refreshAttempts++;\n events.push({ type: 'REFRESH_SUCCESS', isoTimestamp, strategy: 'refreshEndpoint' });\n return true;\n }\n } catch {\n // page.evaluate or localStorage.setItem failed \u2014 fall through\n }\n\n return false;\n }\n\n async function tryReLogin(page: Page, isoTimestamp: string): Promise<boolean> {\n if (!config.loginUrl || !config.email || !config.password) return false;\n\n events.push({\n type: 'REFRESH_ATTEMPT',\n isoTimestamp,\n strategy: 'reLogin',\n attemptCount: refreshAttempts + 1,\n });\n\n try {\n await page.goto(config.loginUrl, { waitUntil: 'domcontentloaded' });\n\n await page\n .locator('input[type=\"email\"], input[name=\"email\"], input[name=\"username\"]')\n .first()\n .fill(config.email);\n\n await page\n .locator('input[type=\"password\"]')\n .first()\n .fill(config.password);\n\n await page\n .locator(\n 'button[type=\"submit\"], button:has-text(\"Sign In\"), button:has-text(\"Log In\"), button:has-text(\"Login\")',\n )\n .first()\n .click();\n\n await page.waitForFunction(\n (loginUrl) => !location.href.includes(loginUrl),\n config.loginUrl,\n { timeout: 10000 },\n );\n\n events.push({ type: 'REFRESH_SUCCESS', isoTimestamp, strategy: 'reLogin' });\n return true;\n } catch {\n events.push({ type: 'REFRESH_FAILED', isoTimestamp, strategy: 'reLogin' });\n return false;\n }\n }\n\n async function refresh(page: Page, isoTimestamp: string): Promise<boolean> {\n // Strategy 1: token refresh endpoint\n if (config.refreshEndpoint) {\n const ok = await tryRefreshEndpoint(page, isoTimestamp);\n if (ok) return true;\n }\n\n // Strategy 2: re-login\n if (config.loginUrl && config.email && config.password) {\n const ok = await tryReLogin(page, isoTimestamp);\n if (ok) return true;\n }\n\n // All strategies exhausted\n refreshAttempts++;\n events.push({ type: 'REFRESH_FAILED', isoTimestamp, attemptCount: refreshAttempts });\n\n if (refreshAttempts >= maxAttempts) {\n authFailure = true;\n events.push({ type: 'AUTH_FAILURE', isoTimestamp, attemptCount: refreshAttempts });\n }\n\n return false;\n }\n\n function getEvents(): SessionEvent[] {\n return [...events];\n }\n\n function getStats(): { healthy: boolean; refreshAttempts: number; authFailure: boolean } {\n return { healthy, refreshAttempts, authFailure };\n }\n\n return {\n onResponse,\n onNavigation,\n isHealthy,\n needsRefresh,\n markHealthy,\n refresh,\n getEvents,\n getStats,\n };\n}\n\n/**\n * Attaches a Playwright response listener to feed HTTP status codes into the\n * session manager. Returns a cleanup function to remove the listener.\n */\nexport function attachSessionWatcher(page: Page, manager: SessionManager): () => void {\n const onResponse = (res: import('playwright').Response) => {\n manager.onResponse(res.url(), res.status());\n };\n page.on('response', onResponse);\n return () => page.off('response', onResponse);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|