browserclaw 0.11.6 → 0.12.1

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/index.d.cts CHANGED
@@ -628,6 +628,72 @@ interface ChallengeWaitResult {
628
628
  /** The challenge still present (null if resolved) */
629
629
  challenge: ChallengeInfo | null;
630
630
  }
631
+ /**
632
+ * A single rule for checking whether the browser session is authenticated.
633
+ * Multiple rules can be combined — all must pass for `authenticated: true`.
634
+ */
635
+ interface AuthCheckRule {
636
+ /** URL must contain this substring (checked against page URL) */
637
+ url?: string;
638
+ /** A cookie with this name must exist (non-empty value) */
639
+ cookie?: string;
640
+ /** A CSS selector that must match at least one element on the page (includes hidden elements) */
641
+ selector?: string;
642
+ /** Text that must be present on the page */
643
+ text?: string;
644
+ /** Text that must NOT be present on the page (e.g. "Sign in", "Log in") */
645
+ textGone?: string;
646
+ /** JavaScript function (as string) that must return a truthy value in the browser context */
647
+ fn?: string;
648
+ }
649
+ /** The kind of auth check rule that was evaluated. */
650
+ type AuthCheckRuleKind = 'url' | 'cookie' | 'selector' | 'text' | 'textGone' | 'fn';
651
+ /** Result of a single auth check rule evaluation. */
652
+ interface AuthCheckDetail {
653
+ /** Which rule type was checked */
654
+ rule: AuthCheckRuleKind;
655
+ /** Whether this individual check passed */
656
+ passed: boolean;
657
+ /** Human-readable detail (e.g. the actual URL, cookie value presence, etc.) */
658
+ detail?: string;
659
+ }
660
+ /** Result of an `isAuthenticated()` call. */
661
+ interface AuthCheckResult {
662
+ /** `true` only if ALL rules passed */
663
+ authenticated: boolean;
664
+ /** Per-rule results for debugging */
665
+ checks: AuthCheckDetail[];
666
+ }
667
+ /** Standard exit reasons for structured telemetry. */
668
+ type ExitReason = 'success' | 'auth_failed' | 'nav_failed' | 'timeout' | 'crash' | 'disconnected' | 'manual' | 'error';
669
+ /** Structured telemetry envelope for a browser session lifecycle. */
670
+ interface RunTelemetry {
671
+ /** Milliseconds to launch Chrome (undefined if connected to existing instance) */
672
+ launchMs?: number;
673
+ /** Milliseconds to establish CDP connection */
674
+ connectMs?: number;
675
+ /** Milliseconds for initial navigation (if a URL was provided at launch) */
676
+ navMs?: number;
677
+ /** Whether auth verification passed (undefined if not checked) */
678
+ authOk?: boolean;
679
+ /** Structured exit reason when the session ends */
680
+ exitReason?: ExitReason | (string & {});
681
+ /** Whether cleanup (process kill, connection close) succeeded */
682
+ cleanupOk?: boolean;
683
+ /** Key timestamps in ISO 8601 format */
684
+ timestamps: {
685
+ /** When the launch/connect sequence started */
686
+ startedAt: string;
687
+ /** When Chrome process became reachable */
688
+ launchedAt?: string;
689
+ /** When Playwright CDP connection was established */
690
+ connectedAt?: string;
691
+ /** When initial navigation completed */
692
+ navigatedAt?: string;
693
+ /** When stop() was called */
694
+ stoppedAt?: string;
695
+ };
696
+ }
631
697
  /** Result of DNS pinning resolution — hostname locked to resolved addresses. */
632
698
  interface PinnedHostname {
633
699
  hostname: string;
@@ -1443,6 +1509,36 @@ declare class CrawlPage {
1443
1509
  timeoutMs?: number;
1444
1510
  pollMs?: number;
1445
1511
  }): Promise<ChallengeWaitResult>;
