@newrelic/browser-agent 1.316.0 → 1.317.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/common/config/configurable.js +1 -1
  3. package/dist/cjs/common/config/init-types.js +1 -0
  4. package/dist/cjs/common/config/init.js +6 -4
  5. package/dist/cjs/common/constants/env.cdn.js +2 -2
  6. package/dist/cjs/common/constants/env.npm.js +2 -2
  7. package/dist/cjs/common/constants/events.js +26 -0
  8. package/dist/cjs/common/harvest/harvester.js +9 -217
  9. package/dist/cjs/common/harvest/send.js +232 -0
  10. package/dist/cjs/common/harvest/types.js +9 -5
  11. package/dist/cjs/common/payloads/payloads.js +127 -0
  12. package/dist/cjs/common/serialize/bel-serializer.js +4 -3
  13. package/dist/cjs/common/session/session-entity.js +8 -8
  14. package/dist/cjs/common/util/data-size.js +2 -2
  15. package/dist/cjs/common/util/obfuscate.js +147 -3
  16. package/dist/cjs/common/v2/mfe-vitals.js +286 -0
  17. package/dist/cjs/common/wrap/wrap-websocket.js +2 -1
  18. package/dist/cjs/common/wrap/wrap-xhr.js +1 -1
  19. package/dist/cjs/features/ajax/aggregate/gql.js +40 -1
  20. package/dist/cjs/features/ajax/aggregate/index.js +51 -13
  21. package/dist/cjs/features/ajax/constants.js +6 -1
  22. package/dist/cjs/features/ajax/instrument/index.js +105 -17
  23. package/dist/cjs/features/generic_events/aggregate/index.js +15 -10
  24. package/dist/cjs/features/generic_events/constants.js +3 -2
  25. package/dist/cjs/features/jserrors/aggregate/format-stack-trace.js +2 -2
  26. package/dist/cjs/features/jserrors/aggregate/index.js +12 -3
  27. package/dist/cjs/features/logging/aggregate/index.js +12 -8
  28. package/dist/cjs/features/metrics/aggregate/index.js +5 -1
  29. package/dist/cjs/features/page_view_event/aggregate/index.js +72 -33
  30. package/dist/cjs/features/page_view_timing/aggregate/index.js +6 -1
  31. package/dist/cjs/features/session_replay/aggregate/index.js +39 -29
  32. package/dist/cjs/features/session_trace/aggregate/index.js +35 -29
  33. package/dist/cjs/features/session_trace/constants.js +17 -3
  34. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +36 -5
  35. package/dist/cjs/features/soft_navigations/aggregate/index.js +7 -1
  36. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  37. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +6 -4
  38. package/dist/cjs/features/utils/aggregate-base.js +4 -3
  39. package/dist/cjs/loaders/api/register.js +26 -4
  40. package/dist/esm/common/config/configurable.js +1 -1
  41. package/dist/esm/common/config/init-types.js +1 -0
  42. package/dist/esm/common/config/init.js +3 -1
  43. package/dist/esm/common/constants/env.cdn.js +2 -2
  44. package/dist/esm/common/constants/env.npm.js +2 -2
  45. package/dist/esm/common/constants/events.js +20 -0
  46. package/dist/esm/common/harvest/harvester.js +9 -217
  47. package/dist/esm/common/harvest/send.js +226 -0
  48. package/dist/esm/common/harvest/types.js +9 -5
  49. package/dist/esm/common/payloads/payloads.js +118 -0
  50. package/dist/esm/common/serialize/bel-serializer.js +4 -3
  51. package/dist/esm/common/session/session-entity.js +9 -9
  52. package/dist/esm/common/util/data-size.js +2 -2
  53. package/dist/esm/common/util/obfuscate.js +147 -3
  54. package/dist/esm/common/v2/mfe-vitals.js +281 -0
  55. package/dist/esm/common/wrap/wrap-websocket.js +2 -1
  56. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  57. package/dist/esm/features/ajax/aggregate/gql.js +39 -1
  58. package/dist/esm/features/ajax/aggregate/index.js +52 -14
  59. package/dist/esm/features/ajax/constants.js +5 -0
  60. package/dist/esm/features/ajax/instrument/index.js +106 -18
  61. package/dist/esm/features/generic_events/aggregate/index.js +15 -10
  62. package/dist/esm/features/generic_events/constants.js +3 -2
  63. package/dist/esm/features/jserrors/aggregate/format-stack-trace.js +2 -2
  64. package/dist/esm/features/jserrors/aggregate/index.js +12 -3
  65. package/dist/esm/features/logging/aggregate/index.js +12 -8
  66. package/dist/esm/features/metrics/aggregate/index.js +5 -1
  67. package/dist/esm/features/page_view_event/aggregate/index.js +71 -32
  68. package/dist/esm/features/page_view_timing/aggregate/index.js +6 -1
  69. package/dist/esm/features/session_replay/aggregate/index.js +39 -29
  70. package/dist/esm/features/session_trace/aggregate/index.js +36 -30
  71. package/dist/esm/features/session_trace/constants.js +16 -2
  72. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +36 -5
  73. package/dist/esm/features/soft_navigations/aggregate/index.js +7 -1
  74. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  75. package/dist/esm/features/soft_navigations/aggregate/interaction.js +6 -4
  76. package/dist/esm/features/utils/aggregate-base.js +4 -3
  77. package/dist/esm/loaders/api/register.js +26 -4
  78. package/dist/tsconfig.tsbuildinfo +1 -1
  79. package/dist/types/common/config/init-types.d.ts +4 -0
  80. package/dist/types/common/config/init-types.d.ts.map +1 -1
  81. package/dist/types/common/config/init.d.ts.map +1 -1
  82. package/dist/types/common/constants/events.d.ts +16 -0
  83. package/dist/types/common/constants/events.d.ts.map +1 -0
  84. package/dist/types/common/harvest/harvester.d.ts +2 -16
  85. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  86. package/dist/types/common/harvest/send.d.ts +9 -0
  87. package/dist/types/common/harvest/send.d.ts.map +1 -0
  88. package/dist/types/common/harvest/types.d.ts +39 -10
  89. package/dist/types/common/harvest/types.d.ts.map +1 -1
  90. package/dist/types/common/payloads/payloads.d.ts +42 -0
  91. package/dist/types/common/payloads/payloads.d.ts.map +1 -0
  92. package/dist/types/common/serialize/bel-serializer.d.ts +1 -1
  93. package/dist/types/common/serialize/bel-serializer.d.ts.map +1 -1
  94. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  95. package/dist/types/common/util/obfuscate.d.ts +23 -2
  96. package/dist/types/common/util/obfuscate.d.ts.map +1 -1
  97. package/dist/types/common/v2/mfe-vitals.d.ts +14 -0
  98. package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -0
  99. package/dist/types/common/wrap/wrap-websocket.d.ts.map +1 -1
  100. package/dist/types/features/ajax/aggregate/gql.d.ts +8 -0
  101. package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -1
  102. package/dist/types/features/ajax/aggregate/index.d.ts +2 -0
  103. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  104. package/dist/types/features/ajax/constants.d.ts +5 -0
  105. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  106. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  107. package/dist/types/features/generic_events/aggregate/index.d.ts +3 -1
  108. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  109. package/dist/types/features/generic_events/constants.d.ts.map +1 -1
  110. package/dist/types/features/jserrors/aggregate/index.d.ts +8 -1
  111. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  112. package/dist/types/features/logging/aggregate/index.d.ts +3 -1
  113. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  114. package/dist/types/features/metrics/aggregate/index.d.ts +2 -0
  115. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  116. package/dist/types/features/page_view_event/aggregate/index.d.ts +3 -0
  117. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  118. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -0
  119. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  120. package/dist/types/features/session_replay/aggregate/index.d.ts +4 -2
  121. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  122. package/dist/types/features/session_trace/aggregate/index.d.ts +3 -1
  123. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  124. package/dist/types/features/session_trace/constants.d.ts +17 -0
  125. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +6 -1
  126. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  127. package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -0
  128. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  129. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -1
  130. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
  131. package/dist/types/features/utils/aggregate-base.d.ts +0 -1
  132. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  133. package/dist/types/loaders/api/register.d.ts.map +1 -1
  134. package/package.json +3 -3
  135. package/src/common/config/configurable.js +1 -1
  136. package/src/common/config/init-types.js +1 -0
  137. package/src/common/config/init.js +2 -1
  138. package/src/common/constants/events.js +20 -0
  139. package/src/common/harvest/harvester.js +9 -201
  140. package/src/common/harvest/send.js +209 -0
  141. package/src/common/harvest/types.js +9 -5
  142. package/src/common/payloads/payloads.js +135 -0
  143. package/src/common/serialize/bel-serializer.js +4 -3
  144. package/src/common/session/session-entity.js +8 -9
  145. package/src/common/util/data-size.js +2 -2
  146. package/src/common/util/obfuscate.js +154 -3
  147. package/src/common/v2/mfe-vitals.js +260 -0
  148. package/src/common/wrap/wrap-websocket.js +2 -1
  149. package/src/common/wrap/wrap-xhr.js +1 -1
  150. package/src/features/ajax/aggregate/gql.js +42 -1
  151. package/src/features/ajax/aggregate/index.js +43 -13
  152. package/src/features/ajax/constants.js +5 -1
  153. package/src/features/ajax/instrument/index.js +114 -18
  154. package/src/features/generic_events/aggregate/index.js +14 -9
  155. package/src/features/generic_events/constants.js +3 -2
  156. package/src/features/jserrors/aggregate/format-stack-trace.js +3 -3
  157. package/src/features/jserrors/aggregate/index.js +12 -3
  158. package/src/features/logging/aggregate/index.js +12 -11
  159. package/src/features/metrics/aggregate/index.js +5 -1
  160. package/src/features/page_view_event/aggregate/index.js +58 -29
  161. package/src/features/page_view_timing/aggregate/index.js +6 -1
  162. package/src/features/session_replay/aggregate/index.js +37 -28
  163. package/src/features/session_trace/aggregate/index.js +37 -30
  164. package/src/features/session_trace/constants.js +7 -1
  165. package/src/features/soft_navigations/aggregate/ajax-node.js +26 -5
  166. package/src/features/soft_navigations/aggregate/index.js +7 -1
  167. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  168. package/src/features/soft_navigations/aggregate/interaction.js +6 -4
  169. package/src/features/utils/aggregate-base.js +3 -3
  170. package/src/loaders/api/register.js +24 -4
