@detiq/crawler 0.2.6 → 0.2.7
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/crawler.js +61 -43
- package/dist/crawler.js.map +2 -2
- package/package.json +1 -1
package/dist/crawler.js
CHANGED
|
@@ -586,20 +586,6 @@ async function crawlProject(opts, onProgress) {
|
|
|
586
586
|
} catch {
|
|
587
587
|
}
|
|
588
588
|
});
|
|
589
|
-
if (opts.httpBasicUsername && opts.httpBasicPassword) {
|
|
590
|
-
const encoded = Buffer.from(`${opts.httpBasicUsername}:${opts.httpBasicPassword}`).toString("base64");
|
|
591
|
-
const appOrigin = new URL(opts.appUrl).origin;
|
|
592
|
-
await page.route("**/*", (route) => {
|
|
593
|
-
const reqUrl = route.request().url();
|
|
594
|
-
if (reqUrl.startsWith(appOrigin)) {
|
|
595
|
-
route.continue({
|
|
596
|
-
headers: { ...route.request().headers(), "Authorization": `Basic ${encoded}` }
|
|
597
|
-
});
|
|
598
|
-
} else {
|
|
599
|
-
route.continue();
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
589
|
if (opts.authToken) {
|
|
604
590
|
const headerName = opts.authTokenHeader ?? "Authorization";
|
|
605
591
|
const prefix = opts.authTokenPrefix ?? "Bearer ";
|
|
@@ -610,15 +596,6 @@ async function crawlProject(opts, onProgress) {
|
|
|
610
596
|
localStorage.setItem("token", token);
|
|
611
597
|
}, opts.authToken);
|
|
612
598
|
}
|
|
613
|
-
const crawlerSecret = process.env.CRAWLER_INTERNAL_SECRET;
|
|
614
|
-
if (crawlerSecret) {
|
|
615
|
-
const loginPattern = `${new URL(opts.appUrl).origin}**/auth/login*`;
|
|
616
|
-
await page.route(loginPattern, (route) => {
|
|
617
|
-
route.continue({
|
|
618
|
-
headers: { ...route.request().headers(), "x-zeta-crawler": crawlerSecret }
|
|
619
|
-
});
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
599
|
await page.addInitScript(() => {
|
|
623
600
|
try {
|
|
624
601
|
window.__zetaCLS = 0;
|
|
@@ -698,7 +675,33 @@ async function crawlProject(opts, onProgress) {
|
|
|
698
675
|
}
|
|
699
676
|
} catch {
|
|
700
677
|
}
|
|
701
|
-
if (!rawPage)
|
|
678
|
+
if (!rawPage) {
|
|
679
|
+
rawPage = page;
|
|
680
|
+
logger.warn("[crawler] rawPage fell back to Stagehand proxy \u2014 CDP connectOverCDP failed; route() calls will be skipped");
|
|
681
|
+
}
|
|
682
|
+
if (opts.httpBasicUsername && opts.httpBasicPassword && typeof rawPage?.route === "function") {
|
|
683
|
+
const encoded = Buffer.from(`${opts.httpBasicUsername}:${opts.httpBasicPassword}`).toString("base64");
|
|
684
|
+
const appOrigin = new URL(opts.appUrl).origin;
|
|
685
|
+
await rawPage.route("**/*", (route) => {
|
|
686
|
+
const reqUrl = route.request().url();
|
|
687
|
+
if (reqUrl.startsWith(appOrigin)) {
|
|
688
|
+
route.continue({
|
|
689
|
+
headers: { ...route.request().headers(), "Authorization": `Basic ${encoded}` }
|
|
690
|
+
});
|
|
691
|
+
} else {
|
|
692
|
+
route.continue();
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
const crawlerSecret = process.env.CRAWLER_INTERNAL_SECRET;
|
|
697
|
+
if (crawlerSecret && typeof rawPage?.route === "function") {
|
|
698
|
+
const loginPattern = `${new URL(opts.appUrl).origin}**/auth/login*`;
|
|
699
|
+
await rawPage.route(loginPattern, (route) => {
|
|
700
|
+
route.continue({
|
|
701
|
+
headers: { ...route.request().headers(), "x-zeta-crawler": crawlerSecret }
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
}
|
|
702
705
|
try {
|
|
703
706
|
await onProgress?.(8, "Loading app");
|
|
704
707
|
await page.goto(opts.appUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(async () => {
|
|
@@ -2589,6 +2592,11 @@ ${snippet}`,
|
|
|
2589
2592
|
if (visited.size >= 5) {
|
|
2590
2593
|
await onProgress?.(90, `App map saved \xB7 ${visited.size} screens, ${edges.length} flows wired`);
|
|
2591
2594
|
}
|
|
2595
|
+
let mainStorageState;
|
|
2596
|
+
try {
|
|
2597
|
+
mainStorageState = await playwrightBrowser.contexts()[0]?.storageState();
|
|
2598
|
+
} catch {
|
|
2599
|
+
}
|
|
2592
2600
|
let sessionAliveForViewports = true;
|
|
2593
2601
|
try {
|
|
2594
2602
|
await page.goto(opts.appUrl, { waitUntil: "domcontentloaded", timeout: 15e3 });
|
|
@@ -2609,20 +2617,27 @@ ${snippet}`,
|
|
|
2609
2617
|
).catch(() => false);
|
|
2610
2618
|
}
|
|
2611
2619
|
if (reauthed) {
|
|
2620
|
+
try {
|
|
2621
|
+
mainStorageState = await playwrightBrowser.contexts()[0]?.storageState();
|
|
2622
|
+
} catch {
|
|
2623
|
+
}
|
|
2612
2624
|
logger.info("[P5] Re-auth succeeded \u2014 proceeding with viewport pass");
|
|
2613
2625
|
} else {
|
|
2614
2626
|
sessionAliveForViewports = false;
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2627
|
+
const hasCookies = (mainStorageState?.cookies?.length ?? 0) > 0;
|
|
2628
|
+
logger.warn({ landed: sessionCheckUrl, hasCookies }, "[P5] Re-auth failed \u2014 will attempt viewport pass with exported cookies");
|
|
2629
|
+
if (!hasCookies) {
|
|
2630
|
+
AppBrain.saveCrawlEvidence(opts.tenantId, opts.projectId, {
|
|
2631
|
+
jobId: opts.jobId,
|
|
2632
|
+
type: "AUTH_FAILURE",
|
|
2633
|
+
payload: {
|
|
2634
|
+
url: opts.appUrl,
|
|
2635
|
+
landed: sessionCheckUrl,
|
|
2636
|
+
reason: "Session expired before mobile/tablet viewport pass and re-auth failed. Re-capture the session in project settings."
|
|
2637
|
+
}
|
|
2638
|
+
}).catch(() => {
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2626
2641
|
}
|
|
2627
2642
|
} else {
|
|
2628
2643
|
logger.info({ url: sessionCheckUrl }, "[P5] Session alive \u2014 proceeding with viewport pass");
|
|
@@ -2631,7 +2646,8 @@ ${snippet}`,
|
|
|
2631
2646
|
logger.warn({ err: String(err) }, "[P5] Session health check navigation failed \u2014 proceeding with viewport pass anyway");
|
|
2632
2647
|
}
|
|
2633
2648
|
const extraProfiles = (opts.deviceProfiles ?? []).filter((p) => p !== "DESKTOP");
|
|
2634
|
-
|
|
2649
|
+
const hasExportedAuth = (mainStorageState?.cookies?.length ?? 0) > 0;
|
|
2650
|
+
if ((sessionAliveForViewports || hasExportedAuth) && extraProfiles.length > 0 && discovered.length > 0) {
|
|
2635
2651
|
const { devices, chromium: pwChromium } = await import("playwright");
|
|
2636
2652
|
const DEVICE_MAP = {
|
|
2637
2653
|
// Override viewport heights: Playwright's device descriptors use viewport-minus-browser-chrome
|
|
@@ -2640,11 +2656,6 @@ ${snippet}`,
|
|
|
2640
2656
|
MOBILE_PORTRAIT: { ...devices["iPhone 15"], viewport: { width: 393, height: 852 } },
|
|
2641
2657
|
TABLET: { ...devices["iPad Pro 11"], viewport: { width: 834, height: 1194 } }
|
|
2642
2658
|
};
|
|
2643
|
-
let mainStorageState;
|
|
2644
|
-
try {
|
|
2645
|
-
mainStorageState = await playwrightBrowser.contexts()[0]?.storageState();
|
|
2646
|
-
} catch {
|
|
2647
|
-
}
|
|
2648
2659
|
for (const profile of extraProfiles) {
|
|
2649
2660
|
const deviceConfig = DEVICE_MAP[profile];
|
|
2650
2661
|
if (!deviceConfig) continue;
|
|
@@ -2702,7 +2713,14 @@ ${snippet}`,
|
|
|
2702
2713
|
});
|
|
2703
2714
|
}
|
|
2704
2715
|
} catch (err) {
|
|
2705
|
-
|
|
2716
|
+
logger.warn({ profile, url: disc.url, err: String(err) }, `[P5] ${profile} screenshot failed \u2014 skipping screen`);
|
|
2717
|
+
AppBrain.saveCrawlEvidence(opts.tenantId, opts.projectId, {
|
|
2718
|
+
jobId: opts.jobId,
|
|
2719
|
+
screenId: disc.screenId,
|
|
2720
|
+
type: "VIEWPORT_SCREENSHOT_FAILURE",
|
|
2721
|
+
payload: { profile, url: disc.url, reason: String(err) }
|
|
2722
|
+
}).catch(() => {
|
|
2723
|
+
});
|
|
2706
2724
|
}
|
|
2707
2725
|
}
|
|
2708
2726
|
await mobilePage.close().catch(() => {
|
|
@@ -2710,7 +2728,7 @@ ${snippet}`,
|
|
|
2710
2728
|
await mobileCtx.close().catch(() => {
|
|
2711
2729
|
});
|
|
2712
2730
|
} catch (profileErr) {
|
|
2713
|
-
|
|
2731
|
+
logger.warn({ profile, err: String(profileErr) }, `[P5] ${profile} browser launch failed`);
|
|
2714
2732
|
} finally {
|
|
2715
2733
|
await mobileBrowser?.close().catch(() => {
|
|
2716
2734
|
});
|