1512
+ /**
1513
+ * Check whether the current page session appears authenticated.
1514
+ *
1515
+ * Evaluates one or more rules against the page state. All rules must pass
1516
+ * for `authenticated` to be `true`. Returns per-rule details for debugging.
1517
+ *
1518
+ * @param rules - Array of auth check rules (url, cookie, selector, text, textGone, fn)
1519
+ * @returns Authentication status and per-rule check details
1520
+ *
1521
+ * @example
1522
+ * ```ts
1523
+ * // Check by URL and absence of login text
1524
+ * const result = await page.isAuthenticated([
1525
+ * { url: '/dashboard' },
1526
+ * { textGone: 'Sign in' },
1527
+ * ]);
1528
+ * if (!result.authenticated) {
1529
+ * console.log('Auth failed:', result.checks.filter(c => !c.passed));
1530
+ * }
1531
+ *
1532
+ * // Check by cookie presence
1533
+ * const result = await page.isAuthenticated([{ cookie: 'session_id' }]);
1534
+ *
1535
+ * // Check with custom JS function
1536
+ * const result = await page.isAuthenticated([
1537
+ * { fn: '() => !!document.querySelector("[data-user-id]")' },
1538
+ * ]);
1539
+ * ```
1540
+ */
1541
+ isAuthenticated(rules: AuthCheckRule[]): Promise<AuthCheckResult>;
1446
1542
  /**
1447
1543
  * Get the underlying Playwright `Page` object for this tab.
1448
1544
  *
@@ -1516,6 +1612,7 @@ declare class BrowserClaw {
1516
1612
  private readonly ssrfPolicy;
1517
1613
  private readonly recordVideo;
1518
1614
  private chrome;
1615
+ private readonly _telemetry;
1519
1616
  private constructor();
1520
1617
  /**
1521
1618
  * Launch a new Chrome instance and connect to it.
@@ -1631,8 +1728,42 @@ declare class BrowserClaw {
1631
1728
  * If the browser was launched by `BrowserClaw.launch()`, the Chrome process
1632
1729
  * will be terminated. If connected via `BrowserClaw.connect()`, only the
1633
1730
  * Playwright connection is closed.
1731
+ *
1732
+ * @param exitReason - Optional structured reason for stopping (e.g. `'success'`, `'auth_failed'`, `'timeout'`)
1733
+ */
1734
+ stop(exitReason?: ExitReason | (string & {})): Promise<void>;
1735
+ /**
1736
+ * Get structured telemetry for this browser session.
1737
+ *
1738
+ * Returns timing data, timestamps, and exit information collected
1739
+ * throughout the session lifecycle. Useful for diagnosing startup
1740
+ * latency, auth failures, and cleanup issues in cron/unattended runs.
1741
+ *
1742
+ * @returns Telemetry envelope with launch/connect/nav timings and exit info
1743
+ *
1744
+ * @example
1745
+ * ```ts
1746
+ * const browser = await BrowserClaw.launch({ url: 'https://example.com' });
1747
+ * const page = await browser.currentPage();
1748
+ *
1749
+ * // ... do work ...
1750
+ *
1751
+ * const auth = await page.isAuthenticated([{ cookie: 'session' }]);
1752
+ * browser.recordAuthResult(auth.authenticated);
1753
+ *
1754
+ * await browser.stop(auth.authenticated ? 'success' : 'auth_failed');
1755
+ * console.log(browser.telemetry());
1756
+ * // { launchMs: 1823, connectMs: 45, navMs: 620, authOk: true,
1757
+ * // exitReason: 'success', cleanupOk: true, timestamps: { ... } }
1758
+ * ```
1759
+ */
1760
+ telemetry(): Readonly<RunTelemetry>;
1761
+ /**
1762
+ * Record the result of an authentication check in the telemetry envelope.
1763
+ *
1764
+ * @param ok - Whether authentication was successful
1634
1765
  */
1635
- stop(): Promise<void>;
1766
+ recordAuthResult(ok: boolean): void;
1636
1767
  }
1637
1768
 
