@newrelic/browser-agent 1.301.0 → 1.302.0-alpha.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 (155) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/common/config/init-types.js +1 -1
  3. package/dist/cjs/common/config/init.js +7 -1
  4. package/dist/cjs/common/config/runtime.js +1 -1
  5. package/dist/cjs/common/constants/agent-constants.js +11 -2
  6. package/dist/cjs/common/constants/env.cdn.js +1 -1
  7. package/dist/cjs/common/constants/env.npm.js +1 -1
  8. package/dist/cjs/common/drain/drain.js +1 -1
  9. package/dist/cjs/common/harvest/harvester.js +30 -39
  10. package/dist/cjs/common/util/mfe.js +45 -0
  11. package/dist/cjs/common/wrap/wrap-promise.js +10 -5
  12. package/dist/cjs/features/ajax/aggregate/index.js +5 -1
  13. package/dist/cjs/features/generic_events/aggregate/index.js +9 -8
  14. package/dist/cjs/features/generic_events/constants.js +3 -1
  15. package/dist/cjs/features/generic_events/instrument/index.js +38 -32
  16. package/dist/cjs/features/jserrors/aggregate/index.js +18 -17
  17. package/dist/cjs/features/logging/aggregate/index.js +19 -15
  18. package/dist/cjs/features/logging/shared/utils.js +3 -3
  19. package/dist/cjs/features/page_view_event/aggregate/index.js +51 -33
  20. package/dist/cjs/features/session_replay/aggregate/index.js +16 -15
  21. package/dist/cjs/features/session_replay/constants.js +2 -6
  22. package/dist/cjs/features/session_replay/instrument/index.js +3 -2
  23. package/dist/cjs/features/session_replay/shared/recorder.js +3 -2
  24. package/dist/cjs/features/session_trace/aggregate/index.js +0 -2
  25. package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -2
  26. package/dist/cjs/features/utils/aggregate-base.js +45 -47
  27. package/dist/cjs/loaders/api/addPageAction.js +2 -2
  28. package/dist/cjs/loaders/api/log.js +2 -2
  29. package/dist/cjs/loaders/api/noticeError.js +2 -2
  30. package/dist/cjs/loaders/api/register-api-types.js +5 -5
  31. package/dist/cjs/loaders/api/register.js +80 -97
  32. package/dist/esm/common/config/init-types.js +1 -1
  33. package/dist/esm/common/config/init.js +7 -1
  34. package/dist/esm/common/config/runtime.js +1 -1
  35. package/dist/esm/common/constants/agent-constants.js +9 -1
  36. package/dist/esm/common/constants/env.cdn.js +1 -1
  37. package/dist/esm/common/constants/env.npm.js +1 -1
  38. package/dist/esm/common/drain/drain.js +1 -1
  39. package/dist/esm/common/harvest/harvester.js +30 -39
  40. package/dist/esm/common/util/mfe.js +38 -0
  41. package/dist/esm/common/wrap/wrap-promise.js +10 -5
  42. package/dist/esm/features/ajax/aggregate/index.js +5 -1
  43. package/dist/esm/features/generic_events/aggregate/index.js +9 -8
  44. package/dist/esm/features/generic_events/constants.js +3 -1
  45. package/dist/esm/features/generic_events/instrument/index.js +38 -32
  46. package/dist/esm/features/jserrors/aggregate/index.js +18 -17
  47. package/dist/esm/features/logging/aggregate/index.js +19 -15
  48. package/dist/esm/features/logging/shared/utils.js +3 -3
  49. package/dist/esm/features/page_view_event/aggregate/index.js +51 -33
  50. package/dist/esm/features/session_replay/aggregate/index.js +17 -16
  51. package/dist/esm/features/session_replay/constants.js +1 -5
  52. package/dist/esm/features/session_replay/instrument/index.js +4 -3
  53. package/dist/esm/features/session_replay/shared/recorder.js +3 -2
  54. package/dist/esm/features/session_trace/aggregate/index.js +0 -2
  55. package/dist/esm/features/soft_navigations/aggregate/index.js +1 -2
  56. package/dist/esm/features/utils/aggregate-base.js +46 -48
  57. package/dist/esm/loaders/api/addPageAction.js +2 -2
  58. package/dist/esm/loaders/api/log.js +2 -2
  59. package/dist/esm/loaders/api/noticeError.js +2 -2
  60. package/dist/esm/loaders/api/register-api-types.js +5 -5
  61. package/dist/esm/loaders/api/register.js +80 -97
  62. package/dist/tsconfig.tsbuildinfo +1 -1
  63. package/dist/types/common/config/init-types.d.ts +4 -1
  64. package/dist/types/common/config/init-types.d.ts.map +1 -1
  65. package/dist/types/common/config/init.d.ts.map +1 -1
  66. package/dist/types/common/constants/agent-constants.d.ts +7 -4
  67. package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
  68. package/dist/types/common/harvest/harvester.d.ts +2 -2
  69. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  70. package/dist/types/common/util/mfe.d.ts +20 -0
  71. package/dist/types/common/util/mfe.d.ts.map +1 -0
  72. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  73. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  74. package/dist/types/features/generic_events/aggregate/index.d.ts +2 -2
  75. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  76. package/dist/types/features/generic_events/constants.d.ts +1 -0
  77. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  78. package/dist/types/features/jserrors/aggregate/index.d.ts +3 -3
  79. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  80. package/dist/types/features/logging/aggregate/index.d.ts +3 -3
  81. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  82. package/dist/types/features/logging/shared/utils.d.ts +2 -2
  83. package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
  84. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -4
  85. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  86. package/dist/types/features/session_replay/aggregate/index.d.ts +13 -4
  87. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/session_replay/constants.d.ts +1 -5
  89. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  90. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  91. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -0
  92. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  93. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  94. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  95. package/dist/types/features/utils/aggregate-base.d.ts +13 -5
  96. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  97. package/dist/types/loaders/api/addPageAction.d.ts +1 -1
  98. package/dist/types/loaders/api/addPageAction.d.ts.map +1 -1
  99. package/dist/types/loaders/api/log.d.ts +1 -1
  100. package/dist/types/loaders/api/log.d.ts.map +1 -1
  101. package/dist/types/loaders/api/noticeError.d.ts +1 -1
  102. package/dist/types/loaders/api/noticeError.d.ts.map +1 -1
  103. package/dist/types/loaders/api/register-api-types.d.ts +4 -4
  104. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  105. package/dist/types/loaders/api/register.d.ts +8 -1
  106. package/dist/types/loaders/api/register.d.ts.map +1 -1
  107. package/package.json +3 -3
  108. package/src/common/config/init-types.js +1 -1
  109. package/src/common/config/init.js +3 -1
  110. package/src/common/config/runtime.js +1 -1
  111. package/src/common/constants/agent-constants.js +10 -0
  112. package/src/common/drain/drain.js +1 -1
  113. package/src/common/harvest/harvester.js +27 -32
  114. package/src/common/util/mfe.js +35 -0
  115. package/src/common/wrap/wrap-promise.js +16 -6
  116. package/src/features/ajax/aggregate/index.js +7 -1
  117. package/src/features/generic_events/aggregate/index.js +9 -8
  118. package/src/features/generic_events/constants.js +3 -1
  119. package/src/features/generic_events/instrument/index.js +44 -35
  120. package/src/features/jserrors/aggregate/index.js +17 -17
  121. package/src/features/logging/aggregate/index.js +20 -13
  122. package/src/features/logging/shared/utils.js +3 -3
  123. package/src/features/page_view_event/aggregate/index.js +53 -28
  124. package/src/features/session_replay/aggregate/index.js +16 -13
  125. package/src/features/session_replay/constants.js +1 -5
  126. package/src/features/session_replay/instrument/index.js +4 -3
  127. package/src/features/session_replay/shared/recorder.js +3 -2
  128. package/src/features/session_trace/aggregate/index.js +0 -2
  129. package/src/features/soft_navigations/aggregate/index.js +1 -2
  130. package/src/features/utils/aggregate-base.js +47 -42
  131. package/src/loaders/api/addPageAction.js +2 -2
  132. package/src/loaders/api/log.js +2 -2
  133. package/src/loaders/api/noticeError.js +2 -2
  134. package/src/loaders/api/register-api-types.js +5 -5
  135. package/src/loaders/api/register.js +62 -89
  136. package/dist/cjs/common/util/target.js +0 -34
  137. package/dist/cjs/features/utils/entity-manager.js +0 -46
  138. package/dist/cjs/features/utils/event-store-manager.js +0 -174
  139. package/dist/cjs/loaders/api/register-api.js +0 -165
  140. package/dist/esm/common/util/target.js +0 -27
  141. package/dist/esm/features/utils/entity-manager.js +0 -39
  142. package/dist/esm/features/utils/event-store-manager.js +0 -166
  143. package/dist/esm/loaders/api/register-api.js +0 -159
  144. package/dist/types/common/util/target.d.ts +0 -18
  145. package/dist/types/common/util/target.d.ts.map +0 -1
  146. package/dist/types/features/utils/entity-manager.d.ts +0 -11
  147. package/dist/types/features/utils/entity-manager.d.ts.map +0 -1
  148. package/dist/types/features/utils/event-store-manager.d.ts +0 -85
  149. package/dist/types/features/utils/event-store-manager.d.ts.map +0 -1
  150. package/dist/types/loaders/api/register-api.d.ts +0 -14
  151. package/dist/types/loaders/api/register-api.d.ts.map +0 -1
  152. package/src/common/util/target.js +0 -27
  153. package/src/features/utils/entity-manager.js +0 -45
  154. package/src/features/utils/event-store-manager.js +0 -165
  155. package/src/loaders/api/register-api.js +0 -152
