UNPKG

@multiplayer-app/session-recorder-browser

Version:
208 lines 8.45 kB
import { MULTIPLAYER_TRACE_DEBUG_PREFIX, MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX, ATTR_MULTIPLAYER_HTTP_REQUEST_BODY, ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS, ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY, ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS, } from '@multiplayer-app/session-recorder-common'; /** * Checks if the trace should be processed based on trace ID prefixes */ export function shouldProcessTrace(traceId) { return (traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX) || traceId.startsWith(MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX)); } /** * Processes request and response body based on trace type and configuration */ export function processBody(payload, config, span) { var _a, _b; const { captureBody, masking } = config; const traceId = span.spanContext().traceId; if (!captureBody) { return {}; } let { requestBody, responseBody } = payload; if (requestBody !== undefined && requestBody !== null) { requestBody = JSON.parse(JSON.stringify(requestBody)); } if (responseBody !== undefined && responseBody !== null) { responseBody = JSON.parse(JSON.stringify(responseBody)); } // Apply masking for debug traces if (traceId.startsWith(MULTIPLAYER_TRACE_DEBUG_PREFIX) || traceId.startsWith(MULTIPLAYER_TRACE_CONTINUOUS_DEBUG_PREFIX)) { if (masking.isContentMaskingEnabled) { requestBody = requestBody && ((_a = masking.maskBody) === null || _a === void 0 ? void 0 : _a.call(masking, requestBody, span)); responseBody = responseBody && ((_b = masking.maskBody) === null || _b === void 0 ? void 0 : _b.call(masking, responseBody, span)); } } // Convert to string if needed if (typeof requestBody !== 'string') { requestBody = JSON.stringify(requestBody); } if (typeof responseBody !== 'string') { responseBody = JSON.stringify(responseBody); } return { requestBody: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.length) ? requestBody : undefined, responseBody: (responseBody === null || responseBody === void 0 ? void 0 : responseBody.length) ? responseBody : undefined, }; } /** * Processes request and response headers based on configuration */ export function processHeaders(payload, config, span) { var _a, _b, _c, _d, _e; const { captureHeaders, masking } = config; if (!captureHeaders) { return {}; } let { requestHeaders = {}, responseHeaders = {} } = payload; // Handle header filtering if (!((_a = masking.headersToInclude) === null || _a === void 0 ? void 0 : _a.length) && !((_b = masking.headersToExclude) === null || _b === void 0 ? void 0 : _b.length)) { // Add null checks to prevent JSON.parse error when headers is undefined if (requestHeaders !== undefined && requestHeaders !== null) { requestHeaders = JSON.parse(JSON.stringify(requestHeaders)); } if (responseHeaders !== undefined && responseHeaders !== null) { responseHeaders = JSON.parse(JSON.stringify(responseHeaders)); } } else { if (masking.headersToInclude) { const _requestHeaders = {}; const _responseHeaders = {}; for (const headerName of masking.headersToInclude) { if (requestHeaders[headerName]) { _requestHeaders[headerName] = requestHeaders[headerName]; } if (responseHeaders[headerName]) { _responseHeaders[headerName] = responseHeaders[headerName]; } } requestHeaders = _requestHeaders; responseHeaders = _responseHeaders; } if ((_c = masking.headersToExclude) === null || _c === void 0 ? void 0 : _c.length) { for (const headerName of masking.headersToExclude) { delete requestHeaders[headerName]; delete responseHeaders[headerName]; } } } // Apply masking const maskedRequestHeaders = ((_d = masking.maskHeaders) === null || _d === void 0 ? void 0 : _d.call(masking, requestHeaders, span)) || requestHeaders; const maskedResponseHeaders = ((_e = masking.maskHeaders) === null || _e === void 0 ? void 0 : _e.call(masking, responseHeaders, span)) || responseHeaders; // Convert to string const requestHeadersStr = typeof maskedRequestHeaders === 'string' ? maskedRequestHeaders : JSON.stringify(maskedRequestHeaders); const responseHeadersStr = typeof maskedResponseHeaders === 'string' ? maskedResponseHeaders : JSON.stringify(maskedResponseHeaders); return { requestHeaders: (requestHeadersStr === null || requestHeadersStr === void 0 ? void 0 : requestHeadersStr.length) ? requestHeadersStr : undefined, responseHeaders: (responseHeadersStr === null || responseHeadersStr === void 0 ? void 0 : responseHeadersStr.length) ? responseHeadersStr : undefined, }; } /** * Processes HTTP payload (body and headers) and sets span attributes */ export function processHttpPayload(payload, config, span) { const traceId = span.spanContext().traceId; if (!shouldProcessTrace(traceId)) { return; } const { requestBody, responseBody } = processBody(payload, config, span); const { requestHeaders, responseHeaders } = processHeaders(payload, config, span); // Set span attributes if (requestBody) { span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_BODY, requestBody); } if (responseBody) { span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_BODY, responseBody); } if (requestHeaders) { span.setAttribute(ATTR_MULTIPLAYER_HTTP_REQUEST_HEADERS, requestHeaders); } if (responseHeaders) { span.setAttribute(ATTR_MULTIPLAYER_HTTP_RESPONSE_HEADERS, responseHeaders); } } /** * Converts Headers object to plain object */ export function headersToObject(headers) { const result = {}; if (!headers) { return result; } if (headers instanceof Headers) { headers.forEach((value, key) => { result[key] = value; }); } else if (Array.isArray(headers)) { // Handle array of [key, value] pairs for (const [key, value] of headers) { if (typeof key === 'string' && typeof value === 'string') { result[key] = value; } } } else if (typeof headers === 'object' && !Array.isArray(headers)) { for (const [key, value] of Object.entries(headers)) { if (typeof key === 'string' && typeof value === 'string') { result[key] = value; } } } return result; } /** * Extracts response body as string from Response object */ export async function extractResponseBody(response) { if (!response.body) { return null; } try { if (response.body instanceof ReadableStream) { // Check if response body is already consumed if (response.bodyUsed) { return null; } const responseClone = response.clone(); return responseClone.text(); } else { return JSON.stringify(response.body); } } catch (error) { // If cloning fails (body already consumed), return null // eslint-disable-next-line no-console console.warn('[MULTIPLAYER_SESSION_RECORDER] Failed to extract response body:', error); return null; } } export const getExporterEndpoint = (exporterEndpoint) => { const hasPath = exporterEndpoint && (() => { try { const url = new URL(exporterEndpoint); return url.pathname !== '/' && url.pathname !== ''; } catch (_a) { return false; } })(); if (hasPath) { return exporterEndpoint; } const trimmedExporterEndpoint = new URL(exporterEndpoint).origin; return `${trimmedExporterEndpoint}/v1/traces`; }; export const getElementInnerText = (element) => { return String(element.innerText || '').trim(); }; export const getElementTextContent = (element) => { var _a; return String(((_a = element.textContent) === null || _a === void 0 ? void 0 : _a.split('\n').filter(Boolean).join(' ')) || '').trim(); }; //# sourceMappingURL=helpers.js.map