1638
1769
  /**
@@ -1890,4 +2021,4 @@ declare function waitForChallengeViaPlaywright(opts: {
1890
2021
  pollMs?: number;
1891
2022
  }): Promise<ChallengeWaitResult>;
1892
2023
 
1893
- export { type AriaNode, type AriaSnapshotResult, type BatchAction, type BatchActionResult, BlockedBrowserTargetError, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogEvent, type DialogHandler, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type RequestResult, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightConnection, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, pressAndHoldViaCdp, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingPathsWithinRoot, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
2024
+ export { type AriaNode, type AriaSnapshotResult, type AuthCheckDetail, type AuthCheckResult, type AuthCheckRule, type AuthCheckRuleKind, type BatchAction, type BatchActionResult, BlockedBrowserTargetError, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogEvent, type DialogHandler, type DialogOptions, type DownloadResult, type ExitReason, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type RequestResult, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type RunTelemetry, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightConnection, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, pressAndHoldViaCdp, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingPathsWithinRoot, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
package/dist/index.d.ts CHANGED
@@ -628,6 +628,72 @@ interface ChallengeWaitResult {
628
628
  /** The challenge still present (null if resolved) */
629
629
  challenge: ChallengeInfo | null;
630
630
  }
631
+ /**
632
+ * A single rule for checking whether the browser session is authenticated.
633
+ * Multiple rules can be combined — all must pass for `authenticated: true`.
634
+ */
635
+ interface AuthCheckRule {
636
+ /** URL must contain this substring (checked against page URL) */
637
+ url?: string;
638
+ /** A cookie with this name must exist (non-empty value) */
639
+ cookie?: string;
640
+ /** A CSS selector that must match at least one element on the page (includes hidden elements) */
641
+ selector?: string;
642
+ /** Text that must be present on the page */
643
+ text?: string;
644
+ /** Text that must NOT be present on the page (e.g. "Sign in", "Log in") */
645
+ textGone?: string;
646
+ /** JavaScript function (as string) that must return a truthy value in the browser context */
647
+ fn?: string;
648
+ }
649
+ /** The kind of auth check rule that was evaluated. */
650
+ type AuthCheckRuleKind = 'url' | 'cookie' | 'selector' | 'text' | 'textGone' | 'fn';
651
+ /** Result of a single auth check rule evaluation. */
652
+ interface AuthCheckDetail {
653
+ /** Which rule type was checked */
654
+ rule: AuthCheckRuleKind;
655
+ /** Whether this individual check passed */
656
+ passed: boolean;
657
+ /** Human-readable detail (e.g. the actual URL, cookie value presence, etc.) */
658
+ detail?: string;
659
+ }
660
+ /** Result of an `isAuthenticated()` call. */
661
+ interface AuthCheckResult {
662
+ /** `true` only if ALL rules passed */
663
+ authenticated: boolean;
664
+ /** Per-rule results for debugging */
665
+ checks: AuthCheckDetail[];
666
+ }
667
+ /** Standard exit reasons for structured telemetry. */
668
+ type ExitReason = 'success' | 'auth_failed' | 'nav_failed' | 'timeout' | 'crash' | 'disconnected' | 'manual' | 'error';
669
+ /** Structured telemetry envelope for a browser session lifecycle. */
670
+ interface RunTelemetry {
671
+ /** Milliseconds to launch Chrome (undefined if connected to existing instance) */
672
+ launchMs?: number;
673
+ /** Milliseconds to establish CDP connection */
674
+ connectMs?: number;
675
+ /** Milliseconds for initial navigation (if a URL was provided at launch) */
676
+ navMs?: number;
677
+ /** Whether auth verification passed (undefined if not checked) */
678
+ authOk?: boolean;
679
+ /** Structured exit reason when the session ends */
680
+ exitReason?: ExitReason | (string & {});
681
+ /** Whether cleanup (process kill, connection close) succeeded */
682
+ cleanupOk?: boolean;
683
+ /** Key timestamps in ISO 8601 format */
684
+ timestamps: {
685
+ /** When the launch/connect sequence started */
686
+ startedAt: string;
687
+ /** When Chrome process became reachable */
688
+ launchedAt?: string;
689
+ /** When Playwright CDP connection was established */
690
+ connectedAt?: string;
691
+ /** When initial navigation completed */
692
+ navigatedAt?: string;
693
+ /** When stop() was called */
694
+ stoppedAt?: string;
695
+ };
696
+ }
631
697
  /** Result of DNS pinning resolution — hostname locked to resolved addresses. */