@@ -14,7 +14,7 @@ import { parseUrl } from '../../../common/url/parse-url';
14
14
  import { DT } from './distributed-tracing';
15
15
  import { responseSizeFromXhr } from './response-size';
16
16
  import { InstrumentBase } from '../../utils/instrument-base';
17
- import { FEATURE_NAME } from '../constants';
17
+ import { CAPTURE_PAYLOAD_SETTINGS, FEATURE_NAME } from '../constants';
18
18
  import { FEATURE_NAMES } from '../../../loaders/features/features';
19
19
  import { SUPPORTABILITY_METRIC } from '../../metrics/constants';
20
20
  import { now } from '../../../common/timing/now';
@@ -25,6 +25,7 @@ var handlersLen = handlers.length;
25
25
  var origRequest = gosNREUMOriginals().o.REQ;
26
26
  var origXHR = gosNREUMOriginals().o.XHR;
27
27
  const NR_CAT_HEADER = 'X-NewRelic-App-Data';
28
+ const INTERNAL_ERROR = 'internal-error';
28
29
  export class Instrument extends InstrumentBase {
29
30
  static featureName = FEATURE_NAME;
30
31
  constructor(agentRef) {
@@ -70,10 +71,12 @@ export class Instrument extends InstrumentBase {
70
71
  }
71
72
  }
72
73
  function subscribeToEvents(agentRef, ee, handler, dt) {
74
+ const shouldInterceptPayloads = [CAPTURE_PAYLOAD_SETTINGS.ALL, CAPTURE_PAYLOAD_SETTINGS.FAILURES].includes(agentRef.init.ajax?.capture_payloads);
73
75
  ee.on('new-xhr', onNewXhr);
74
76
  ee.on('open-xhr-start', onOpenXhrStart);
75
77
  ee.on('open-xhr-end', onOpenXhrEnd);
76
78
  ee.on('send-xhr-start', onSendXhrStart);
79
+ ee.on('setRequestHeader-xhr-start', onSetRequestHeader);
77
80
  ee.on('xhr-cb-time', onXhrCbTime);
78
81
  ee.on('xhr-load-added', onXhrLoadAdded);
79
82
  ee.on('xhr-load-removed', onXhrLoadRemoved);
@@ -145,6 +148,13 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
145
148
  }
146
149
  }
