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