632
698
  interface PinnedHostname {
633
699
  hostname: string;
@@ -1443,6 +1509,36 @@ declare class CrawlPage {
1443
1509
  timeoutMs?: number;
1444
1510
  pollMs?: number;
1445
1511
  }): Promise<ChallengeWaitResult>;
1512
+ /**
1513
+ * Check whether the current page session appears authenticated.
1514
+ *
1515
+ * Evaluates one or more rules against the page state. All rules must pass
1516
+ * for `authenticated` to be `true`. Returns per-rule details for debugging.
1517
+ *
1518
+ * @param rules - Array of auth check rules (url, cookie, selector, text, textGone, fn)
1519
+ * @returns Authentication status and per-rule check details
1520
+ *
1521
+ * @example
1522
+ * ```ts
1523
+ * // Check by URL and absence of login text
1524
+ * const result = await page.isAuthenticated([
1525
+ * { url: '/dashboard' },
1526
+ * { textGone: 'Sign in' },
1527
+ * ]);
1528
+ * if (!result.authenticated) {
1529
+ * console.log('Auth failed:', result.checks.filter(c => !c.passed));
1530
+ * }
1531
+ *
1532
+ * // Check by cookie presence
1533
+ * const result = await page.isAuthenticated([{ cookie: 'session_id' }]);
1534
+ *
1535
+ * // Check with custom JS function
1536
+ * const result = await page.isAuthenticated([
1537
+ * { fn: '() => !!document.querySelector("[data-user-id]")' },
1538
+ * ]);
1539
+ * ```
1540
+ */
1541
+ isAuthenticated(rules: AuthCheckRule[]): Promise<AuthCheckResult>;
1446
1542
  /**
1447
1543
  * Get the underlying Playwright `Page` object for this tab.
1448
1544
  *
@@ -1516,6 +1612,7 @@ declare class BrowserClaw {
1516
1612
  private readonly ssrfPolicy;
1517
1613
  private readonly recordVideo;
1518
1614
  private chrome;
1615
+ private readonly _telemetry;
1519
1616
  private constructor();
1520
1617
  /**
1521
1618
  * Launch a new Chrome instance and connect to it.
@@ -1631,8 +1728,42 @@ declare class BrowserClaw {
1631
1728
  * If the browser was launched by `BrowserClaw.launch()`, the Chrome process
1632
1729
  * will be terminated. If connected via `BrowserClaw.connect()`, only the
1633
1730
  * Playwright connection is closed.
1731
+ *
1732
+ * @param exitReason - Optional structured reason for stopping (e.g. `'success'`, `'auth_failed'`, `'timeout'`)
1733
+ */
1734
+ stop(exitReason?: ExitReason | (string & {})): Promise<void>;
1735
+ /**
1736
+ * Get structured telemetry for this browser session.
1737
+ *
1738
+ * Returns timing data, timestamps, and exit information collected
1739
+ * throughout the session lifecycle. Useful for diagnosing startup
1740
+ * latency, auth failures, and cleanup issues in cron/unattended runs.
1741
+ *
1742
+ * @returns Telemetry envelope with launch/connect/nav timings and exit info
1743
+ *
1744
+ * @example
1745
+ * ```ts
1746
+ * const browser = await BrowserClaw.launch({ url: 'https://example.com' });
1747
+ * const page = await browser.currentPage();
1748
+ *
1749
+ * // ... do work ...
1750
+ *
1751
+ * const auth = await page.isAuthenticated([{ cookie: 'session' }]);
1752
+ * browser.recordAuthResult(auth.authenticated);
1753
+ *
1754
+ * await browser.stop(auth.authenticated ? 'success' : 'auth_failed');
1755
+ * console.log(browser.telemetry());
1756
+ * // { launchMs: 1823, connectMs: 45, navMs: 620, authOk: true,
1757
+ * // exitReason: 'success', cleanupOk: true, timestamps: { ... } }
1758
+ * ```
1759
+ */
1760
+ telemetry(): Readonly<RunTelemetry>;
1761
+ /**
1762
+ * Record the result of an authentication check in the telemetry envelope.
1763
+ *
1764
+ * @param ok - Whether authentication was successful
1634
1765
  */
