UNPKG

@multiplayer-app/session-recorder-browser

Version:
168 lines 6.84 kB
import { isDocument, isFormData, isNullish, isObject, isString, } from '../utils/type-utils'; import { formDataToQuery } from '../utils/request-utils'; import { configs } from './configs'; function _tryReadFetchBody({ body, url, }) { if (isNullish(body)) { return null; } if (isString(body)) { return body; } if (isDocument(body)) { return body.textContent; } if (isFormData(body)) { return formDataToQuery(body); } if (isObject(body)) { try { return JSON.stringify(body); } catch (_a) { return '[Fetch] Failed to stringify request object'; } } return `[Fetch] Cannot read body of type ${toString.call(body)}`; } async function _tryReadResponseBody(response) { var _a, _b; try { // Clone the response to avoid consuming the original stream const clonedResponse = response.clone(); // Try different methods to read the body if ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('application/json')) { const json = await clonedResponse.json(); return JSON.stringify(json); } else if ((_b = response.headers.get('content-type')) === null || _b === void 0 ? void 0 : _b.includes('text/')) { return await clonedResponse.text(); } else { // For other content types, try text first, fallback to arrayBuffer try { return await clonedResponse.text(); } catch (_c) { try { const arrayBuffer = await clonedResponse.arrayBuffer(); return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`; } catch (_d) { return '[Fetch] Unable to read response body'; } } } } catch (error) { return `[Fetch] Error reading response body: ${error instanceof Error ? error.message : 'Unknown error'}`; } } function _headersToObject(headers) { const result = {}; headers.forEach((value, key) => { result[key] = value; }); return result; } // Convert HeadersInit to a plain object without needing to construct a Request function _headersInitToObject(headersInit) { if (!headersInit) return {}; if (headersInit instanceof Headers) { return _headersToObject(headersInit); } const result = {}; if (Array.isArray(headersInit)) { for (const [key, value] of headersInit) { result[String(key).toLowerCase()] = String(value); } return result; } // Record<string, string> for (const [key, value] of Object.entries(headersInit)) { result[String(key).toLowerCase()] = String(value); } return result; } if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') { // Idempotency guard: avoid double-patching // @ts-ignore if (window.fetch.__mp_session_recorder_patched__) { // Already patched; do nothing } else { // @ts-ignore window.fetch.__mp_session_recorder_patched__ = true; // Store original fetch const originalFetch = window.fetch; // Override fetch window.fetch = async function (input, // eslint-disable-next-line init) { const networkRequest = {}; // Capture request data const inputIsRequest = typeof Request !== 'undefined' && input instanceof Request; if (configs.recordRequestHeaders) { if (inputIsRequest) { networkRequest.requestHeaders = _headersToObject(input.headers); } else { networkRequest.requestHeaders = _headersInitToObject(init === null || init === void 0 ? void 0 : init.headers); } } if (configs.shouldRecordBody) { const urlStr = inputIsRequest ? input.url : (typeof input === 'string' || input instanceof URL ? String(input) : ''); // Only attempt to read the body from init (safe); avoid constructing/cloning Requests // If the caller passed a Request as input, we do not attempt to read its body here // eslint-disable-next-line const candidateBody = init === null || init === void 0 ? void 0 : init.body; if (!isNullish(candidateBody)) { const requestBody = _tryReadFetchBody({ body: candidateBody, url: urlStr, }); if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length) && new Blob([requestBody]).size <= configs.maxCapturingHttpPayloadSize) { networkRequest.requestBody = requestBody; } } } try { // Make the actual fetch request const response = await originalFetch(input, init); // Capture response data if (configs.recordResponseHeaders) { networkRequest.responseHeaders = _headersToObject(response.headers); } if (configs.shouldRecordBody) { const responseBody = await _tryReadResponseBody(response); if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length) && new Blob([responseBody]).size <= configs.maxCapturingHttpPayloadSize) { networkRequest.responseBody = responseBody; } } // Attach network request data to the response for later access // @ts-ignore response.networkRequest = networkRequest; return response; } catch (error) { // Even if the fetch fails, we can still capture the request data // Attach captured request data to the thrown error for downstream handling // @ts-ignore if (error && typeof error === 'object') { // @ts-ignore error.networkRequest = networkRequest; } throw error; } }; // Preserve the original fetch function's properties Object.setPrototypeOf(window.fetch, originalFetch); Object.defineProperty(window.fetch, 'name', { value: 'fetch' }); Object.defineProperty(window.fetch, 'length', { value: originalFetch.length }); } } //# sourceMappingURL=fetch.js.map