@newrelic/browser-agent 1.301.0 → 1.302.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 (142) 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/features/ajax/aggregate/index.js +5 -1
  12. package/dist/cjs/features/generic_events/aggregate/index.js +9 -8
  13. package/dist/cjs/features/generic_events/constants.js +3 -1
  14. package/dist/cjs/features/generic_events/instrument/index.js +38 -32
  15. package/dist/cjs/features/jserrors/aggregate/index.js +18 -17
  16. package/dist/cjs/features/logging/aggregate/index.js +19 -15
  17. package/dist/cjs/features/logging/shared/utils.js +3 -3
  18. package/dist/cjs/features/page_view_event/aggregate/index.js +3 -33
  19. package/dist/cjs/features/session_replay/aggregate/index.js +13 -13
  20. package/dist/cjs/features/session_replay/shared/recorder.js +3 -2
  21. package/dist/cjs/features/session_trace/aggregate/index.js +0 -2
  22. package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -2
  23. package/dist/cjs/features/utils/aggregate-base.js +45 -47
  24. package/dist/cjs/loaders/api/addPageAction.js +2 -2
  25. package/dist/cjs/loaders/api/log.js +2 -2
  26. package/dist/cjs/loaders/api/noticeError.js +2 -2
  27. package/dist/cjs/loaders/api/register-api-types.js +5 -5
  28. package/dist/cjs/loaders/api/register.js +80 -97
  29. package/dist/esm/common/config/init-types.js +1 -1
  30. package/dist/esm/common/config/init.js +7 -1
  31. package/dist/esm/common/config/runtime.js +1 -1
  32. package/dist/esm/common/constants/agent-constants.js +9 -1
  33. package/dist/esm/common/constants/env.cdn.js +1 -1
  34. package/dist/esm/common/constants/env.npm.js +1 -1
  35. package/dist/esm/common/drain/drain.js +1 -1
  36. package/dist/esm/common/harvest/harvester.js +30 -39
  37. package/dist/esm/common/util/mfe.js +38 -0
  38. package/dist/esm/features/ajax/aggregate/index.js +5 -1
  39. package/dist/esm/features/generic_events/aggregate/index.js +9 -8
  40. package/dist/esm/features/generic_events/constants.js +3 -1
  41. package/dist/esm/features/generic_events/instrument/index.js +38 -32
  42. package/dist/esm/features/jserrors/aggregate/index.js +18 -17
  43. package/dist/esm/features/logging/aggregate/index.js +19 -15
  44. package/dist/esm/features/logging/shared/utils.js +3 -3
  45. package/dist/esm/features/page_view_event/aggregate/index.js +3 -33
  46. package/dist/esm/features/session_replay/aggregate/index.js +13 -13
  47. package/dist/esm/features/session_replay/shared/recorder.js +3 -2
  48. package/dist/esm/features/session_trace/aggregate/index.js +0 -2
  49. package/dist/esm/features/soft_navigations/aggregate/index.js +1 -2
  50. package/dist/esm/features/utils/aggregate-base.js +46 -48
  51. package/dist/esm/loaders/api/addPageAction.js +2 -2
  52. package/dist/esm/loaders/api/log.js +2 -2
  53. package/dist/esm/loaders/api/noticeError.js +2 -2
  54. package/dist/esm/loaders/api/register-api-types.js +5 -5
  55. package/dist/esm/loaders/api/register.js +80 -97
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/dist/types/common/config/init-types.d.ts +4 -1
  58. package/dist/types/common/config/init-types.d.ts.map +1 -1
  59. package/dist/types/common/config/init.d.ts.map +1 -1
  60. package/dist/types/common/constants/agent-constants.d.ts +7 -4
  61. package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
  62. package/dist/types/common/harvest/harvester.d.ts +2 -2
  63. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  64. package/dist/types/common/util/mfe.d.ts +20 -0
  65. package/dist/types/common/util/mfe.d.ts.map +1 -0
  66. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/generic_events/aggregate/index.d.ts +2 -2
  68. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/generic_events/constants.d.ts +1 -0
  70. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  71. package/dist/types/features/jserrors/aggregate/index.d.ts +3 -3
  72. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  73. package/dist/types/features/logging/aggregate/index.d.ts +3 -3
  74. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  75. package/dist/types/features/logging/shared/utils.d.ts +2 -2
  76. package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
  77. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -4
  78. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  79. package/dist/types/features/session_replay/aggregate/index.d.ts +13 -4
  80. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  81. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -0
  82. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  83. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  84. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  85. package/dist/types/features/utils/aggregate-base.d.ts +13 -5
  86. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  87. package/dist/types/loaders/api/addPageAction.d.ts +1 -1
  88. package/dist/types/loaders/api/addPageAction.d.ts.map +1 -1
  89. package/dist/types/loaders/api/log.d.ts +1 -1
  90. package/dist/types/loaders/api/log.d.ts.map +1 -1
  91. package/dist/types/loaders/api/noticeError.d.ts +1 -1
  92. package/dist/types/loaders/api/noticeError.d.ts.map +1 -1
  93. package/dist/types/loaders/api/register-api-types.d.ts +4 -4
  94. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  95. package/dist/types/loaders/api/register.d.ts +8 -1
  96. package/dist/types/loaders/api/register.d.ts.map +1 -1
  97. package/package.json +2 -2
  98. package/src/common/config/init-types.js +1 -1
  99. package/src/common/config/init.js +3 -1
  100. package/src/common/config/runtime.js +1 -1
  101. package/src/common/constants/agent-constants.js +10 -0
  102. package/src/common/drain/drain.js +1 -1
  103. package/src/common/harvest/harvester.js +27 -32
  104. package/src/common/util/mfe.js +35 -0
  105. package/src/features/ajax/aggregate/index.js +7 -1
  106. package/src/features/generic_events/aggregate/index.js +9 -8
  107. package/src/features/generic_events/constants.js +3 -1
  108. package/src/features/generic_events/instrument/index.js +44 -35
  109. package/src/features/jserrors/aggregate/index.js +17 -17
  110. package/src/features/logging/aggregate/index.js +20 -13
  111. package/src/features/logging/shared/utils.js +3 -3
  112. package/src/features/page_view_event/aggregate/index.js +3 -28
  113. package/src/features/session_replay/aggregate/index.js +12 -10
  114. package/src/features/session_replay/shared/recorder.js +3 -2
  115. package/src/features/session_trace/aggregate/index.js +0 -2
  116. package/src/features/soft_navigations/aggregate/index.js +1 -2
  117. package/src/features/utils/aggregate-base.js +47 -42
  118. package/src/loaders/api/addPageAction.js +2 -2
  119. package/src/loaders/api/log.js +2 -2
  120. package/src/loaders/api/noticeError.js +2 -2
  121. package/src/loaders/api/register-api-types.js +5 -5
  122. package/src/loaders/api/register.js +62 -89
  123. package/dist/cjs/common/util/target.js +0 -34
  124. package/dist/cjs/features/utils/entity-manager.js +0 -46
  125. package/dist/cjs/features/utils/event-store-manager.js +0 -174
  126. package/dist/cjs/loaders/api/register-api.js +0 -165
  127. package/dist/esm/common/util/target.js +0 -27
  128. package/dist/esm/features/utils/entity-manager.js +0 -39
  129. package/dist/esm/features/utils/event-store-manager.js +0 -166
  130. package/dist/esm/loaders/api/register-api.js +0 -159
  131. package/dist/types/common/util/target.d.ts +0 -18
  132. package/dist/types/common/util/target.d.ts.map +0 -1
  133. package/dist/types/features/utils/entity-manager.d.ts +0 -11
  134. package/dist/types/features/utils/entity-manager.d.ts.map +0 -1
  135. package/dist/types/features/utils/event-store-manager.d.ts +0 -85
  136. package/dist/types/features/utils/event-store-manager.d.ts.map +0 -1
  137. package/dist/types/loaders/api/register-api.d.ts +0 -14
  138. package/dist/types/loaders/api/register-api.d.ts.map +0 -1
  139. package/src/common/util/target.js +0 -27
  140. package/src/features/utils/entity-manager.js +0 -45
  141. package/src/features/utils/event-store-manager.js +0 -165
  142. 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,6 @@ 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';