147
150
  }
151
+ function onSetRequestHeader(args, xhr) {
152
+ // args[0] = header name, args[1] = header value
153
+ if (shouldInterceptPayloads && args.length >= 2) {
154
+ this.requestHeaders ??= {};
155
+ this.requestHeaders[args[0].toLowerCase()] = args[1];
156
+ }
157
+ }
148
158
  function onSendXhrStart(args, xhr) {
149
159
  var metrics = this.metrics;
150
160
  var data = args[0];
@@ -154,7 +164,7 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
154
164
  if (size) metrics.txSize = size;
155
165
  }
156
166
  this.startTime = now();
157
- this.body = data;
167
+ this.requestBody = data;
158
168
  this.listener = function (evt) {
159
169
  try {
160
170
  if (evt.type === 'abort' && !context.loadCaptureCalled) {
@@ -163,7 +173,7 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
163
173
  if (evt.type !== 'load' || context.called === context.totalCbs && (context.onloadCalled || typeof xhr.onload !== 'function') && typeof context.end === 'function') context.end(xhr);
164
174
  } catch (e) {
165
175
  try {
166
- ee.emit('internal-error', [e]);
176
+ ee.emit(INTERNAL_ERROR, [e]);
167
177
  } catch (err) {
168
178
  // do nothing
169
179
  }
@@ -291,8 +301,27 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
291
301
  addUrl(this, extractUrl(target));
292
302
  const method = ('' + (target && target instanceof origRequest && target.method || opts.method || 'GET')).toUpperCase();
293
303
  this.params.method = method;
294
- this.body = opts.body;
295
- this.txSize = dataSize(opts.body) || 0;
304
+ this.txSize = dataSize(opts.body || target?.body) || 0;
305
+
306
+ // Capture request headers
307
+ try {
308
+ var headers = opts.headers || target?.headers;
309
+ if (shouldInterceptPayloads && headers) {
310
+ this.requestHeaders ??= {};
311
+ if (headers instanceof Headers) {
312
+ headers.forEach(function (value, key) {
313
+ this.requestHeaders[key.toLowerCase()] = value;
314
+ }.bind(this));
315
+ } else if (typeof headers === 'object') {
316
+ for (var key in headers) {
317
+ this.requestHeaders[key.toLowerCase()] = headers[key];
318
+ }
319
+ }
320
+ }
321
+ } catch (e) {
322
+ // Silently fail if we can't access headers
323
+ }
324
+ this.requestBody = opts.body || target?.body;
296
325
  }
297
326
 
298
327
  // we capture failed call as status 0, the actual error is ignored
@@ -303,19 +332,45 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
303
332
  if (hasUndefinedHostname(this.params)) return; // don't bother with fetch to url with no hostname
304
333
 
305
334
  this.params.status = res ? res.status : 0;
335
+ const finishAndReport = () => {
336
+ // convert rxSize to a number - handle both string (from content-length header) and number (from fallback)
337
+ const num = +this.rxSize;
338
+ const responseSize = this.rxSize != null && !isNaN(num) ? num : undefined;
339
+ const metrics = {
340
+ txSize: this.txSize,
341
+ rxSize: responseSize,
342
+ duration: this.endTime - this.startTime
343
+ };
344
+ const payload = [this.params, metrics, this.startTime, this.endTime, 'fetch'];
345
+ this.targets.forEach(target => reportToAgg(payload, this, target));
346
+ };
306
347
 
307
- // convert rxSize to a number
308
- let responseSize;
309
- if (typeof this.rxSize === 'string' && this.rxSize.length > 0) {
310
- responseSize = +this.rxSize;
348
+ /** Since accessing fetch bodies is an async process, these are
349
+ * reasonable conditions to check to not do needless extra work */
350
+ if (!res || !shouldInterceptPayloads) {
351
+ finishAndReport();
352
+ return;
311
353
  }
312
- const metrics = {
313
- txSize: this.txSize,
314
- rxSize: responseSize,
315
- duration: now() - this.startTime
316
- };
317
- const payload = [this.params, metrics, this.startTime, this.endTime, 'fetch'];
318
- this.targets.forEach(target => reportToAgg(payload, this, target));
354
+
355
+ // Clone the response to read the body without consuming the original
356
+ res.clone().text().then(text => {
357
+ this.responseBody = text;
358
+ // Use captured payload size as fallback if content-length header was missing or is 0 with a body
359
+ // Only apply fallback for non-network-error responses (status !== 0)
360
+ if ((!this.rxSize || this.rxSize === '0' || this.rxSize === 0) && text !== undefined && this.params.status !== 0) {
361
+ this.rxSize = dataSize(text);
362
+ }
363
+ if (res?.headers) {
364
+ this.responseHeaders = {};
365
+ res.headers.forEach(function (value, key) {
366
+ this.responseHeaders[key.toLowerCase()] = value;
367
+ }.bind(this));
368
+ }
369
+ }).catch(err => {
370
+ ee.emit(INTERNAL_ERROR, [err]);
371
+ }).finally(() => {
372
+ finishAndReport();
373
+ });
319
374
  }
320
375
 
321
376
  // Create report for XHR request that has finished
@@ -330,7 +385,7 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
330
385
  if (params.aborted) return;
331
386
  if (hasUndefinedHostname(params)) return; // don't bother with XHR of url with no hostname
332
387
 
333
- metrics.duration = now() - this.startTime;
388
+ metrics.duration = this.endTime - this.startTime;
334
389
  if (!this.loadCaptureCalled && xhr.readyState === 4) {
335
390
  captureXhrData(this, xhr);
336
391
  } else if (params.status == null) {
@@ -339,6 +394,25 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
339
394
 
340
395
  // Always send cbTime, even if no noticeable time was taken.
341
396
  metrics.cbTime = this.cbTime;
397
+ if (shouldInterceptPayloads) {
398
+ try {
399
+ this.responseBody = xhr.responseText;
400
+ } catch (e) {
401
+ this.responseBody = xhr.response;
402
+ }
403
+
404
+ // Use captured payload size as fallback if not already determined or is 0 with a body
405
+ // Only apply fallback for non-network-error responses (status !== 0)
406
+ if ((!metrics.rxSize || metrics.rxSize === 0) && this.responseBody !== undefined && params.status !== 0) {
407
+ const size = dataSize(this.responseBody);
408
+ if (size !== undefined) metrics.rxSize = size;
409
+ }
410
+ try {
411
+ this.responseHeaders = parseResponseHeaders(xhr.getAllResponseHeaders());
412
+ } catch (err) {
413
+ ee.emit(INTERNAL_ERROR, [err]);
414
+ }
415
+ }
342
416
  const payload = [params, metrics, this.startTime, this.endTime, 'xhr'];
343
417
  this.targets.forEach(target => reportToAgg(payload, this, target));
344
418
  }
@@ -348,7 +422,8 @@ function subscribeToEvents(agentRef, ee, handler, dt) {
348
422
  function captureXhrData(ctx, xhr) {
349
423
  ctx.params.status = xhr.status;
350
424
  var size = responseSizeFromXhr(xhr, ctx.lastSize);
351
- if (size) ctx.metrics.rxSize = size;
425
+ // Don't set rxSize for network errors (status 0)
426
+ if (size !== undefined && xhr.status !== 0) ctx.metrics.rxSize = size;
352
427
  if (ctx.sameOrigin && xhr.getAllResponseHeaders().indexOf(NR_CAT_HEADER) >= 0) {
353
428
  var header = xhr.getResponseHeader(NR_CAT_HEADER);
354
429
  if (header) {
@@ -370,4 +445,17 @@ function addUrl(ctx, url) {
370
445
  ctx.parsedOrigin = parsed;
371
446
  ctx.sameOrigin = parsed.sameOrigin;
372
447
  }
448
+ function parseResponseHeaders(headerStr) {
449
+ const headers = {};
450
+ if (!headerStr) return headers;
451
+ headerStr.split('\r\n').forEach(function (line) {
452
+ const separatorIndex = line.indexOf(': ');
453
+ if (separatorIndex > 0) {
454
+ const name = line.substring(0, separatorIndex);
455
+ const value = line.substring(separatorIndex + 2);
456
+ headers[name.toLowerCase()] = value;
457
+ }
458
+ });
459
+ return headers;
460
+ }
373
461
  export const Ajax = Instrument;
@@ -10,10 +10,11 @@ import { AggregateBase } from '../../utils/aggregate-base';
10
10
  import { warn } from '../../../common/util/console';
11
11
  import { now } from '../../../common/timing/now';
12
12
  import { registerHandler } from '../../../common/event-emitter/register-handler';
13
- import { applyFnToProps } from '../../../common/util/traverse';
13
+ import { Obfuscator } from '../../../common/util/obfuscate';
14
14
  import { UserActionsAggregator } from './user-actions/user-actions-aggregator';
15
15
  import { isIFrameWindow } from '../../../common/dom/iframe';
16
16
  import { isPureObject } from '../../../common/util/type-check';
17
+ import { EVENT_TYPES } from '../../../common/constants/events';
17
18
  import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
18
19
  export class Aggregate extends AggregateBase {
19
20
  static featureName = FEATURE_NAME;
@@ -21,6 +22,10 @@ export class Aggregate extends AggregateBase {
21
22
  constructor(agentRef) {
22
23
  super(agentRef, FEATURE_NAME);
23
24
  this.referrerUrl = isBrowserScope && document.referrer ? cleanURL(document.referrer) : undefined;
25
+
26
+ // Create generic obfuscator (no specific event types) since this feature handles multiple event types
27
+ // Will check each event's eventType property at runtime against obfuscation rules
28
+ this.obfuscator = new Obfuscator(agentRef);
24
29
  this.waitForFlags(['ins']).then(([ins]) => {
25
30
  if (!ins) {
26
31
  this.blocked = true;
@@ -40,7 +45,7 @@ export class Aggregate extends AggregateBase {
40
45
  registerHandler('api-addPageAction', (timestamp, name, attributes, target) => {
41
46
  this.addEvent({
42
47
  ...attributes,
43
- eventType: 'PageAction',
48
+ eventType: EVENT_TYPES.PA,
44
49
  timestamp: this.#toEpoch(timestamp),
45
50
  timeSinceLoad: timestamp / 1000,
46
51
  actionName: name,
@@ -68,7 +73,7 @@ export class Aggregate extends AggregateBase {
68
73
  } = aggregatedUserAction.event;
69
74
  aggregatedUserAction.targets.forEach(mfeTarget => {
70
75
  const userActionEvent = {
71
- eventType: 'UserAction',
76
+ eventType: EVENT_TYPES.UA,
72
77
  timestamp: this.#toEpoch(timeStamp),
73
78
  action: type,
74
79
  actionCount: aggregatedUserAction.count,
@@ -155,7 +160,7 @@ export class Aggregate extends AggregateBase {
155
160
  const detailObj = agentRef.init.performance.capture_detail ? createDetailAttrs(entry.detail) : {};
156
161
  this.addEvent({
157
162
  ...detailObj,
158
- eventType: 'BrowserPerformance',
163
+ eventType: EVENT_TYPES.BP,
159
164
  timestamp: this.#toEpoch(entry.startTime),
160
165
  entryName: entry.name,
161
166
  entryDuration: entry.duration,
@@ -221,7 +226,7 @@ export class Aggregate extends AggregateBase {
221
226
  this.reportSupportabilityMetric('Generic/Performance/Resource/Seen');
222
227
  const event = {
223
228
  ...entryObject,
224
- eventType: 'BrowserPerformance',
229
+ eventType: EVENT_TYPES.BP,
225
230
  timestamp: this.#toEpoch(entryObject.startTime),
226
231
  entryName: cleanURL(name),
227
232
  entryDuration: duration,
@@ -241,7 +246,7 @@ export class Aggregate extends AggregateBase {
241
246
  } = args;
242
247
  const event = {
243
248
  ...customAttributes,
244
- eventType: 'BrowserPerformance',
249
+ eventType: EVENT_TYPES.BP,
245
250
  timestamp: this.#toEpoch(start),
246
251
  entryName: n,
247
252
  entryDuration: duration,
@@ -253,7 +258,7 @@ export class Aggregate extends AggregateBase {
253
258
  registerHandler('ws-complete', nrData => {
254
259
  const event = {
255
260
  ...nrData,
256
- eventType: 'WebSocket',
261
+ eventType: EVENT_TYPES.WS,
257
262
  timestamp: this.#toEpoch(nrData.timestamp),
258
263
  openedAt: this.#toEpoch(nrData.openedAt),
259
264
  closedAt: this.#toEpoch(nrData.closedAt)
@@ -268,7 +273,7 @@ export class Aggregate extends AggregateBase {
268
273
  if (!agentRef.init.feature_flags.includes('no_spv')) {
269
274
  registerHandler('spv', evt => {
270
275
  this.addEvent({
271
- eventType: 'SecurityPolicyViolation',
276
+ eventType: EVENT_TYPES.SPV,
272
277
  timestamp: this.#toEpoch(evt.timeStamp),
273
278
  blockedUri: evt.blockedURI,
274
279
  documentUri: evt.documentURI,
@@ -337,9 +342,9 @@ export class Aggregate extends AggregateBase {
337
342
  });
338
343
  }
339
344
  serializer(eventBuffer) {
340
- return applyFnToProps({
345
+ return this.obfuscator.traverseAndObfuscateEvents({
341
346
  ins: eventBuffer
342
- }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
347
+ });
343
348
  }
344
349
  queryStringsBuilder() {
345
350
  return {
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
+ import { EVENT_TYPES } from '../../common/constants/events';
5
6
  import { FEATURE_NAMES } from '../../loaders/features/features';
6
7
  export const FEATURE_NAME = FEATURE_NAMES.genericEvents;
7
8
  export const OBSERVED_EVENTS = ['auxclick', 'click', 'copy', 'keydown', 'paste', 'scrollend'];
@@ -9,7 +10,7 @@ export const OBSERVED_WINDOW_EVENTS = ['focus', 'blur'];
9
10
  export const RAGE_CLICK_THRESHOLD_EVENTS = 4;
10
11
  export const RAGE_CLICK_THRESHOLD_MS = 1000;
11
12
  export const FRUSTRATION_TIMEOUT_MS = 2000;
12
- export const RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance'];
13
+ export const RESERVED_EVENT_TYPES = [EVENT_TYPES.PA, EVENT_TYPES.UA, EVENT_TYPES.BP];
13
14
  export const FEATURE_FLAGS = {
14
15
  RESOURCES: 'experimental.resources',
15
16
  REGISTER: 'register'
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -9,7 +9,7 @@ export function formatStackTrace(stackLines) {
9
9
  return truncateStackLines(stackLines).replace(stripNewlinesRegex, '');
10
10
  }
11
11
 
12
- // takes array of stack lines and returns string with top 50 and buttom 50 lines
12
+ // takes array of stack lines and returns string with top 50 and bottom 50 lines
13
13
  function truncateStackLines(stackLines) {
14
14
  var stackString;
15
15
  if (stackLines.length > 100) {
@@ -15,11 +15,12 @@ import { FEATURE_NAME } from '../constants';
15
15
  import { FEATURE_NAMES } from '../../../loaders/features/features';
16
16
  import { AggregateBase } from '../../utils/aggregate-base';
17
17
  import { now } from '../../../common/timing/now';
18
- import { applyFnToProps } from '../../../common/util/traverse';
18
+ import { Obfuscator } from '../../../common/util/obfuscate';
19
19
  import { evaluateInternalError } from './internal-errors';
20
20
  import { getRegisteredTargetsFromFilename, getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
21
21
  import { buildCauseString } from './cause-string';
22
22
  import { ShortCircuit } from '../../../common/util/short-circuit';
23
+ import { EVENT_TYPES } from '../../../common/constants/events';
23
24
 
24
25
  /**
25
26
  * @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
@@ -36,6 +37,10 @@ export class Aggregate extends AggregateBase {
36
37
  this.observedAt = {};
37
38
  this.pageviewReported = {};
38
39
  this.errorOnPage = false;
40
+
41
+ // Create obfuscators for each event type this feature handles
42
+ this.errorObfuscator = new Obfuscator(agentRef, EVENT_TYPES.JSE);
43
+ this.xhrObfuscator = new Obfuscator(agentRef, EVENT_TYPES.AJAX);
39
44
  register('err', this.processError.bind(this), this.featureName, this.ee);
40
45
  register('ierr', this.processError.bind(this), this.featureName, this.ee);
41
46
  register('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee);
@@ -51,7 +56,11 @@ export class Aggregate extends AggregateBase {
51
56
  });
52
57
  }
53
58
  serializer(aggregatorTypeToBucketsMap) {
54
- return applyFnToProps(aggregatorTypeToBucketsMap, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
59
+ return {
60
+ err: this.errorObfuscator.traverseAndObfuscateEvents(aggregatorTypeToBucketsMap.err),
61
+ ierr: this.errorObfuscator.traverseAndObfuscateEvents(aggregatorTypeToBucketsMap.ierr),
62
+ xhr: this.xhrObfuscator.traverseAndObfuscateEvents(aggregatorTypeToBucketsMap.xhr)
63
+ };
55
64
  }
56
65
  queryStringsBuilder(aggregatorTakeReturnedData) {
57
66
  const qs = {};
@@ -253,7 +262,7 @@ export class Aggregate extends AggregateBase {
253
262
  params.stack_trace = truncateSize(stackInfo.stackString);
254
263
  this.observedAt[bucketHash] = Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(time));
255
264
  } else {
256
- params.browser_stack_hash = stringHashCode(stackInfo.stackString);
265
+ params.browser_stack_hash = stringHashCode(truncateSize(stackInfo.stackString));
257
266
  }
258
267
  params.releaseIds = stringify(this.agentRef.runtime.releaseIds);
259
268
 
@@ -9,11 +9,12 @@ import { AggregateBase } from '../../utils/aggregate-base';
9
9
  import { FEATURE_NAME, LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS, LOGGING_MODE } from '../constants';
10
10
  import { Log } from '../shared/log';
11
11
  import { isValidLogLevel } from '../shared/utils';
12
- import { applyFnToProps } from '../../../common/util/traverse';
12
+ import { Obfuscator } from '../../../common/util/obfuscate';
13
13
  import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants';
14
14
  import { ABORT_REASONS } from '../../session_replay/constants';
15
15
  import { canEnableSessionTracking } from '../../utils/feature-gates';
16
16
  import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
17
+ import { EVENT_TYPES } from '../../../common/constants/events';
17
18
  const LOGGING_EVENT = 'Logging/Event/';
18
19
  export class Aggregate extends AggregateBase {
19
20
  static featureName = FEATURE_NAME;
@@ -32,6 +33,9 @@ export class Aggregate extends AggregateBase {
32
33
  this.harvestOpts.raw = true;
33
34
  super.customAttributesAreSeparate = true;
34
35
 
36
+ // Create obfuscator for log entries
37
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.LOG);
38
+
35
39
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
36
40
  this.ee.on(SESSION_EVENTS.RESET, () => {
37
41
  this.abort(ABORT_REASONS.RESET);
@@ -50,7 +54,7 @@ export class Aggregate extends AggregateBase {
50
54
  }; // likewise, don't want to overwrite the mode if it was set already
51
55
  const session = this.agentRef.runtime.session;
52
56
  if (canEnableSessionTracking(agentRef.init) && session) {
53
- if (session.isNew) this.#syncWithSessionManager();else updateLocalLoggingMode(session.state.loggingMode, session.state.logApiMode);
57
+ if (session.state.loggingMode === null || session.state.logApiMode === null) this.#writeToStorage(this.loggingMode);else updateLocalLoggingMode(session.state.loggingMode, session.state.logApiMode);
54
58
  }
55
59
  if (this.loggingMode.auto === LOGGING_MODE.OFF && this.loggingMode.api === LOGGING_MODE.OFF) {
56
60
  this.blocked = true;
@@ -112,7 +116,7 @@ export class Aggregate extends AggregateBase {
112
116
  common: {
113
117
  /** Attributes in the `common` section are added to `all` logs generated in the payload */
114
118
  attributes: {
115
- ...applyFnToProps(this.agentRef.info.jsAttributes, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string'),
119
+ ...this.obfuscator.traverseAndObfuscateEvents(this.agentRef.info.jsAttributes),
116
120
  ...(this.harvestEndpointVersion === 1 && {
117
121
  'entity.guid': this.agentRef.runtime.appMetadata.agents[0].entityGuid,
118
122
  appId: this.agentRef.info.applicationID
@@ -137,7 +141,7 @@ export class Aggregate extends AggregateBase {
137
141
  }
138
142
  },
139
143
  /** logs section contains individual unique log entries */
140
- logs: applyFnToProps(eventBuffer, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
144
+ logs: this.obfuscator.traverseAndObfuscateEvents(eventBuffer)
141
145
  }];
142
146
  }
143
147
  queryStringsBuilder() {
@@ -158,13 +162,13 @@ export class Aggregate extends AggregateBase {
158
162
  auto: LOGGING_MODE.OFF,
159
163
  api: LOGGING_MODE.OFF
160
164
  };
161
- this.#syncWithSessionManager();
165
+ this.#writeToStorage(this.loggingMode);
162
166
  this.deregisterDrain();
163
167
  }
164
- #syncWithSessionManager() {
168
+ #writeToStorage(logModes) {
165
169
  this.agentRef.runtime.session?.write({
166
- loggingMode: this.loggingMode.auto,
167
- logApiMode: this.loggingMode.api
170
+ loggingMode: logModes.auto,
171
+ logApiMode: logModes.api
168
172
  });
169
173
  }
170
174
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { registerHandler } from '../../../common/event-emitter/register-handler';
@@ -12,6 +12,7 @@ import { isBrowserScope, isWorkerScope } from '../../../common/constants/runtime
12
12
  import { AggregateBase } from '../../utils/aggregate-base';
13
13
  import { isIFrameWindow } from '../../../common/dom/iframe';
14
14
  import { evaluateHarvestMetadata } from './harvest-metadata';
15
+ import { Obfuscator } from '../../../common/util/obfuscate';
15
16
  // import { WEBSOCKET_TAG } from '../../../common/wrap/wrap-websocket'
16
17
  // import { handleWebsocketEvents } from './websocket-detection'
17
18
 
@@ -21,6 +22,9 @@ export class Aggregate extends AggregateBase {
21
22
  super(agentRef, FEATURE_NAME);
22
23
  this.harvestOpts.aggregatorTypes = ['cm', 'sm']; // the types in EventAggregator this feature cares about
23
24
 
25
+ // Create a generic obfuscator for checking if rules are configured
26
+ this.obfuscator = new Obfuscator(agentRef);
27
+
24
28
  /** all the harvest metadata metrics need to be evaluated simulataneously at unload time so just temporarily buffer them and dont make SMs immediately from the data */
25
29
  this.harvestMetadata = {};
26
30
  this.harvestOpts.beforeUnload = () => {
@@ -15,23 +15,42 @@ import { firstPaint } from '../../../common/vitals/first-paint';
15
15
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
16
16
  import { now } from '../../../common/timing/now';
17
17
  import { TimeKeeper } from '../../../common/timing/time-keeper';
18
- import { applyFnToProps } from '../../../common/util/traverse';
19
- import { send } from '../../../common/harvest/harvester';
18
+ import { send } from '../../../common/harvest/send';
19
+ import { canEnableSessionTracking } from '../../utils/feature-gates';
20
+ import { Obfuscator } from '../../../common/util/obfuscate';
20
21
  import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
21
22
  import { getSubmitMethod } from '../../../common/util/submit-data';
22
23
  import { webdriverDetected } from '../../../common/util/webdriver-detection';
24
+ import { EVENT_TYPES } from '../../../common/constants/events';
23
25
  export class Aggregate extends AggregateBase {
24
26
  static featureName = CONSTANTS.FEATURE_NAME;
25
27
  constructor(agentRef) {
26
28
  super(agentRef, CONSTANTS.FEATURE_NAME);
29
+ this.isSessionTrackingEnabled = canEnableSessionTracking(agentRef.init) && !!agentRef.runtime.session;
27
30
  this.sentRum = false; // flag to facilitate calling sendRum() once externally (by the consent API in agent-session.js)
28
31
 
29
32
  this.timeToFirstByte = 0;
30
33
  this.firstByteToWindowLoad = 0; // our "frontend" duration
31
34
  this.firstByteToDomContent = 0; // our "dom processing" duration
32
35
  this.retries = 0;
36
+
37
+ // Create obfuscator for page view events
38
+ this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.PVE);
33
39
  agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.runtime.session);
34
40
  if (isBrowserScope) {
41
+ const cached = this.isSessionTrackingEnabled && agentRef.runtime.session?.state.cachedRumResponse;
42
+ if (cached) {
43
+ const {
44
+ app: cachedApp,
45
+ ...cachedFlags
46
+ } = cached;
47
+
48
+ // set the agent runtime objects that require the rum response or entity guid
49
+ if (!Object.keys(this.agentRef.runtime.appMetadata).length) agentRef.runtime.appMetadata = cachedApp;
50
+ this.drain();
51
+ agentRef.runtime.harvester.startTimer();
52
+ activateFeatures(cachedFlags, agentRef);
53
+ }
35
54
  timeToFirstByte.subscribe(({
36
55
  value,
37
56
  attrs
@@ -76,14 +95,13 @@ export class Aggregate extends AggregateBase {
76
95
  ua: info.userAttributes,
77
96
  at: info.atts
78
97
  };
79
- if (this.agentRef.runtime.session) queryParameters.fsh = Number(this.agentRef.runtime.session.isNew); // "first session harvest" aka RUM request or PageView event of a session
80
-
81
- let body = applyFnToProps({
98
+ if (this.agentRef.runtime.session) queryParameters.fsh = Number(!this.agentRef.runtime.session.state.cachedRumResponse);
99
+ let body = this.obfuscator.traverseAndObfuscateEvents({
82
100
  ja: {
83
101
  ...customAttributes,
84
102
  webdriverDetected
85
103
  }
86
- }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
104
+ });
87
105
  if (globalScope.performance) {
88
106
  const navTimingEntry = getNavigationEntry();
89
107
  if (navTimingEntry) {
@@ -131,6 +149,7 @@ export class Aggregate extends AggregateBase {
131
149
  }) {
132
150
  const rumEndTime = now();
133
151
  let app, flags;
152
+ const hasCachedRumResponse = !!this.agentRef.runtime.session?.state.cachedRumResponse;
134
153
  try {
135
154
  ({
136
155
  app,
@@ -140,10 +159,20 @@ export class Aggregate extends AggregateBase {
140
159
  // wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
141
160
  warn(53, error);
142
161
  }
162
+ const shouldCacheResponse = this.isSessionTrackingEnabled && !hasCachedRumResponse && !!app;
163
+ if (hasCachedRumResponse) {
164
+ let {
165
+ app: cachedApp,
166
+ ...cachedFlags
167
+ } = this.agentRef.runtime.session.state.cachedRumResponse;
168
+ app ??= cachedApp;
169
+ flags = cachedFlags;
170
+ }
143
171
  super.postHarvestCleanup({
144
172
  sent,
145
- retry
173
+ retry: retry && !hasCachedRumResponse
146
174
  }); // this will set isRetrying & re-buffer the body if request is to be retried
175
+
147
176
  if (this.isRetrying && this.retries++ < 1) {
148
177
  // Only retry once
149
178
  setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
@@ -153,7 +182,6 @@ export class Aggregate extends AggregateBase {
153
182
  }
154
183
  if (status >= 400 || status === 0) {
155
184
  warn(18, status);
156
- this.blocked = true;
157
185
 
158
186
  // Get estimated payload size of our backlog
159
187
  const textEncoder = new TextEncoder();
@@ -164,15 +192,24 @@ export class Aggregate extends AggregateBase {
164
192
  }, 0);
165
193
  const BCSError = 'BCS/Error/';
166
194
  // Send SMs about failed RUM request
167
- const body = {
168
- sm: [{
169
- params: {
170
- name: BCSError + status
171
- },
172
- stats: {
173
- c: 1
174
- }
175
- }, {
195
+ const sm = [{
196
+ params: {
197
+ name: BCSError + status
198
+ },
199
+ stats: {
200
+ c: 1
201
+ }
202
+ }, {
203
+ params: {
204
+ name: BCSError + 'Duration/Ms'
205
+ },
206
+ stats: {
207
+ c: 1,
208
+ t: rumEndTime - this.rumStartTime
209
+ }
210
+ }];
211
+ if (!hasCachedRumResponse) {
212
+ sm.push({
176
213
  params: {
177
214
  name: BCSError + 'Dropped/Bytes'
178
215
  },
@@ -180,28 +217,30 @@ export class Aggregate extends AggregateBase {
180
217
  c: 1,
181
218
  t: payloadSize
182
219
  }
183
- }, {
184
- params: {
185
- name: BCSError + 'Duration/Ms'
186
- },
187
- stats: {
188
- c: 1,
189
- t: rumEndTime - this.rumStartTime
190
- }
191
- }]
192
- };
220
+ });
221
+ }
193
222
  send(this.agentRef, {
194
223
  endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
195
224
  payload: {
196
- body
225
+ body: {
226
+ sm
227
+ }
197
228
  },
198
229
  submitMethod: getSubmitMethod(),
199
230
  featureName: FEATURE_NAMES.metrics
200
231
  });
201
-
202
- // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
203
- this.ee.abort();
204
- return;
232
+ if (!hasCachedRumResponse) {
233
+ this.blocked = true;
234
+ this.ee.abort();
235
+ return;
236
+ }
237
+ } else if (shouldCacheResponse) {
238
+ this.agentRef.runtime.session.write({
239
+ cachedRumResponse: {
240
+ app,
241
+ ...flags
242
+ }
243
+ });
205
244
  }
206
245
  try {
207
246
  const wasReady = this.agentRef.runtime.timeKeeper.ready;