1635
- stop(): Promise<void>;
1766
+ recordAuthResult(ok: boolean): void;
1636
1767
  }
1637
1768
 
1638
1769
  /**
@@ -1890,4 +2021,4 @@ declare function waitForChallengeViaPlaywright(opts: {
1890
2021
  pollMs?: number;
1891
2022
  }): Promise<ChallengeWaitResult>;
1892
2023
 
1893
- export { type AriaNode, type AriaSnapshotResult, type BatchAction, type BatchActionResult, BlockedBrowserTargetError, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogEvent, type DialogHandler, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type RequestResult, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightConnection, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, pressAndHoldViaCdp, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingPathsWithinRoot, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
2024
+ export { type AriaNode, type AriaSnapshotResult, type AuthCheckDetail, type AuthCheckResult, type AuthCheckRule, type AuthCheckRuleKind, type BatchAction, type BatchActionResult, BlockedBrowserTargetError, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogEvent, type DialogHandler, type DialogOptions, type DownloadResult, type ExitReason, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type RequestResult, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type RunTelemetry, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightConnection, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, pressAndHoldViaCdp, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingPathsWithinRoot, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
package/dist/index.js CHANGED
@@ -1562,6 +1562,7 @@ ${stderrOutput.slice(0, 2e3)}` : "";
1562
1562
  userDataDir,
1563
1563
  cdpPort,
1564
1564
  startedAt,
1565
+ launchMs: Date.now() - startedAt,
1565
1566
  proc
1566
1567
  };
1567
1568
  }
@@ -6316,6 +6317,137 @@ var CrawlPage = class {
6316
6317
  pollMs: opts?.pollMs
6317
6318
  });
6318
6319
  }
6320
+ // ── Auth Health ──────────────────────────────────────────────
6321
+ /**
6322
+ * Check whether the current page session appears authenticated.
6323
+ *
6324
+ * Evaluates one or more rules against the page state. All rules must pass
6325
+ * for `authenticated` to be `true`. Returns per-rule details for debugging.
6326
+ *
6327
+ * @param rules - Array of auth check rules (url, cookie, selector, text, textGone, fn)
6328
+ * @returns Authentication status and per-rule check details
6329
+ *
6330
+ * @example
6331
+ * ```ts
6332
+ * // Check by URL and absence of login text
6333
+ * const result = await page.isAuthenticated([
6334
+ * { url: '/dashboard' },
6335
+ * { textGone: 'Sign in' },
6336
+ * ]);
6337
+ * if (!result.authenticated) {
6338
+ * console.log('Auth failed:', result.checks.filter(c => !c.passed));
6339
+ * }
6340
+ *
6341
+ * // Check by cookie presence
6342
+ * const result = await page.isAuthenticated([{ cookie: 'session_id' }]);
6343
+ *
6344
+ * // Check with custom JS function
6345
+ * const result = await page.isAuthenticated([
6346
+ * { fn: '() => !!document.querySelector("[data-user-id]")' },
6347
+ * ]);
6348
+ * ```
6349
+ */
6350
+ async isAuthenticated(rules) {
6351
+ if (!rules.length) return { authenticated: true, checks: [] };
6352
+ const page = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.targetId });
6353
+ const checks = [];
6354
+ for (const rule of rules) {
6355
+ if (rule.url !== void 0) {
6356
+ const currentUrl = page.url();
6357
+ const passed = currentUrl.includes(rule.url);
6358
+ checks.push({ rule: "url", passed, detail: passed ? currentUrl : `expected "${rule.url}" in "${currentUrl}"` });
6359
+ }
6360
+ if (rule.cookie !== void 0) {
6361
+ const cookies = await page.context().cookies();
6362
+ const found = cookies.some((c) => c.name === rule.cookie && c.value !== "");
6363
+ checks.push({
6364
+ rule: "cookie",
6365
+ passed: found,
6366
+ detail: found ? `cookie "${rule.cookie}" present` : `cookie "${rule.cookie}" missing or empty`
6367
+ });
6368
+ }
6369
+ if (rule.selector !== void 0) {
6370
+ try {
6371
+ const count = await page.locator(rule.selector).count();
6372
+ const passed = count > 0;
6373
+ checks.push({
6374
+ rule: "selector",
6375
+ passed,
6376
+ detail: passed ? `"${rule.selector}" found (${String(count)})` : `"${rule.selector}" not found`
6377
+ });
6378
+ } catch (err) {
6379
+ console.warn(
6380
+ `[browserclaw] isAuthenticated selector check failed: ${err instanceof Error ? err.message : String(err)}`
6381
+ );
6382
+ checks.push({ rule: "selector", passed: false, detail: `"${rule.selector}" error during evaluation` });
6383
+ }
6384
+ }
6385
+ if (rule.text !== void 0 || rule.textGone !== void 0) {
6386
+ let bodyText = null;
6387
+ try {
6388
+ const raw = await evaluateViaPlaywright({
6389
+ cdpUrl: this.cdpUrl,
6390
+ targetId: this.targetId,
6391
+ fn: '() => { const b = document.body; return b ? b.innerText : ""; }'
6392
+ });
6393
+ bodyText = typeof raw === "string" ? raw : null;
6394
+ } catch (err) {
6395
+ console.warn(
6396
+ `[browserclaw] isAuthenticated body text fetch failed: ${err instanceof Error ? err.message : String(err)}`
6397
+ );
6398
+ }
6399
+ if (rule.text !== void 0) {
6400
+ if (bodyText === null) {
6401
+ checks.push({ rule: "text", passed: false, detail: `"${rule.text}" error during evaluation` });
6402
+ } else {
6403
+ const passed = bodyText.includes(rule.text);
6404
+ checks.push({
6405
+ rule: "text",
6406
+ passed,
6407
+ detail: passed ? `"${rule.text}" found` : `"${rule.text}" not found in page text`
6408
+ });
6409
+ }
6410
+ }
6411
+ if (rule.textGone !== void 0) {
6412
+ if (bodyText === null) {
6413
+ checks.push({ rule: "textGone", passed: false, detail: `"${rule.textGone}" error during evaluation` });
6414
+ } else {
6415
+ const passed = !bodyText.includes(rule.textGone);
6416
+ checks.push({
6417
+ rule: "textGone",
6418
+ passed,
6419
+ detail: passed ? `"${rule.textGone}" absent (good)` : `"${rule.textGone}" still present`
6420
+ });
6421
+ }
6422
+ }
6423
+ }
6424
+ if (rule.fn !== void 0) {
6425
+ try {
6426
+ const result = await evaluateViaPlaywright({
6427
+ cdpUrl: this.cdpUrl,
6428
+ targetId: this.targetId,
6429
+ fn: rule.fn
6430
+ });
6431
+ const passed = result !== null && result !== void 0 && result !== false && result !== 0 && result !== "";
6432
+ checks.push({
6433
+ rule: "fn",
6434
+ passed,
6435
+ detail: passed ? "function returned truthy" : `function returned ${JSON.stringify(result)}`
6436
+ });
6437
+ } catch (err) {
6438
+ checks.push({
6439
+ rule: "fn",
6440
+ passed: false,
6441
+ detail: `function threw: ${err instanceof Error ? err.message : String(err)}`
6442
+ });
6443
+ }
6444
+ }
6445
+ }
6446
+ return {
6447
+ authenticated: checks.length > 0 && checks.every((c) => c.passed),
6448
+ checks
6449
+ };
6450
+ }
6319
6451
  // ── Playwright Escape Hatches ─────────────────────────────────