@@ -33,6 +33,13 @@ export class Instrument extends InstrumentBase {
33
33
  setupFinishedAPI(agentRef);
34
34
  setupRegisterAPI(agentRef);
35
35
  setupMeasureAPI(agentRef);
36
+ const ufEnabled = agentRef.init.feature_flags.includes('user_frustrations');
37
+ let historyEE;
38
+ if (isBrowserScope && ufEnabled) {
39
+ wrapFetch(this.ee);
40
+ wrapXhr(this.ee);
41
+ historyEE = wrapHistory(this.ee);
42
+ }
36
43
  if (isBrowserScope) {
37
44
  if (agentRef.init.user_actions.enabled) {
38
45
  OBSERVED_EVENTS.forEach(eventType => windowAddEventListener(eventType, evt => handle('ua', [evt], undefined, this.featureName, this.ee), true));
@@ -46,6 +53,37 @@ export class Instrument extends InstrumentBase {
46
53
  }
47
54
  // Capture is not used here so that we don't get element focus/blur events, only the window's as they do not bubble. They are also not cancellable, so no worries about being front of line.
48
55
  );
56
+ if (ufEnabled) {
57
+ globalScope.addEventListener('error', () => {
58
+ handle('uaErr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
59
+ }, eventListenerOpts(false, this.removeOnAbort?.signal));
60
+ this.ee.on('open-xhr-start', (args, xhr) => {
61
+ if (!isInternalTraffic(args[1])) {
62
+ xhr.addEventListener('readystatechange', () => {
63
+ if (xhr.readyState === 2) {
64
+ // HEADERS_RECEIVED
65
+ handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
66
+ }
67
+ });
68
+ }
69
+ });
70
+ this.ee.on('fetch-start', fetchArguments => {
71
+ if (fetchArguments.length >= 1 && !isInternalTraffic(extractUrl(fetchArguments[0]))) {
72
+ handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
73
+ }
74
+ });
75
+ function isInternalTraffic(url) {
76
+ const parsedUrl = parseUrl(url);
77
+ return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port);
78
+ }
79
+ historyEE.on('pushState-end', navigationChange);
80
+ historyEE.on('replaceState-end', navigationChange);
81
+ window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
82
+ window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
83
+ function navigationChange() {
84
+ historyEE.emit('navChange');
85
+ }
86
+ }
49
87
  }
50
88
  if (agentRef.init.performance.resources.enabled && globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
51
89
  const observer = new PerformanceObserver(list => {
@@ -58,14 +96,6 @@ export class Instrument extends InstrumentBase {
58
96
  buffered: true
59
97
  });
60
98
  }
61
- const historyEE = wrapHistory(this.ee);
62
- historyEE.on('pushState-end', navigationChange);
63
- historyEE.on('replaceState-end', navigationChange);
64
- window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
65
- window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
66
- function navigationChange() {
67
- historyEE.emit('navChange');
68
- }
69
99
  }
70
100
  try {
71
101
  this.removeOnAbort = new AbortController();
@@ -74,30 +104,6 @@ export class Instrument extends InstrumentBase {
74
104
  this.removeOnAbort?.abort();
75
105
  this.abortHandler = undefined; // weakly allow this abort op to run only once
76
106
  };
77
- globalScope.addEventListener('error', () => {
78
- handle('uaErr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
79
- }, eventListenerOpts(false, this.removeOnAbort?.signal));
80
- wrapFetch(this.ee);
81
- wrapXhr(this.ee);
82
- this.ee.on('open-xhr-start', (args, xhr) => {
83
- if (!isInternalTraffic(args[1])) {
84
- xhr.addEventListener('readystatechange', () => {
85
- if (xhr.readyState === 2) {
86
- // HEADERS_RECEIVED
87
- handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
88
- }
89
- });
90
- }
91
- });
92
- this.ee.on('fetch-start', fetchArguments => {
93
- if (fetchArguments.length >= 1 && !isInternalTraffic(extractUrl(fetchArguments[0]))) {
94
- handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
95
- }
96
- });
97
- function isInternalTraffic(url) {
98
- const parsedUrl = parseUrl(url);
99
- return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port);
100
- }
101
107
 
102
108
  /** If any of the sources are active, import the aggregator. otherwise deregister */
103
109
  if (genericEventSourceConfigs.some(x => x)) this.importAggregator(agentRef, () => import(/* webpackChunkName: "generic_events-aggregate" */'../aggregate'));else this.deregisterDrain();
@@ -17,8 +17,7 @@ import { AggregateBase } from '../../utils/aggregate-base';
17
17
  import { now } from '../../../common/timing/now';
18
18
  import { applyFnToProps } from '../../../common/util/traverse';
19
19
  import { evaluateInternalError } from './internal-errors';
20
- import { isContainerAgentTarget } from '../../../common/util/target';
21
- import { warn } from '../../../common/util/console';
20
+ import { getVersion2Attributes } from '../../../common/util/mfe';
22
21
  import { buildCauseString } from './cause-string';
23
22
 
24
23
  /**
@@ -29,6 +28,9 @@ export class Aggregate extends AggregateBase {
29
28
  static featureName = FEATURE_NAME;
30
29
  constructor(agentRef) {
31
30
  super(agentRef, FEATURE_NAME);
31
+
32
+ /** set up agg-level behaviors specific to this feature */
33
+ this.harvestOpts.aggregatorTypes = ['err', 'ierr', 'xhr']; // the types in EventAggregator this feature cares about
32
34
  this.stackReported = {};
33
35
  this.observedAt = {};
34
36
  this.pageviewReported = {};
@@ -41,8 +43,6 @@ export class Aggregate extends AggregateBase {
41
43
  register('ierr', (...args) => this.storeError(...args), this.featureName, this.ee);
42
44
  register('softNavFlush', (interactionId, wasFinished, softNavAttrs, interactionEndTime) => this.onSoftNavNotification(interactionId, wasFinished, softNavAttrs, interactionEndTime), this.featureName, this.ee); // when an ixn is done or cancelled
43
45
 
44
- this.harvestOpts.aggregatorTypes = ['err', 'ierr', 'xhr']; // the types in EventAggregator this feature cares about
45
-
46
46
  // 0 == off, 1 == on
47
47
  this.waitForFlags(['err']).then(([errFlag]) => {
48
48
  if (errFlag) {
@@ -102,10 +102,8 @@ export class Aggregate extends AggregateBase {
102
102
  * @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
103
103
  * @returns
104
104
  */
105
- storeError(err, time, internal, customAttributes, hasReplay, swallowReason, targetEntityGuid) {
105
+ storeError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
106
106
  if (!err) return;
107
- const target = this.agentRef.runtime.entityManager.get(targetEntityGuid);
108
- if (!target) return warn(56, this.featureName);
109
107
  // are we in an interaction
110
108
  time = time || now();
111
109
  let filterOutput;
@@ -143,7 +141,7 @@ export class Aggregate extends AggregateBase {
143
141
  if (filterOutput?.group) params.errorGroup = filterOutput.group;
144
142
 
145
143
  // Should only decorate "hasReplay" for the container agent, so check if the target matches the config
146
- if (hasReplay && isContainerAgentTarget(target, this.agentRef)) params.hasReplay = hasReplay;
144
+ if (hasReplay && !target) params.hasReplay = hasReplay;
147
145
  /**
148
146
  * The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
149
147
  * stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
@@ -178,14 +176,14 @@ export class Aggregate extends AggregateBase {
178
176
 
179
177
  // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
180
178
  const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
181
- if (this.shouldAllowMainAgentToCapture(targetEntityGuid)) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
179
+ if (this.shouldAllowMainAgentToCapture(target)) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
182
180
  // still send EE events for other features such as above, but stop this one from aggregating internal data
183
181
  if (this.blocked) return;
184
182
  if (err?.__newrelic?.[this.agentIdentifier]) {
185
183
  params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
186
184
  params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
187
185
  }
188
- if (this.shouldAllowMainAgentToCapture(targetEntityGuid)) {
186
+ if (this.shouldAllowMainAgentToCapture(target)) {
189
187
  const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav]);
190
188
  // Note: the following are subject to potential race cond wherein if the other feature aren't fully initialized, it'll be treated as there being no associated interaction.
191
189
  // They each will also tack on their respective properties to the params object as part of the decision flow.
@@ -206,11 +204,14 @@ export class Aggregate extends AggregateBase {
206
204
  }
207
205
 
208
206
  // always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
209
- if (targetEntityGuid) this.#storeJserrorForHarvest([...jsErrorEvent, targetEntityGuid], false, params._softNavAttributes);
207
+ if (target) this.#storeJserrorForHarvest([...jsErrorEvent, target], false, params._softNavAttributes);
210
208
  }
211
209
  #storeJserrorForHarvest(errorInfoArr, softNavOccurredFinished, softNavCustomAttrs = {}) {
212
- let [type, bucketHash, params, newMetrics, localAttrs, targetEntityGuid] = errorInfoArr;
213
- const allCustomAttrs = {};
210
+ let [type, bucketHash, params, newMetrics, localAttrs, target] = errorInfoArr;
211
+ const allCustomAttrs = {
212
+ /** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
213
+ ...getVersion2Attributes(target, this)
214
+ };
214
215
  if (softNavOccurredFinished) {
215
216
  Object.entries(softNavCustomAttrs).forEach(([k, v]) => setCustom(k, v)); // when an ixn finishes, it'll include stuff in jsAttributes + attrs specific to the ixn
216
217
  bucketHash += params.browserInteractionId;
@@ -225,7 +226,7 @@ export class Aggregate extends AggregateBase {
225
226
 
226
227
  const jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
227
228
  const aggregateHash = bucketHash + ':' + jsAttributesHash;
228
- this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs], targetEntityGuid);
229
+ this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]);
229
230
  function setCustom(key, val) {
230
231
  allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
231
232
  }
@@ -234,11 +235,11 @@ export class Aggregate extends AggregateBase {
234
235
  /**
235
236
  * If the event lacks an entityGuid (the default behavior), the main agent should capture the data. If the data is assigned to a sub-entity target
236
237
  * the main agent should not capture events unless it is configured to do so.
237
- * @param {string} entityGuid - the context object for the event
238
+ * @param {string} target - the context object for the event
238
239
  * @returns {boolean} - whether the main agent should capture the event to its internal target
239
240
  */
240
- shouldAllowMainAgentToCapture(entityGuid) {
241
- return !entityGuid || this.agentRef.init.api.duplicate_registered_data;
241
+ shouldAllowMainAgentToCapture(target) {
242
+ return !target || this.agentRef.init.api.duplicate_registered_data;
242
243
  }
243
244
 
244
245
  // TO-DO: Remove this function when old spa is taken out. #storeJserrorForHarvest handles the work with the softnav feature.
@@ -10,15 +10,18 @@ import { FEATURE_NAME, LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS, LOGGING_MODE }
10
10
  import { Log } from '../shared/log';
11
11
  import { isValidLogLevel } from '../shared/utils';
12
12
  import { applyFnToProps } from '../../../common/util/traverse';
13
- import { isContainerAgentTarget } from '../../../common/util/target';
14
13
  import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants';
15
14
  import { ABORT_REASONS } from '../../session_replay/constants';
16
15
  import { canEnableSessionTracking } from '../../utils/feature-gates';
16
+ import { getVersion2Attributes } from '../../../common/util/mfe';
17
17
  export class Aggregate extends AggregateBase {
18
18
  static featureName = FEATURE_NAME;
19
19
  constructor(agentRef) {
20
20
  super(agentRef, FEATURE_NAME);
21
21
  this.isSessionTrackingEnabled = canEnableSessionTracking(agentRef.init) && agentRef.runtime.session;
22
+
23
+ /** set up agg-level behaviors specific to this feature */
24
+ this.harvestOpts.raw = true;
22
25
  super.customAttributesAreSeparate = true;
23
26
 
24
27
  // 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.
@@ -29,7 +32,6 @@ export class Aggregate extends AggregateBase {
29
32
  if (this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
30
33
  if (this.loggingMode !== LOGGING_MODE.OFF && data.loggingMode === LOGGING_MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);else this.loggingMode = data.loggingMode;
31
34
  });
32
- this.harvestOpts.raw = true;
33
35
  this.waitForFlags(['log']).then(([loggingMode]) => {
34
36
  const session = this.agentRef.runtime.session ?? {};
35
37
  if (this.loggingMode === LOGGING_MODE.OFF || session.isNew && loggingMode === LOGGING_MODE.OFF) {
@@ -56,10 +58,14 @@ export class Aggregate extends AggregateBase {
56
58
  loggingMode: this.loggingMode
57
59
  });
58
60
  }
59
- handleLog(timestamp, message, attributes = {}, level = LOG_LEVELS.INFO, targetEntityGuid) {
60
- if (!this.agentRef.runtime.entityManager.get(targetEntityGuid)) return warn(56, this.featureName);
61
+ handleLog(timestamp, message, attributes = {}, level = LOG_LEVELS.INFO, target) {
61
62
  if (this.blocked || !this.loggingMode) return;
62
63
  if (!attributes || typeof attributes !== 'object') attributes = {};
64
+ attributes = {
65
+ ...attributes,
66
+ /** Specific attributes only supplied if harvesting to endpoint version 2 */
67
+ ...getVersion2Attributes(target, this)
68
+ };
63
69
  if (typeof level === 'string') level = level.toUpperCase();
64
70
  if (!isValidLogLevel(level)) return warn(30, level);
65
71
  if (this.loggingMode < (LOGGING_MODE[level] || Infinity)) {
@@ -83,10 +89,9 @@ export class Aggregate extends AggregateBase {
83
89
  }
84
90
  if (typeof message !== 'string' || !message) return warn(32);
85
91
  const log = new Log(Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp)), message, attributes, level);
86
- this.events.add(log, targetEntityGuid);
92
+ this.events.add(log);
87
93
  }
88
- serializer(eventBuffer, targetEntityGuid) {
89
- const target = this.agentRef.runtime.entityManager.get(targetEntityGuid);
94
+ serializer(eventBuffer) {
90
95
  const sessionEntity = this.agentRef.runtime.session;
91
96
  return [{
92
97
  common: {
@@ -94,19 +99,19 @@ export class Aggregate extends AggregateBase {
94
99
  attributes: {
95
100
  ...this.agentRef.info.jsAttributes,
96
101
  // user-provided custom attributes
97
- 'entity.guid': target.entityGuid,
98
- // browser entity guid as provided API target OR the default from RUM response if not supplied
102
+ ...(this.harvestEndpointVersion === 1 && {
103
+ 'entity.guid': this.agentRef.runtime.appMetadata.agents[0].entityGuid,
104
+ appId: this.agentRef.info.applicationID
105
+ }),
99
106
  ...(sessionEntity && {
100
107
  session: sessionEntity.state.value || '0',
101
108
  // The session ID that we generate and keep across page loads
102
- hasReplay: sessionEntity.state.sessionReplayMode === 1 && isContainerAgentTarget(target, this.agentRef),
109
+ hasReplay: sessionEntity.state.sessionReplayMode === 1,
103
110
  // True if a session replay recording is running
104
111
  hasTrace: sessionEntity.state.sessionTraceMode === 1 // True if a session trace recording is running
105
112
  }),
106
113
  ptid: this.agentRef.runtime.ptid,
107
114
  // page trace id
108
- appId: target.applicationID || this.agentRef.info.applicationID,
109
- // Application ID from info object,
110
115
  standalone: Boolean(this.agentRef.info.sa),
111
116
  // copy paste (true) vs APM (false)
112
117
  agentVersion: this.agentRef.runtime.version,
@@ -121,10 +126,9 @@ export class Aggregate extends AggregateBase {
121
126
  logs: applyFnToProps(eventBuffer, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
122
127
  }];
123
128
  }
124
- queryStringsBuilder(_, targetEntityGuid) {
125
- const target = this.agentRef.runtime.entityManager.get(targetEntityGuid);
129
+ queryStringsBuilder() {
126
130
  return {
127
- browser_monitoring_key: target.licenseKey
131
+ browser_monitoring_key: this.agentRef.info.licenseKey
128
132
  };
129
133
  }
130
134
 
@@ -13,11 +13,11 @@ import { LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS } from '../constants';
13
13
  * @param {string} message - the log message string
14
14
  * @param {{[key: string]: *}} customAttributes - The log's custom attributes if any
15
15
  * @param {enum} level - the log level enum
16
- * @param {object=} targetEntityGuid - the optional target entity guid provided by an api call
16
+ * @param {object=} target - the optional target provided by an api call
17
17
  */
18
- export function bufferLog(ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, targetEntityGuid, timestamp = now()) {
18
+ export function bufferLog(ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, target, timestamp = now()) {
19
19
  handle(SUPPORTABILITY_METRIC_CHANNEL, ["API/logging/".concat(level.toLowerCase(), "/called")], undefined, FEATURE_NAMES.metrics, ee);
20
- handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, targetEntityGuid], undefined, FEATURE_NAMES.logging, ee);
20
+ handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, target], undefined, FEATURE_NAMES.logging, ee);
21
21
  }
22
22
 
23
23
  /**
@@ -17,8 +17,9 @@ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
17
17
  import { now } from '../../../common/timing/now';
18
18
  import { TimeKeeper } from '../../../common/timing/time-keeper';
19
19
  import { applyFnToProps } from '../../../common/util/traverse';
20
- import { registerHandler } from '../../../common/event-emitter/register-handler';
21
- import { isContainerAgentTarget } from '../../../common/util/target';
20
+ import { send } from '../../../common/harvest/harvester';
21
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
22
+ import { getSubmitMethod } from '../../../common/util/submit-data';
22
23
  export class Aggregate extends AggregateBase {
23
24
  static featureName = CONSTANTS.FEATURE_NAME;
24
25
  constructor(agentRef) {
@@ -27,9 +28,6 @@ export class Aggregate extends AggregateBase {
27
28
  this.firstByteToWindowLoad = 0; // our "frontend" duration
28
29
  this.firstByteToDomContent = 0; // our "dom processing" duration
29
30
 
30
- registerHandler('send-rum', (customAttibutes, target) => {
31
- this.sendRum(customAttibutes, target);
32
- }, this.featureName, this.ee);
33
31
  if (!isValid(agentRef.info)) {
34
32
  this.ee.abort();
35
33
  return warn(43);
@@ -57,7 +55,7 @@ export class Aggregate extends AggregateBase {
57
55
  *
58
56
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
59
57
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
60
- * @param {*} target The target to harvest to - Since we will not know the entityGuid before harvesting, this must be an object directly supplied from the info object or API, not an entityGuid string for lookup with the entityManager - Defaults to { licenseKey: this.agentRef.info.licenseKey, applicationID: this.agentRef.info.applicationID }
58
+ * @param {*} target The target to harvest to
61
59
  */
62
60
  sendRum(customAttributes = this.agentRef.info.jsAttributes, target = {
63
61
  licenseKey: this.agentRef.info.licenseKey,
@@ -120,7 +118,7 @@ export class Aggregate extends AggregateBase {
120
118
  this.rumStartTime = now();
121
119
  this.agentRef.runtime.harvester.triggerHarvestFor(this, {
122
120
  directSend: {
123
- targetApp: target,
121
+ target,
124
122
  payload: {
125
123
  qs: queryParameters,
126
124
  body
@@ -133,8 +131,7 @@ export class Aggregate extends AggregateBase {
133
131
  postHarvestCleanup({
134
132
  status,
135
133
  responseText,
136
- xhr,
137
- targetApp
134
+ xhr
138
135
  }) {
139
136
  const rumEndTime = now();
140
137
  let app, flags;
@@ -143,16 +140,57 @@ export class Aggregate extends AggregateBase {
143
140
  app,
144
141
  ...flags
145
142
  } = JSON.parse(responseText));
146
- this.processEntities(app.agents, targetApp);
147
143
  } catch (error) {
148
144
  // wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
149
145
  warn(53, error);
150
146
  }
151
-
152
- /** Only run agent-wide side-effects if the harvest was for the main agent */
153
- if (!isContainerAgentTarget(targetApp, this.agentRef)) return;
154
147
  if (status >= 400 || status === 0) {
155
148
  warn(18, status);
149
+
150
+ // Get estimated payload size of our backlog
151
+ const textEncoder = new TextEncoder();
152
+ const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
153
+ if (!value) return acc;
154
+ const encoded = textEncoder.encode(value);
155
+ return acc + encoded.byteLength;
156
+ }, 0);
157
+
158
+ // Send SMs about failed RUM request
159
+ const body = {
160
+ sm: [{
161
+ params: {
162
+ name: "Browser/Supportability/BCS/Error/".concat(status)
163
+ },
164
+ stats: {
165
+ c: 1
166
+ }
167
+ }, {
168
+ params: {
169
+ name: 'Browser/Supportability/BCS/Error/Dropped/Bytes'
170
+ },
171
+ stats: {
172
+ c: 1,
173
+ t: payloadSize
174
+ }
175
+ }, {
176
+ params: {
177
+ name: 'Browser/Supportability/BCS/Error/Duration/Ms'
178
+ },
179
+ stats: {
180
+ c: 1,
181
+ t: rumEndTime - this.rumStartTime
182
+ }
183
+ }]
184
+ };
185
+ send(this.agentRef, {
186
+ endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
187
+ payload: {
188
+ body
189
+ },
190
+ submitMethod: getSubmitMethod(),
191
+ featureName: FEATURE_NAMES.metrics
192
+ });
193
+
156
194
  // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
157
195
  this.ee.abort();
158
196
  return;
@@ -181,24 +219,4 @@ export class Aggregate extends AggregateBase {
181
219
  this.agentRef.runtime.harvester.startTimer();
182
220
  activateFeatures(flags, this.agentRef);
183
221
  }
184
- processEntities(entities, targetApp) {
185
- if (!entities || !targetApp) return;
186
- entities.forEach(agent => {
187
- const entityManager = this.agentRef.runtime.entityManager;
188
- const entityGuid = agent.entityGuid;
189
- const entity = entityManager.get(entityGuid);
190
- if (entity) return; // already processed
191
-
192
- if (isContainerAgentTarget(targetApp, this.agentRef)) {
193
- entityManager.setDefaultEntity({
194
- ...targetApp,
195
- entityGuid
196
- });
197
- }
198
- entityManager.set(agent.entityGuid, {
199
- ...targetApp,
200
- entityGuid
201
- });
202
- });
203
- }
204
222
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { registerHandler } from '../../../common/event-emitter/register-handler';
10
- import { ABORT_REASONS, FEATURE_NAME, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants';
10
+ import { ABORT_REASONS, ERROR_DURING_REPLAY, FEATURE_NAME, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, TRIGGERS } from '../constants';
11
11
  import { AggregateBase } from '../../utils/aggregate-base';
12
12
  import { sharedChannel } from '../../../common/constants/shared-channel';
13
13
  import { obj as encodeObj } from '../../../common/url/encode';
@@ -21,6 +21,7 @@ import { now } from '../../../common/timing/now';
21
21
  import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
22
22
  import { cleanURL } from '../../../common/url/clean-url';
23
23
  import { canEnableSessionTracking } from '../../utils/feature-gates';
24
+ import { PAUSE_REPLAY } from '../../../loaders/api/constants';
24
25
  export class Aggregate extends AggregateBase {
25
26
  static featureName = FEATURE_NAME;
26
27
  mode = MODE.OFF;
@@ -36,6 +37,8 @@ export class Aggregate extends AggregateBase {
36
37
  this.gzipper = undefined;
37
38
  /** populated with the u8 string lib async */
38
39
  this.u8 = undefined;
40
+ /** flips to false if the compressor libraries cannot import */
41
+ this.shouldCompress = true;
39
42
 
40
43
  /** set by BCS response */
41
44
  this.entitled = false;
@@ -70,10 +73,10 @@ export class Aggregate extends AggregateBase {
70
73
  if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
71
74
  this.mode = data.sessionReplayMode;
72
75
  });
73
- registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
76
+ registerHandler(PAUSE_REPLAY, () => {
74
77
  this.forceStop(this.mode === MODE.FULL);
75
78
  }, this.featureName, this.ee);
76
- registerHandler(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
79
+ registerHandler(ERROR_DURING_REPLAY, e => {
77
80
  this.handleError(e);
78
81
  }, this.featureName, this.ee);
79
82
  const {
@@ -207,23 +210,22 @@ export class Aggregate extends AggregateBase {
207
210
  this.gzipper = gzipSync;
208
211
  this.u8 = strToU8;
209
212
  } catch (err) {
210
- // compressor failed to load, but we can still record without compression as a last ditch effort
213
+ this.shouldCompress = false;
214
+ // compressor failed to load, but we can still try to record without compression as a last ditch effort
211
215
  }
212
216
  }
213
- makeHarvestPayload(shouldRetryOnFail) {
214
- const payloadOutput = {
215
- targetApp: undefined,
216
- payload: undefined
217
- };
218
- if (this.mode !== MODE.FULL || this.blocked) return;
219
- if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return;
217
+ makeHarvestPayload() {
218
+ if (this.mode !== MODE.FULL || this.blocked) return; // harvests should only be made in FULL mode, and not if the feature is blocked
219
+ if (this.shouldCompress && !this.gzipper) return; // if compression is enabled, but the libraries have not loaded, wait for them to load
220
+ if (!this.recorder || !this.timeKeeper?.ready || !(this.recorder.hasSeenSnapshot && this.recorder.hasSeenMeta)) return; // if the recorder or the timekeeper is not ready, or the recorder has not yet seen a snapshot, do not harvest
221
+
220
222
  const recorderEvents = this.recorder.getEvents();
221
223
  // get the event type and use that to trigger another harvest if needed
222
224
  if (!recorderEvents.events.length) return;
223
225
  const payload = this.getHarvestContents(recorderEvents);
224
226
  if (!payload.body.length) {
225
227
  this.recorder.clearBuffer();
226
- return [payloadOutput];
228
+ return;
227
229
  }
228
230
  this.reportSupportabilityMetric('SessionReplay/Harvest/Attempts');
229
231
  let len = 0;
@@ -238,19 +240,18 @@ export class Aggregate extends AggregateBase {
238
240
  }
239
241
  if (len > MAX_PAYLOAD_SIZE) {
240
242
  this.abort(ABORT_REASONS.TOO_BIG, len);
241
- return [payloadOutput];
243
+ return;
242
244
  }
245
+
243
246
  // TODO -- Gracefully handle the buffer for retries.
244
247
  if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
245
248
  sessionReplaySentFirstChunk: true
246
249
  });
247
250
  this.recorder.clearBuffer();
248
- if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this);
249
- payloadOutput.payload = payload;
250
251
  if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
251
252
  warn(59, JSON.stringify(this.agentRef.runtime.session.state));
252
253
  }
253
- return [payloadOutput];
254
+ return payload;
254
255
  }
255
256
 
256
257
  /**
@@ -5,11 +5,7 @@
5
5
  import { MODE } from '../../common/session/constants';
6
6
  import { FEATURE_NAMES } from '../../loaders/features/features';
7
7
  export const FEATURE_NAME = FEATURE_NAMES.sessionReplay;
8
- export const SR_EVENT_EMITTER_TYPES = {
9
- RECORD: 'recordReplay',
10
- PAUSE: 'pauseReplay',
11
- ERROR_DURING_REPLAY: 'errorDuringReplay'
12
- };
8
+ export const ERROR_DURING_REPLAY = 'errorDuringReplay';
13
9
  export const AVG_COMPRESSION = 0.12;
14
10
  export const RRWEB_EVENT_TYPES = {
15
11
  DomContentLoaded: 0,
@@ -10,9 +10,10 @@ import { handle } from '../../../common/event-emitter/handle';
10
10
  import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants';
11
11
  import { InstrumentBase } from '../../utils/instrument-base';
12
12
  import { hasReplayPrerequisite, isPreloadAllowed } from '../shared/utils';
13
- import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants';
13
+ import { ERROR_DURING_REPLAY, FEATURE_NAME, TRIGGERS } from '../constants';
14
14
  import { setupRecordReplayAPI } from '../../../loaders/api/recordReplay';
15
15
  import { setupPauseReplayAPI } from '../../../loaders/api/pauseReplay';
16
+ import { RECORD_REPLAY } from '../../../loaders/api/constants';
16
17
  export class Instrument extends InstrumentBase {
17
18
  static featureName = FEATURE_NAME;
18
19
  /** @type {Promise|undefined} A promise that resolves when the recorder module is imported and added to the class. Undefined if the recorder has never been staged to import with `importRecorder`. */
@@ -30,7 +31,7 @@ export class Instrument extends InstrumentBase {
30
31
  session = JSON.parse(localStorage.getItem("".concat(PREFIX, "_").concat(DEFAULT_KEY)));
31
32
  } catch (err) {}
32
33
  if (hasReplayPrerequisite(agentRef.init)) {
33
- this.ee.on(SR_EVENT_EMITTER_TYPES.RECORD, () => this.#apiStartOrRestartReplay());
34
+ this.ee.on(RECORD_REPLAY, () => this.#apiStartOrRestartReplay());
34
35
  }
35
36
  if (this.#canPreloadRecorder(session)) {
36
37
  this.importRecorder().then(recorder => {
@@ -44,7 +45,7 @@ export class Instrument extends InstrumentBase {
44
45
  if (this.blocked) return;
45
46
  if (this.agentRef.runtime.isRecording) {
46
47
  this.errorNoticed = true;
47
- handle(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee);
48
+ handle(ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee);
48
49
  }
49
50
  });
50
51
  }
@@ -42,6 +42,7 @@ export class Recorder {
42
42
  this.backloggedEvents = new RecorderEvents(this.shouldFix);
43
43
  /** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
44
44
  this.hasSeenSnapshot = false;
45
+ this.hasSeenMeta = false;
45
46
  /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
46
47
  this.lastMeta = false;
47
48
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
@@ -196,9 +197,9 @@ export class Recorder {
196
197
  }
197
198
 
198
199
  // meta event
199
- this.events.hasMeta ||= event.type === RRWEB_EVENT_TYPES.Meta;
200
+ this.hasSeenMeta ||= this.events.hasMeta ||= event.type === RRWEB_EVENT_TYPES.Meta;
200
201
  // snapshot event
201
- this.events.hasSnapshot ||= this.hasSeenSnapshot ||= event.type === RRWEB_EVENT_TYPES.FullSnapshot;
202
+ this.hasSeenSnapshot ||= this.events.hasSnapshot ||= event.type === RRWEB_EVENT_TYPES.FullSnapshot;
202
203
 
203
204
  //* dont let the EventBuffer class double evaluate the event data size, it's a performance burden and we have special reasons to do it outside the event buffer */
204
205
  this.events.add(event, eventBytes);
@@ -92,8 +92,6 @@ export class Aggregate extends AggregateBase {
92
92
  sessionTraceMode: this.mode
93
93
  });
94
94
  this.drain();
95
- /** try to harvest immediately. This will not send if the trace is not running in FULL mode due to the pre-harvest checks. */
96
- this.agentRef.runtime.harvester.triggerHarvestFor(this);
97
95
  }
98
96
  preHarvestChecks() {
99
97
  if (this.blocked || this.mode !== MODE.FULL) return; // only allow harvest if running in full mode
@@ -46,7 +46,6 @@ export class Aggregate extends AggregateBase {
46
46
  this.waitForFlags(['spa']).then(([spaOn]) => {
47
47
  if (spaOn) {
48
48
  this.drain();
49
- setTimeout(() => agentRef.runtime.harvester.triggerHarvestFor(this), 0); // send the IPL ixn on next tick, giving some time for any ajax to finish; we may want to just remove this?
50
49
  } else {
51
50
  this.blocked = true; // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
52
51
  this.deregisterDrain();
@@ -141,7 +140,7 @@ export class Aggregate extends AggregateBase {
141
140
  */
142
141
  if (this.interactionInProgress?.isActiveDuring(timestamp)) return this.interactionInProgress;
143
142
  let saveIxn;
144
- const interactionsBuffer = this.interactionsToHarvest.get()?.[0]?.data;
143
+ const interactionsBuffer = this.interactionsToHarvest.get();
145
144
  if (!interactionsBuffer) return undefined; // no interactions have been staged yet, so nothing to search through)
146
145
  for (let idx = interactionsBuffer.length - 1; idx >= 0; idx--) {
147
146
  // reverse search for the latest completed interaction for efficiency