/ shared / logger / node_modules / @sentry / utils / esm / envelope.js
envelope.js
  1  import { dsnToString } from './dsn.js';
  2  import { normalize } from './normalize.js';
  3  import { dropUndefinedKeys } from './object.js';
  4  
  5  /**
  6   * Creates an envelope.
  7   * Make sure to always explicitly provide the generic to this function
  8   * so that the envelope types resolve correctly.
  9   */
 10  function createEnvelope(headers, items = []) {
 11    return [headers, items] ;
 12  }
 13  
 14  /**
 15   * Add an item to an envelope.
 16   * Make sure to always explicitly provide the generic to this function
 17   * so that the envelope types resolve correctly.
 18   */
 19  function addItemToEnvelope(envelope, newItem) {
 20    const [headers, items] = envelope;
 21    return [headers, [...items, newItem]] ;
 22  }
 23  
 24  /**
 25   * Convenience function to loop through the items and item types of an envelope.
 26   * (This function was mostly created because working with envelope types is painful at the moment)
 27   *
 28   * If the callback returns true, the rest of the items will be skipped.
 29   */
 30  function forEachEnvelopeItem(
 31    envelope,
 32    callback,
 33  ) {
 34    const envelopeItems = envelope[1];
 35  
 36    for (const envelopeItem of envelopeItems) {
 37      const envelopeItemType = envelopeItem[0].type;
 38      const result = callback(envelopeItem, envelopeItemType);
 39  
 40      if (result) {
 41        return true;
 42      }
 43    }
 44  
 45    return false;
 46  }
 47  
 48  /**
 49   * Returns true if the envelope contains any of the given envelope item types
 50   */
 51  function envelopeContainsItemType(envelope, types) {
 52    return forEachEnvelopeItem(envelope, (_, type) => types.includes(type));
 53  }
 54  
 55  /**
 56   * Encode a string to UTF8.
 57   */
 58  function encodeUTF8(input, textEncoder) {
 59    const utf8 = textEncoder || new TextEncoder();
 60    return utf8.encode(input);
 61  }
 62  
 63  /**
 64   * Serializes an envelope.
 65   */
 66  function serializeEnvelope(envelope, textEncoder) {
 67    const [envHeaders, items] = envelope;
 68  
 69    // Initially we construct our envelope as a string and only convert to binary chunks if we encounter binary data
 70    let parts = JSON.stringify(envHeaders);
 71  
 72    function append(next) {
 73      if (typeof parts === 'string') {
 74        parts = typeof next === 'string' ? parts + next : [encodeUTF8(parts, textEncoder), next];
 75      } else {
 76        parts.push(typeof next === 'string' ? encodeUTF8(next, textEncoder) : next);
 77      }
 78    }
 79  
 80    for (const item of items) {
 81      const [itemHeaders, payload] = item;
 82  
 83      append(`\n${JSON.stringify(itemHeaders)}\n`);
 84  
 85      if (typeof payload === 'string' || payload instanceof Uint8Array) {
 86        append(payload);
 87      } else {
 88        let stringifiedPayload;
 89        try {
 90          stringifiedPayload = JSON.stringify(payload);
 91        } catch (e) {
 92          // In case, despite all our efforts to keep `payload` circular-dependency-free, `JSON.strinify()` still
 93          // fails, we try again after normalizing it again with infinite normalization depth. This of course has a
 94          // performance impact but in this case a performance hit is better than throwing.
 95          stringifiedPayload = JSON.stringify(normalize(payload));
 96        }
 97        append(stringifiedPayload);
 98      }
 99    }
100  
101    return typeof parts === 'string' ? parts : concatBuffers(parts);
102  }
103  
104  function concatBuffers(buffers) {
105    const totalLength = buffers.reduce((acc, buf) => acc + buf.length, 0);
106  
107    const merged = new Uint8Array(totalLength);
108    let offset = 0;
109    for (const buffer of buffers) {
110      merged.set(buffer, offset);
111      offset += buffer.length;
112    }
113  
114    return merged;
115  }
116  
117  /**
118   * Parses an envelope
119   */
120  function parseEnvelope(
121    env,
122    textEncoder,
123    textDecoder,
124  ) {
125    let buffer = typeof env === 'string' ? textEncoder.encode(env) : env;
126  
127    function readBinary(length) {
128      const bin = buffer.subarray(0, length);
129      // Replace the buffer with the remaining data excluding trailing newline
130      buffer = buffer.subarray(length + 1);
131      return bin;
132    }
133  
134    function readJson() {
135      let i = buffer.indexOf(0xa);
136      // If we couldn't find a newline, we must have found the end of the buffer
137      if (i < 0) {
138        i = buffer.length;
139      }
140  
141      return JSON.parse(textDecoder.decode(readBinary(i))) ;
142    }
143  
144    const envelopeHeader = readJson();
145    // eslint-disable-next-line @typescript-eslint/no-explicit-any
146    const items = [];
147  
148    while (buffer.length) {
149      const itemHeader = readJson();
150      const binaryLength = typeof itemHeader.length === 'number' ? itemHeader.length : undefined;
151  
152      items.push([itemHeader, binaryLength ? readBinary(binaryLength) : readJson()]);
153    }
154  
155    return [envelopeHeader, items];
156  }
157  
158  /**
159   * Creates attachment envelope items
160   */
161  function createAttachmentEnvelopeItem(
162    attachment,
163    textEncoder,
164  ) {
165    const buffer = typeof attachment.data === 'string' ? encodeUTF8(attachment.data, textEncoder) : attachment.data;
166  
167    return [
168      dropUndefinedKeys({
169        type: 'attachment',
170        length: buffer.length,
171        filename: attachment.filename,
172        content_type: attachment.contentType,
173        attachment_type: attachment.attachmentType,
174      }),
175      buffer,
176    ];
177  }
178  
179  const ITEM_TYPE_TO_DATA_CATEGORY_MAP = {
180    session: 'session',
181    sessions: 'session',
182    attachment: 'attachment',
183    transaction: 'transaction',
184    event: 'error',
185    client_report: 'internal',
186    user_report: 'default',
187    profile: 'profile',
188    replay_event: 'replay',
189    replay_recording: 'replay',
190    check_in: 'monitor',
191  };
192  
193  /**
194   * Maps the type of an envelope item to a data category.
195   */
196  function envelopeItemTypeToDataCategory(type) {
197    return ITEM_TYPE_TO_DATA_CATEGORY_MAP[type];
198  }
199  
200  /** Extracts the minimal SDK info from from the metadata or an events */
201  function getSdkMetadataForEnvelopeHeader(metadataOrEvent) {
202    if (!metadataOrEvent || !metadataOrEvent.sdk) {
203      return;
204    }
205    const { name, version } = metadataOrEvent.sdk;
206    return { name, version };
207  }
208  
209  /**
210   * Creates event envelope headers, based on event, sdk info and tunnel
211   * Note: This function was extracted from the core package to make it available in Replay
212   */
213  function createEventEnvelopeHeaders(
214    event,
215    sdkInfo,
216    tunnel,
217    dsn,
218  ) {
219    const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata.dynamicSamplingContext;
220    return {
221      event_id: event.event_id ,
222      sent_at: new Date().toISOString(),
223      ...(sdkInfo && { sdk: sdkInfo }),
224      ...(!!tunnel && { dsn: dsnToString(dsn) }),
225      ...(dynamicSamplingContext && {
226        trace: dropUndefinedKeys({ ...dynamicSamplingContext }),
227      }),
228    };
229  }
230  
231  export { addItemToEnvelope, createAttachmentEnvelopeItem, createEnvelope, createEventEnvelopeHeaders, envelopeContainsItemType, envelopeItemTypeToDataCategory, forEachEnvelopeItem, getSdkMetadataForEnvelopeHeader, parseEnvelope, serializeEnvelope };
232  //# sourceMappingURL=envelope.js.map