6320
6452
  /**
6321
6453
  * Get the underlying Playwright `Page` object for this tab.
@@ -6374,9 +6506,11 @@ var BrowserClaw = class _BrowserClaw {
6374
6506
  ssrfPolicy;
6375
6507
  recordVideo;
6376
6508
  chrome;
6377
- constructor(cdpUrl, chrome, ssrfPolicy, recordVideo) {
6509
+ _telemetry;
6510
+ constructor(cdpUrl, chrome, telemetry, ssrfPolicy, recordVideo) {
6378
6511
  this.cdpUrl = cdpUrl;
6379
6512
  this.chrome = chrome;
6513
+ this._telemetry = telemetry;
6380
6514
  this.ssrfPolicy = ssrfPolicy;
6381
6515
  this.recordVideo = recordVideo;
6382
6516
  }
@@ -6405,13 +6539,21 @@ var BrowserClaw = class _BrowserClaw {
6405
6539
  * ```
6406
6540
  */
6407
6541
  static async launch(opts = {}) {
6542
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6408
6543
  const chrome = await launchChrome(opts);
6409
6544
  const cdpUrl = `http://127.0.0.1:${String(chrome.cdpPort)}`;
6410
6545
  const ssrfPolicy = opts.allowInternal === true ? { ...opts.ssrfPolicy, dangerouslyAllowPrivateNetwork: true } : opts.ssrfPolicy;
6411
- const browser = new _BrowserClaw(cdpUrl, chrome, ssrfPolicy, opts.recordVideo);
6546
+ const telemetry = {
6547
+ launchMs: chrome.launchMs,
6548
+ timestamps: { startedAt, launchedAt: (/* @__PURE__ */ new Date()).toISOString() }
6549
+ };
6550
+ const browser = new _BrowserClaw(cdpUrl, chrome, telemetry, ssrfPolicy, opts.recordVideo);
6412
6551
  if (opts.url !== void 0 && opts.url !== "") {
6413
6552
  const page = await browser.currentPage();
6553
+ const navT0 = Date.now();
6414
6554
  await page.goto(opts.url);
6555
+ telemetry.navMs = Date.now() - navT0;
6556
+ telemetry.timestamps.navigatedAt = (/* @__PURE__ */ new Date()).toISOString();
6415
6557
  }
6416
6558
  return browser;
6417
6559
  }
