diff --git a/driver_patches/framesPatch.ts b/driver_patches/framesPatch.ts index e86322b..fe85009 100644 --- a/driver_patches/framesPatch.ts +++ b/driver_patches/framesPatch.ts @@ -1,6 +1,41 @@ import { type Project, SyntaxKind } from "ts-morph"; import { assertDefined } from "./utils.ts"; +// URL patterns excluded from networkidle calculations. +// These are captcha providers, analytics, tracking, fraud detection, and session +// heartbeat endpoints that poll continuously and would prevent networkidle from firing. +const NETWORKIDLE_EXCLUDED_URL_PATTERNS = [ + // -- Captcha providers -- + 'challenges.cloudflare.com', + 'google.com/recaptcha', + 'www.gstatic.com/recaptcha', + 'hcaptcha.com', + 'api.funcaptcha.com', + 'client-api.arkoselabs.com', + // -- Analytics & tracking -- + 'google-analytics.com', + 'googletagmanager.com', + 'analytics.google.com', + // -- Session recording & heatmaps -- + 'hotjar.com', + 'fullstory.com', + 'logrocket.com', + 'mouseflow.com', + 'clarity.ms', + // -- Telemetry & monitoring -- + 'browser-intake-datadoghq.com', + 'sentry.io', + 'newrelic.com', + 'nr-data.net', + // -- Fraud detection -- + 'forter.com', + // -- Common polling/heartbeat patterns -- + '/heartbeat', + '/keepalive', + '/keep-alive', + '/beacon', +]; + // ---------------- // server/frames.ts // ---------------- @@ -29,6 +64,30 @@ export function patchFrames(project: Project) { "frame._isolatedWorld = undefined;" ]); + // -- _inflightRequestStarted Method (captcha networkidle exclusion) -- + const inflightStartedMethod = frameManagerClass.getMethodOrThrow("_inflightRequestStarted"); + const inflightStartedBody = inflightStartedMethod.getBodyOrThrow().asKindOrThrow(SyntaxKind.Block); + const faviconCheckStart = assertDefined( + inflightStartedBody.getStatements().find(s => s.getText().includes('request._isFavicon')), + '_isFavicon check in _inflightRequestStarted' + ); + inflightStartedBody.insertStatements(faviconCheckStart.getChildIndex() + 1, [ + `const _reqUrl = request.url();`, + `if (${JSON.stringify(NETWORKIDLE_EXCLUDED_URL_PATTERNS)}.some(p => _reqUrl.includes(p))) return;`, + ]); + + // -- _inflightRequestFinished Method (captcha networkidle exclusion) -- + const inflightFinishedMethod = frameManagerClass.getMethodOrThrow("_inflightRequestFinished"); + const inflightFinishedBody = inflightFinishedMethod.getBodyOrThrow().asKindOrThrow(SyntaxKind.Block); + const faviconCheckFinish = assertDefined( + inflightFinishedBody.getStatements().find(s => s.getText().includes('request._isFavicon')), + '_isFavicon check in _inflightRequestFinished' + ); + inflightFinishedBody.insertStatements(faviconCheckFinish.getChildIndex() + 1, [ + `const _reqUrl = request.url();`, + `if (${JSON.stringify(NETWORKIDLE_EXCLUDED_URL_PATTERNS)}.some(p => _reqUrl.includes(p))) return;`, + ]); + // ------- Frame Class ------- const frameClass = framesSourceFile.getClassOrThrow("Frame"); // Add Properties to the Frame Class