@detiq/crawler 0.2.6 → 0.2.9

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 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) rawPage = page;
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
- logger.warn({ landed: sessionCheckUrl }, "[P5] Re-auth failed \u2014 skipping mobile/tablet screenshots");
2616
- AppBrain.saveCrawlEvidence(opts.tenantId, opts.projectId, {
2617
- jobId: opts.jobId,
2618
- type: "AUTH_FAILURE",
2619
- payload: {
2620
- url: opts.appUrl,
2621
- landed: sessionCheckUrl,
2622
- reason: "Session expired before mobile/tablet viewport pass and re-auth failed. Re-capture the session in project settings."
2623
- }
2624
- }).catch(() => {
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
- if (sessionAliveForViewports && extraProfiles.length > 0 && discovered.length > 0) {
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
- console.warn(`[crawler] ${profile} pass failed for ${disc.url}:`, err);
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
- console.warn(`[crawler] ${profile} browser launch failed:`, profileErr);
2731
+ logger.warn({ profile, err: String(profileErr) }, `[P5] ${profile} browser launch failed`);
2714
2732
  } finally {
2715
2733
  await mobileBrowser?.close().catch(() => {
2716
2734
  });