UNPKG

@embrace-io/web-sdk

Version:
1 lines 8.54 kB
{"version":3,"file":"FetchTransport.cjs","names":[],"sources":["../../../src/transport/FetchTransport/FetchTransport.ts"],"sourcesContent":["import { diag } from '@opentelemetry/api';\nimport type {\n ExportResponse,\n IExporterTransport,\n} from '@opentelemetry/otlp-exporter-base';\nimport type { FetchRequestParameters } from './types.ts';\n\n// The fetch spec limits inflight keepalive request bodies to 64KiB total per\n// page context. We use a conservative budget to leave room for third-party\n// scripts that may also use keepalive or sendBeacon.\n// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch\nconst KEEPALIVE_BYTE_BUDGET = 49152; // 48KiB\n\n// Chrome enforces a 9-request concurrent keepalive limit per renderer process.\nconst MAX_KEEPALIVE_REQUESTS = 9;\n\nlet inflightKeepaliveBytes = 0;\nlet inflightKeepaliveCount = 0;\n\n/** @internal for testing only */\nexport function _resetKeepaliveTracking(): void {\n inflightKeepaliveBytes = 0;\n inflightKeepaliveCount = 0;\n}\n\nexport class FetchTransport implements IExporterTransport {\n public constructor(private readonly _config: FetchRequestParameters) {}\n\n // Embrace endpoints require request bodies to be compressed with gzip\n private static async _compressRequest(\n data: Uint8Array<ArrayBuffer>,\n ): Promise<Uint8Array<ArrayBuffer>> {\n const stream = new CompressionStream('gzip');\n const writer = stream.writable.getWriter();\n\n void writer.write(data);\n void writer.close();\n\n const compressedChunks: Uint8Array[] = [];\n const reader = stream.readable.getReader();\n\n let done = false;\n while (!done) {\n const result = await reader.read();\n\n if (result.value) {\n compressedChunks.push(result.value);\n }\n\n done = result.done;\n }\n\n const compressedData = new Uint8Array(\n compressedChunks.reduce((acc, chunk) => acc + chunk.length, 0),\n );\n\n let offset = 0;\n\n for (const chunk of compressedChunks) {\n compressedData.set(chunk, offset);\n offset += chunk.length;\n }\n\n return compressedData;\n }\n\n public send(\n data: Uint8Array<ArrayBuffer>,\n timeoutMillis: number,\n ): Promise<ExportResponse> {\n return this._asyncSend(data, timeoutMillis);\n }\n\n public shutdown(): void {\n // Intentionally left empty, nothing to do.\n }\n\n public async _asyncSend(\n data: Uint8Array<ArrayBuffer>,\n timeoutMillis: number,\n ): Promise<ExportResponse> {\n let request = data;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this._config.headers,\n };\n\n // Use AbortSignal.timeout if available, otherwise fallback to AbortController\n // https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static\n let signal: AbortSignal;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n if ('timeout' in AbortSignal) {\n // ok to ignore because there is a guard\n // eslint-disable-next-line baseline-js/use-baseline\n signal = AbortSignal.timeout(timeoutMillis);\n } else {\n const controller = new AbortController();\n signal = controller.signal;\n timeoutId = setTimeout(() => {\n controller.abort(new DOMException('TimeoutError', 'TimeoutError'));\n }, timeoutMillis);\n }\n\n let keepalive = false;\n\n try {\n if (this._config.compression === 'gzip') {\n try {\n request = await FetchTransport._compressRequest(data);\n } catch (error) {\n const compressError =\n error instanceof Error ? error : new Error(String(error));\n diag.warn(\n `Fetch transport gzip compression failed: ${compressError.message}`,\n );\n return { status: 'failure', error: compressError };\n }\n headers['Content-Encoding'] = 'gzip';\n headers['Content-Length'] = request.length.toString();\n }\n\n const wouldExceedBytes =\n inflightKeepaliveBytes + request.byteLength > KEEPALIVE_BYTE_BUDGET;\n const wouldExceedCount = inflightKeepaliveCount >= MAX_KEEPALIVE_REQUESTS;\n keepalive = !wouldExceedBytes && !wouldExceedCount;\n\n if (keepalive) {\n inflightKeepaliveBytes += request.byteLength;\n inflightKeepaliveCount++;\n } else {\n const reason = wouldExceedBytes\n ? `inflight bytes (${inflightKeepaliveBytes} + ${request.byteLength}) would exceed ${KEEPALIVE_BYTE_BUDGET}B budget`\n : `concurrent count (${inflightKeepaliveCount}) at limit of ${MAX_KEEPALIVE_REQUESTS}`;\n diag.debug(`Sending without keepalive: ${reason}`);\n }\n\n const response = await fetch(this._config.url, {\n method: 'POST',\n keepalive,\n headers,\n body: request,\n signal,\n });\n\n if (response.ok) {\n return { status: 'success' };\n }\n\n const error = new Error(`${response.status} Fetch request failed`);\n const message = `Fetch transport received HTTP ${response.status} (keepalive=${keepalive})`;\n if (response.status >= 500) {\n diag.debug(message);\n return { status: 'retryable', error };\n }\n diag.warn(message);\n return { status: 'failure', error };\n } catch (error) {\n const fetchError =\n error instanceof Error ? error : new Error(String(error));\n const isRetryable =\n fetchError instanceof TypeError ||\n (fetchError instanceof DOMException &&\n fetchError.name === 'TimeoutError');\n const message = `Fetch transport failed (keepalive=${keepalive}): ${fetchError.message}`;\n if (isRetryable) {\n diag.debug(message);\n } else {\n diag.warn(message);\n }\n return {\n status: isRetryable ? 'retryable' : 'failure',\n error: fetchError,\n };\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n if (keepalive) {\n inflightKeepaliveBytes -= request.byteLength;\n inflightKeepaliveCount--;\n }\n }\n }\n}\n"],"mappings":"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"}