envelope.js 6.5 KB

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