/ shared / logger / node_modules / @sentry / utils / esm / baggage.js
baggage.js
  1  import { isString } from './is.js';
  2  import { logger } from './logger.js';
  3  
  4  const BAGGAGE_HEADER_NAME = 'baggage';
  5  
  6  const SENTRY_BAGGAGE_KEY_PREFIX = 'sentry-';
  7  
  8  const SENTRY_BAGGAGE_KEY_PREFIX_REGEX = /^sentry-/;
  9  
 10  /**
 11   * Max length of a serialized baggage string
 12   *
 13   * https://www.w3.org/TR/baggage/#limits
 14   */
 15  const MAX_BAGGAGE_STRING_LENGTH = 8192;
 16  
 17  /**
 18   * Takes a baggage header and turns it into Dynamic Sampling Context, by extracting all the "sentry-" prefixed values
 19   * from it.
 20   *
 21   * @param baggageHeader A very bread definition of a baggage header as it might appear in various frameworks.
 22   * @returns The Dynamic Sampling Context that was found on `baggageHeader`, if there was any, `undefined` otherwise.
 23   */
 24  function baggageHeaderToDynamicSamplingContext(
 25    // Very liberal definition of what any incoming header might look like
 26    baggageHeader,
 27  ) {
 28    if (!isString(baggageHeader) && !Array.isArray(baggageHeader)) {
 29      return undefined;
 30    }
 31  
 32    // Intermediary object to store baggage key value pairs of incoming baggage headers on.
 33    // It is later used to read Sentry-DSC-values from.
 34    let baggageObject = {};
 35  
 36    if (Array.isArray(baggageHeader)) {
 37      // Combine all baggage headers into one object containing the baggage values so we can later read the Sentry-DSC-values from it
 38      baggageObject = baggageHeader.reduce((acc, curr) => {
 39        const currBaggageObject = baggageHeaderToObject(curr);
 40        return {
 41          ...acc,
 42          ...currBaggageObject,
 43        };
 44      }, {});
 45    } else {
 46      // Return undefined if baggage header is an empty string (technically an empty baggage header is not spec conform but
 47      // this is how we choose to handle it)
 48      if (!baggageHeader) {
 49        return undefined;
 50      }
 51  
 52      baggageObject = baggageHeaderToObject(baggageHeader);
 53    }
 54  
 55    // Read all "sentry-" prefixed values out of the baggage object and put it onto a dynamic sampling context object.
 56    const dynamicSamplingContext = Object.entries(baggageObject).reduce((acc, [key, value]) => {
 57      if (key.match(SENTRY_BAGGAGE_KEY_PREFIX_REGEX)) {
 58        const nonPrefixedKey = key.slice(SENTRY_BAGGAGE_KEY_PREFIX.length);
 59        acc[nonPrefixedKey] = value;
 60      }
 61      return acc;
 62    }, {});
 63  
 64    // Only return a dynamic sampling context object if there are keys in it.
 65    // A keyless object means there were no sentry values on the header, which means that there is no DSC.
 66    if (Object.keys(dynamicSamplingContext).length > 0) {
 67      return dynamicSamplingContext ;
 68    } else {
 69      return undefined;
 70    }
 71  }
 72  
 73  /**
 74   * Turns a Dynamic Sampling Object into a baggage header by prefixing all the keys on the object with "sentry-".
 75   *
 76   * @param dynamicSamplingContext The Dynamic Sampling Context to turn into a header. For convenience and compatibility
 77   * with the `getDynamicSamplingContext` method on the Transaction class ,this argument can also be `undefined`. If it is
 78   * `undefined` the function will return `undefined`.
 79   * @returns a baggage header, created from `dynamicSamplingContext`, or `undefined` either if `dynamicSamplingContext`
 80   * was `undefined`, or if `dynamicSamplingContext` didn't contain any values.
 81   */
 82  function dynamicSamplingContextToSentryBaggageHeader(
 83    // this also takes undefined for convenience and bundle size in other places
 84    dynamicSamplingContext,
 85  ) {
 86    // Prefix all DSC keys with "sentry-" and put them into a new object
 87    const sentryPrefixedDSC = Object.entries(dynamicSamplingContext).reduce(
 88      (acc, [dscKey, dscValue]) => {
 89        if (dscValue) {
 90          acc[`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`] = dscValue;
 91        }
 92        return acc;
 93      },
 94      {},
 95    );
 96  
 97    return objectToBaggageHeader(sentryPrefixedDSC);
 98  }
 99  
100  /**
101   * Will parse a baggage header, which is a simple key-value map, into a flat object.
102   *
103   * @param baggageHeader The baggage header to parse.
104   * @returns a flat object containing all the key-value pairs from `baggageHeader`.
105   */
106  function baggageHeaderToObject(baggageHeader) {
107    return baggageHeader
108      .split(',')
109      .map(baggageEntry => baggageEntry.split('=').map(keyOrValue => decodeURIComponent(keyOrValue.trim())))
110      .reduce((acc, [key, value]) => {
111        acc[key] = value;
112        return acc;
113      }, {});
114  }
115  
116  /**
117   * Turns a flat object (key-value pairs) into a baggage header, which is also just key-value pairs.
118   *
119   * @param object The object to turn into a baggage header.
120   * @returns a baggage header string, or `undefined` if the object didn't have any values, since an empty baggage header
121   * is not spec compliant.
122   */
123  function objectToBaggageHeader(object) {
124    if (Object.keys(object).length === 0) {
125      // An empty baggage header is not spec compliant: We return undefined.
126      return undefined;
127    }
128  
129    return Object.entries(object).reduce((baggageHeader, [objectKey, objectValue], currentIndex) => {
130      const baggageEntry = `${encodeURIComponent(objectKey)}=${encodeURIComponent(objectValue)}`;
131      const newBaggageHeader = currentIndex === 0 ? baggageEntry : `${baggageHeader},${baggageEntry}`;
132      if (newBaggageHeader.length > MAX_BAGGAGE_STRING_LENGTH) {
133        (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
134          logger.warn(
135            `Not adding key: ${objectKey} with val: ${objectValue} to baggage header due to exceeding baggage size limits.`,
136          );
137        return baggageHeader;
138      } else {
139        return newBaggageHeader;
140      }
141    }, '');
142  }
143  
144  export { BAGGAGE_HEADER_NAME, MAX_BAGGAGE_STRING_LENGTH, SENTRY_BAGGAGE_KEY_PREFIX, SENTRY_BAGGAGE_KEY_PREFIX_REGEX, baggageHeaderToDynamicSamplingContext, dynamicSamplingContextToSentryBaggageHeader };
145  //# sourceMappingURL=baggage.js.map