import { dsnToString } from './dsn.js'; import { normalize } from './normalize.js'; import { dropUndefinedKeys } from './object.js'; /** * Creates an envelope. * Make sure to always explicitly provide the generic to this function * so that the envelope types resolve correctly. */ function createEnvelope(headers, items = []) { return [headers, items] ; } /** * Add an item to an envelope. * Make sure to always explicitly provide the generic to this function * so that the envelope types resolve correctly. */ function addItemToEnvelope(envelope, newItem) { const [headers, items] = envelope; return [headers, [...items, newItem]] ; } /** * Convenience function to loop through the items and item types of an envelope. * (This function was mostly created because working with envelope types is painful at the moment) * * If the callback returns true, the rest of the items will be skipped. */ function forEachEnvelopeItem( envelope, callback, ) { const envelopeItems = envelope[1]; for (const envelopeItem of envelopeItems) { const envelopeItemType = envelopeItem[0].type; const result = callback(envelopeItem, envelopeItemType); if (result) { return true; } } return false; } /** * Returns true if the envelope contains any of the given envelope item types */ function envelopeContainsItemType(envelope, types) { return forEachEnvelopeItem(envelope, (_, type) => types.includes(type)); } /** * Encode a string to UTF8. */ function encodeUTF8(input, textEncoder) { const utf8 = textEncoder || new TextEncoder(); return utf8.encode(input); } /** * Serializes an envelope. */ function serializeEnvelope(envelope, textEncoder) { const [envHeaders, items] = envelope; // Initially we construct our envelope as a string and only convert to binary chunks if we encounter binary data let parts = JSON.stringify(envHeaders); function append(next) { if (typeof parts === 'string') { parts = typeof next === 'string' ? parts + next : [encodeUTF8(parts, textEncoder), next]; } else { parts.push(typeof next === 'string' ? encodeUTF8(next, textEncoder) : next); } } for (const item of items) { const [itemHeaders, payload] = item; append(`\n${JSON.stringify(itemHeaders)}\n`); if (typeof payload === 'string' || payload instanceof Uint8Array) { append(payload); } else { let stringifiedPayload; try { stringifiedPayload = JSON.stringify(payload); } catch (e) { // In case, despite all our efforts to keep `payload` circular-dependency-free, `JSON.strinify()` still // fails, we try again after normalizing it again with infinite normalization depth. This of course has a // performance impact but in this case a performance hit is better than throwing. stringifiedPayload = JSON.stringify(normalize(payload)); } append(stringifiedPayload); } } return typeof parts === 'string' ? parts : concatBuffers(parts); } function concatBuffers(buffers) { const totalLength = buffers.reduce((acc, buf) => acc + buf.length, 0); const merged = new Uint8Array(totalLength); let offset = 0; for (const buffer of buffers) { merged.set(buffer, offset); offset += buffer.length; } return merged; } /** * Parses an envelope */ function parseEnvelope( env, textEncoder, textDecoder, ) { let buffer = typeof env === 'string' ? textEncoder.encode(env) : env; function readBinary(length) { const bin = buffer.subarray(0, length); // Replace the buffer with the remaining data excluding trailing newline buffer = buffer.subarray(length + 1); return bin; } function readJson() { let i = buffer.indexOf(0xa); // If we couldn't find a newline, we must have found the end of the buffer if (i < 0) { i = buffer.length; } return JSON.parse(textDecoder.decode(readBinary(i))) ; } const envelopeHeader = readJson(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const items = []; while (buffer.length) { const itemHeader = readJson(); const binaryLength = typeof itemHeader.length === 'number' ? itemHeader.length : undefined; items.push([itemHeader, binaryLength ? readBinary(binaryLength) : readJson()]); } return [envelopeHeader, items]; } /** * Creates attachment envelope items */ function createAttachmentEnvelopeItem( attachment, textEncoder, ) { const buffer = typeof attachment.data === 'string' ? encodeUTF8(attachment.data, textEncoder) : attachment.data; return [ dropUndefinedKeys({ type: 'attachment', length: buffer.length, filename: attachment.filename, content_type: attachment.contentType, attachment_type: attachment.attachmentType, }), buffer, ]; } const ITEM_TYPE_TO_DATA_CATEGORY_MAP = { session: 'session', sessions: 'session', attachment: 'attachment', transaction: 'transaction', event: 'error', client_report: 'internal', user_report: 'default', profile: 'profile', replay_event: 'replay', replay_recording: 'replay', check_in: 'monitor', feedback: 'feedback', span: 'span', statsd: 'metric_bucket', }; /** * Maps the type of an envelope item to a data category. */ function envelopeItemTypeToDataCategory(type) { return ITEM_TYPE_TO_DATA_CATEGORY_MAP[type]; } /** Extracts the minimal SDK info from the metadata or an events */ function getSdkMetadataForEnvelopeHeader(metadataOrEvent) { if (!metadataOrEvent || !metadataOrEvent.sdk) { return; } const { name, version } = metadataOrEvent.sdk; return { name, version }; } /** * Creates event envelope headers, based on event, sdk info and tunnel * Note: This function was extracted from the core package to make it available in Replay */ function createEventEnvelopeHeaders( event, sdkInfo, tunnel, dsn, ) { const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata.dynamicSamplingContext; return { event_id: event.event_id , sent_at: new Date().toISOString(), ...(sdkInfo && { sdk: sdkInfo }), ...(!!tunnel && dsn && { dsn: dsnToString(dsn) }), ...(dynamicSamplingContext && { trace: dropUndefinedKeys({ ...dynamicSamplingContext }), }), }; } export { addItemToEnvelope, createAttachmentEnvelopeItem, createEnvelope, createEventEnvelopeHeaders, envelopeContainsItemType, envelopeItemTypeToDataCategory, forEachEnvelopeItem, getSdkMetadataForEnvelopeHeader, parseEnvelope, serializeEnvelope }; //# sourceMappingURL=envelope.js.map