22
20
  export class Aggregate extends AggregateBase {
23
21
  static featureName = CONSTANTS.FEATURE_NAME;
24
22
  constructor(agentRef) {
@@ -27,9 +25,6 @@ export class Aggregate extends AggregateBase {
27
25
  this.firstByteToWindowLoad = 0; // our "frontend" duration
28
26
  this.firstByteToDomContent = 0; // our "dom processing" duration
29
27
 
30
- registerHandler('send-rum', (customAttibutes, target) => {
31
- this.sendRum(customAttibutes, target);
32
- }, this.featureName, this.ee);
33
28
  if (!isValid(agentRef.info)) {
34
29
  this.ee.abort();
35
30
  return warn(43);
@@ -57,7 +52,7 @@ export class Aggregate extends AggregateBase {
57
52
  *
58
53
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
59
54
  * @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 }
55
+ * @param {*} target The target to harvest to
61
56
  */
62
57
  sendRum(customAttributes = this.agentRef.info.jsAttributes, target = {
63
58
  licenseKey: this.agentRef.info.licenseKey,
@@ -120,7 +115,7 @@ export class Aggregate extends AggregateBase {
120
115
  this.rumStartTime = now();
121
116
  this.agentRef.runtime.harvester.triggerHarvestFor(this, {
122
117
  directSend: {
123
- targetApp: target,
118
+ target,
124
119
  payload: {
125
120
  qs: queryParameters,
126
121
  body
@@ -133,8 +128,7 @@ export class Aggregate extends AggregateBase {
133
128
  postHarvestCleanup({
134
129
  status,
135
130
  responseText,
136
- xhr,
137
- targetApp
131
+ xhr
138
132
  }) {
139
133
  const rumEndTime = now();
140
134
  let app, flags;
@@ -143,14 +137,10 @@ export class Aggregate extends AggregateBase {
143
137
  app,
144
138
  ...flags
145
139
  } = JSON.parse(responseText));
146
- this.processEntities(app.agents, targetApp);
147
140
  } catch (error) {
148
141
  // wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
149
142
  warn(53, error);
150
143
  }
151
-
152
- /** Only run agent-wide side-effects if the harvest was for the main agent */
153
- if (!isContainerAgentTarget(targetApp, this.agentRef)) return;
154
144
  if (status >= 400 || status === 0) {
155
145
  warn(18, status);
156
146
  // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
@@ -181,24 +171,4 @@ export class Aggregate extends AggregateBase {
181
171
  this.agentRef.runtime.harvester.startTimer();
182
172
  activateFeatures(flags, this.agentRef);
183
173
  }
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
174
  }
@@ -36,6 +36,8 @@ export class Aggregate extends AggregateBase {
36
36
  this.gzipper = undefined;
37
37
  /** populated with the u8 string lib async */
38
38
  this.u8 = undefined;
39
+ /** flips to false if the compressor libraries cannot import */
40
+ this.shouldCompress = true;
39
41
 
40
42
  /** set by BCS response */
41
43
  this.entitled = false;
@@ -207,23 +209,22 @@ export class Aggregate extends AggregateBase {
207
209
  this.gzipper = gzipSync;
208
210
  this.u8 = strToU8;
209
211
  } catch (err) {
210
- // compressor failed to load, but we can still record without compression as a last ditch effort
212
+ this.shouldCompress = false;
213
+ // compressor failed to load, but we can still try to record without compression as a last ditch effort
211
214
  }
212
215
  }
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;
216
+ makeHarvestPayload() {
217
+ if (this.mode !== MODE.FULL || this.blocked) return; // harvests should only be made in FULL mode, and not if the feature is blocked
218
+ if (this.shouldCompress && !this.gzipper) return; // if compression is enabled, but the libraries have not loaded, wait for them to load
219
+ 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
220
+
220
221
  const recorderEvents = this.recorder.getEvents();
221
222
  // get the event type and use that to trigger another harvest if needed
222
223
  if (!recorderEvents.events.length) return;
223
224
  const payload = this.getHarvestContents(recorderEvents);
224
225
  if (!payload.body.length) {
225
226
  this.recorder.clearBuffer();
226
- return [payloadOutput];
227
+ return;
227
228
  }
228
229
  this.reportSupportabilityMetric('SessionReplay/Harvest/Attempts');
229
230
  let len = 0;
@@ -238,19 +239,18 @@ export class Aggregate extends AggregateBase {
238
239
  }
239
240
  if (len > MAX_PAYLOAD_SIZE) {
240
241
  this.abort(ABORT_REASONS.TOO_BIG, len);
241
- return [payloadOutput];
242
+ return;
242
243
  }
244
+
243
245
  // TODO -- Gracefully handle the buffer for retries.
244
246
  if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
245
247
  sessionReplaySentFirstChunk: true
246
248
  });
247
249
  this.recorder.clearBuffer();
248
- if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this);
249
- payloadOutput.payload = payload;
250
250
  if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
251
251
  warn(59, JSON.stringify(this.agentRef.runtime.session.state));
252
252
  }
253
- return [payloadOutput];
253
+ return payload;
254
254
  }
255
255
 
256
256
  /**
@@ -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