@@ -6430,6 +6572,8 @@ var BrowserClaw = class _BrowserClaw {
6430
6572
  * ```
6431
6573
  */
6432
6574
  static async connect(cdpUrl, opts) {
6575
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6576
+ const connectT0 = Date.now();
6433
6577
  let resolvedUrl = cdpUrl;
6434
6578
  if (resolvedUrl === void 0 || resolvedUrl === "") {
6435
6579
  const discovered = await discoverChromeCdpUrl();
@@ -6445,7 +6589,11 @@ var BrowserClaw = class _BrowserClaw {
6445
6589
  }
6446
6590
  await connectBrowser(resolvedUrl, opts?.authToken);
6447
6591
  const ssrfPolicy = opts?.allowInternal === true ? { ...opts.ssrfPolicy, dangerouslyAllowPrivateNetwork: true } : opts?.ssrfPolicy;
6448
- return new _BrowserClaw(resolvedUrl, null, ssrfPolicy, opts?.recordVideo);
6592
+ const telemetry = {
6593
+ connectMs: Date.now() - connectT0,
6594
+ timestamps: { startedAt, connectedAt: (/* @__PURE__ */ new Date()).toISOString() }
6595
+ };
6596
+ return new _BrowserClaw(resolvedUrl, null, telemetry, ssrfPolicy, opts?.recordVideo);
6449
6597
  }
6450
6598
  /**
6451
6599
  * Open a URL in a new tab and return the page handle.
@@ -6474,7 +6622,12 @@ var BrowserClaw = class _BrowserClaw {
6474
6622
  * @returns CrawlPage for the first/active page
6475
6623
  */
6476
6624
  async currentPage() {
6625
+ const connectT0 = Date.now();
6477
6626
  const { browser } = await connectBrowser(this.cdpUrl);
6627
+ if (this._telemetry.connectMs === void 0) {
6628
+ this._telemetry.connectMs = Date.now() - connectT0;
6629
+ this._telemetry.timestamps.connectedAt = (/* @__PURE__ */ new Date()).toISOString();
6630
+ }
6478
6631
  const pages = getAllPages(browser);
6479
6632
  if (!pages.length) throw new Error("No pages available. Use browser.open(url) to create a tab.");
6480
6633
  const tid = await pageTargetId(pages[0]).catch(() => null);
@@ -6551,15 +6704,61 @@ var BrowserClaw = class _BrowserClaw {
6551
6704
  * If the browser was launched by `BrowserClaw.launch()`, the Chrome process
6552
6705
  * will be terminated. If connected via `BrowserClaw.connect()`, only the
6553
6706
  * Playwright connection is closed.
6707
+ *
6708
+ * @param exitReason - Optional structured reason for stopping (e.g. `'success'`, `'auth_failed'`, `'timeout'`)
6554
6709
  */
6555
- async stop() {
6556
- clearRecordingContext(this.cdpUrl);
6557
- await disconnectBrowser();
6558
- if (this.chrome) {
6559
- await stopChrome(this.chrome);
6560
- this.chrome = null;
6710
+ async stop(exitReason) {
6711
+ this._telemetry.timestamps.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
6712
+ if (exitReason !== void 0) this._telemetry.exitReason = exitReason;
6713
+ try {
6714
+ clearRecordingContext(this.cdpUrl);
6715
+ await disconnectBrowser();
6716
+ if (this.chrome) {
6717
+ await stopChrome(this.chrome);
6718
+ this.chrome = null;
6719
+ }
6720
+ this._telemetry.cleanupOk = true;
6721
+ } catch (err) {
6722
+ this._telemetry.cleanupOk = false;
6723
+ throw err;
6561
6724
  }
6562
6725
  }
6726
+ /**
6727
+ * Get structured telemetry for this browser session.
6728
+ *
6729
+ * Returns timing data, timestamps, and exit information collected
6730
+ * throughout the session lifecycle. Useful for diagnosing startup
6731
+ * latency, auth failures, and cleanup issues in cron/unattended runs.
6732
+ *
6733
+ * @returns Telemetry envelope with launch/connect/nav timings and exit info
6734
+ *
6735
+ * @example
6736
+ * ```ts
6737
+ * const browser = await BrowserClaw.launch({ url: 'https://example.com' });
6738
+ * const page = await browser.currentPage();
6739
+ *
6740
+ * // ... do work ...
6741
+ *
6742
+ * const auth = await page.isAuthenticated([{ cookie: 'session' }]);
6743
+ * browser.recordAuthResult(auth.authenticated);
6744
+ *
6745
+ * await browser.stop(auth.authenticated ? 'success' : 'auth_failed');
6746
+ * console.log(browser.telemetry());
6747
+ * // { launchMs: 1823, connectMs: 45, navMs: 620, authOk: true,
6748
+ * // exitReason: 'success', cleanupOk: true, timestamps: { ... } }
6749
+ * ```
6750
+ */
6751
+ telemetry() {
6752
+ return this._telemetry;
6753
+ }
6754
+ /**
6755
+ * Record the result of an authentication check in the telemetry envelope.
6756
+ *
6757
+ * @param ok - Whether authentication was successful
6758
+ */
6759
+ recordAuthResult(ok) {
6760
+ this._telemetry.authOk = ok;
6761
+ }
6563
6762
  };
6564
6763
 
6565
6764
  export { BlockedBrowserTargetError, BrowserClaw, BrowserTabNotFoundError, CrawlPage, InvalidBrowserNavigationUrlError, STEALTH_SCRIPT, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightConnection, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, pressAndHoldViaCdp, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingPathsWithinRoot, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };