import { isMonolithLogOrigin, MAX_CORRELATED_REQUESTS, sanitizeRequestCorrelation, } from '../core/request-correlation'; import { canRouteHttpToJourney, resolveJourneyEngine } from '../core/runtime'; import type { HttpCompleteReport } from './http-report'; import type { JourneyRequestEvent } from '../core/types'; const capturedResponses = new WeakMap(); interface ResponseHeaders { headers?: { 'cf-ray'?: unknown; 'get'?(name: string): unknown }; url?: string; responseURL?: string; request?: { responseURL?: string }; getResponseHeader?(name: string): unknown; } /** Observation only. Safe to run before jQuery invokes application callbacks. */ export function captureRequestCorrelation( report: HttpCompleteReport ): JourneyRequestEvent | undefined { try { const step = report.stamp?.step; if ( !step?.journey || step.journey.closed || !canRouteHttpToJourney(step.journey) || report.stamp?.ignore || report.meta?.ignore || report.aborted || !report.url || resolveJourneyEngine(step.journey).shouldIgnoreRequest(report.requestKey) ) { return; } // Other environments/backends have not verified this log-field/data-view contract. const pageUrl = globalThis.location?.href; const base = report.baseURL ? new URL(report.baseURL, pageUrl).href : pageUrl; const url = new URL(report.url, base); if (!isMonolithLogOrigin(url.href)) { return; } const response = report.response as ResponseHeaders | undefined; if (!response || typeof response !== 'object') { return; } const captured = capturedResponses.get(response); if (captured) { return step.requests?.includes(captured) ? captured : undefined; } const finalUrl = response.url || response.responseURL || response.request?.responseURL; if (finalUrl && !isMonolithLogOrigin(finalUrl)) { return; } const rayId = typeof response.getResponseHeader === 'function' ? response.getResponseHeader('cf-ray') : typeof response.headers?.get === 'function' ? response.headers.get('cf-ray') : response.headers?.['cf-ray']; if (typeof rayId !== 'string') { return; } const request = sanitizeRequestCorrelation({ rayId, startedAtEpoch: Date.now() - report.durationMs, durationMs: report.durationMs, status: report.status as number, }); if (!request) { return; } capturedResponses.set(response, request); step.requests ??= []; const requests = step.requests; if (requests.length >= MAX_CORRELATED_REQUESTS) { step.requestsTruncated = true; // Keep errors when later successes arrive. A new error replaces a success first. const replace = requests.findIndex(item => item.status < 400); if (replace < 0 && request.status < 400) { return request; } requests.splice(replace < 0 ? 0 : replace, 1); } requests.push(request); return request; } catch { // Missing/blocked headers and unusual transport objects must not affect requests. } }