@newrelic/browser-agent 1.271.0 → 1.273.0

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 (123) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/common/aggregate/aggregator.js +23 -30
  3. package/dist/cjs/common/aggregate/event-aggregator.js +84 -0
  4. package/dist/cjs/common/config/init.js +8 -4
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/harvest/harvest-scheduler.js +1 -1
  8. package/dist/cjs/common/harvest/harvest.js +1 -5
  9. package/dist/cjs/common/harvest/types.js +0 -1
  10. package/dist/cjs/features/ajax/aggregate/index.js +52 -62
  11. package/dist/cjs/features/generic_events/aggregate/index.js +57 -36
  12. package/dist/cjs/features/generic_events/instrument/index.js +1 -1
  13. package/dist/cjs/features/jserrors/aggregate/index.js +23 -69
  14. package/dist/cjs/features/logging/aggregate/index.js +52 -59
  15. package/dist/cjs/features/metrics/aggregate/index.js +8 -5
  16. package/dist/cjs/features/page_view_timing/aggregate/index.js +8 -25
  17. package/dist/cjs/features/session_replay/aggregate/index.js +11 -10
  18. package/dist/cjs/features/session_replay/shared/recorder-events.js +2 -2
  19. package/dist/cjs/features/session_trace/aggregate/index.js +77 -88
  20. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +22 -13
  21. package/dist/cjs/features/soft_navigations/aggregate/index.js +10 -20
  22. package/dist/cjs/features/soft_navigations/instrument/index.js +5 -9
  23. package/dist/cjs/features/spa/aggregate/index.js +10 -26
  24. package/dist/cjs/features/utils/aggregate-base.js +37 -0
  25. package/dist/cjs/features/utils/event-buffer.js +36 -87
  26. package/dist/cjs/features/utils/instrument-base.js +3 -3
  27. package/dist/cjs/loaders/features/features.js +13 -1
  28. package/dist/esm/common/aggregate/aggregator.js +23 -30
  29. package/dist/esm/common/aggregate/event-aggregator.js +78 -0
  30. package/dist/esm/common/config/init.js +8 -4
  31. package/dist/esm/common/constants/env.cdn.js +1 -1
  32. package/dist/esm/common/constants/env.npm.js +1 -1
  33. package/dist/esm/common/harvest/harvest-scheduler.js +1 -1
  34. package/dist/esm/common/harvest/harvest.js +1 -5
  35. package/dist/esm/common/harvest/types.js +0 -1
  36. package/dist/esm/features/ajax/aggregate/index.js +53 -62
  37. package/dist/esm/features/generic_events/aggregate/index.js +57 -36
  38. package/dist/esm/features/generic_events/instrument/index.js +1 -1
  39. package/dist/esm/features/jserrors/aggregate/index.js +24 -70
  40. package/dist/esm/features/logging/aggregate/index.js +52 -59
  41. package/dist/esm/features/metrics/aggregate/index.js +8 -5
  42. package/dist/esm/features/page_view_timing/aggregate/index.js +9 -26
  43. package/dist/esm/features/session_replay/aggregate/index.js +12 -11
  44. package/dist/esm/features/session_replay/shared/recorder-events.js +2 -2
  45. package/dist/esm/features/session_trace/aggregate/index.js +77 -88
  46. package/dist/esm/features/session_trace/aggregate/trace/storage.js +22 -13
  47. package/dist/esm/features/soft_navigations/aggregate/index.js +11 -21
  48. package/dist/esm/features/soft_navigations/instrument/index.js +5 -9
  49. package/dist/esm/features/spa/aggregate/index.js +11 -27
  50. package/dist/esm/features/utils/aggregate-base.js +37 -0
  51. package/dist/esm/features/utils/event-buffer.js +36 -88
  52. package/dist/esm/features/utils/instrument-base.js +3 -3
  53. package/dist/esm/loaders/features/features.js +12 -0
  54. package/dist/types/common/aggregate/aggregator.d.ts +4 -6
  55. package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
  56. package/dist/types/common/aggregate/event-aggregator.d.ts +26 -0
  57. package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -0
  58. package/dist/types/common/config/init.d.ts.map +1 -1
  59. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  60. package/dist/types/common/harvest/types.d.ts +1 -4
  61. package/dist/types/common/harvest/types.d.ts.map +1 -1
  62. package/dist/types/features/ajax/aggregate/index.d.ts +2 -10
  63. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  64. package/dist/types/features/generic_events/aggregate/index.d.ts +5 -11
  65. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  66. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  67. package/dist/types/features/jserrors/aggregate/index.d.ts +4 -7
  68. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/logging/aggregate/index.d.ts +10 -28
  70. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  72. package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -9
  73. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  74. package/dist/types/features/session_replay/aggregate/index.d.ts +3 -4
  75. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  76. package/dist/types/features/session_replay/shared/recorder-events.d.ts +1 -1
  77. package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
  78. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -1
  79. package/dist/types/features/session_trace/aggregate/index.d.ts +17 -19
  80. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  81. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +10 -6
  82. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  83. package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -9
  84. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  85. package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -1
  86. package/dist/types/features/spa/aggregate/index.d.ts +2 -3
  87. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/utils/aggregate-base.d.ts +14 -0
  89. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  90. package/dist/types/features/utils/event-buffer.d.ts +19 -56
  91. package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
  92. package/dist/types/loaders/features/features.d.ts +3 -0
  93. package/dist/types/loaders/features/features.d.ts.map +1 -1
  94. package/package.json +3 -2
  95. package/src/common/aggregate/aggregator.js +22 -32
  96. package/src/common/aggregate/event-aggregator.js +76 -0
  97. package/src/common/config/init.js +6 -2
  98. package/src/common/harvest/harvest-scheduler.js +1 -1
  99. package/src/common/harvest/harvest.js +1 -5
  100. package/src/common/harvest/types.js +0 -1
  101. package/src/features/ajax/aggregate/index.js +60 -67
  102. package/src/features/generic_events/aggregate/index.js +48 -38
  103. package/src/features/generic_events/instrument/index.js +2 -0
  104. package/src/features/jserrors/aggregate/index.js +21 -77
  105. package/src/features/logging/aggregate/index.js +46 -60
  106. package/src/features/metrics/aggregate/index.js +6 -4
  107. package/src/features/page_view_timing/aggregate/index.js +9 -30
  108. package/src/features/session_replay/aggregate/index.js +10 -14
  109. package/src/features/session_replay/shared/recorder-events.js +2 -2
  110. package/src/features/session_trace/aggregate/index.js +64 -73
  111. package/src/features/session_trace/aggregate/trace/storage.js +25 -14
  112. package/src/features/soft_navigations/aggregate/index.js +11 -22
  113. package/src/features/soft_navigations/instrument/index.js +6 -9
  114. package/src/features/spa/aggregate/index.js +12 -27
  115. package/src/features/utils/aggregate-base.js +39 -0
  116. package/src/features/utils/event-buffer.js +36 -83
  117. package/src/features/utils/instrument-base.js +3 -3
  118. package/src/loaders/features/features.js +13 -0
  119. package/dist/cjs/features/ajax/aggregate/chunk.js +0 -51
  120. package/dist/esm/features/ajax/aggregate/chunk.js +0 -44
  121. package/dist/types/features/ajax/aggregate/chunk.d.ts +0 -8
  122. package/dist/types/features/ajax/aggregate/chunk.d.ts.map +0 -1
  123. package/src/features/ajax/aggregate/chunk.js +0 -52
@@ -12,9 +12,9 @@ import { warn } from '../../../common/util/console';
12
12
  import { now } from '../../../common/timing/now';
13
13
  import { registerHandler } from '../../../common/event-emitter/register-handler';
14
14
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
15
- import { EventBuffer } from '../../utils/event-buffer';
16
15
  import { applyFnToProps } from '../../../common/util/traverse';
17
16
  import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
17
+ import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
18
18
  import { UserActionsAggregator } from './user-actions/user-actions-aggregator';
19
19
  import { isIFrameWindow } from '../../../common/dom/iframe';
20
20
  export class Aggregate extends AggregateBase {
@@ -24,14 +24,12 @@ export class Aggregate extends AggregateBase {
24
24
  this.eventsPerHarvest = 1000;
25
25
  this.harvestTimeSeconds = agentRef.init.generic_events.harvestTimeSeconds;
26
26
  this.referrerUrl = isBrowserScope && document.referrer ? cleanURL(document.referrer) : undefined;
27
- this.events = new EventBuffer();
28
27
  this.waitForFlags(['ins']).then(([ins]) => {
29
28
  if (!ins) {
30
29
  this.blocked = true;
31
30
  this.deregisterDrain();
32
31
  return;
33
32
  }
34
- const preHarvestMethods = [];
35
33
  if (agentRef.init.page_action.enabled) {
36
34
  registerHandler('api-addPageAction', (timestamp, name, attributes) => {
37
35
  this.addEvent({
@@ -48,9 +46,10 @@ export class Aggregate extends AggregateBase {
48
46
  });
49
47
  }, this.featureName, this.ee);
50
48
  }
49
+ let addUserAction;
51
50
  if (isBrowserScope && agentRef.init.user_actions.enabled) {
52
51
  this.userActionAggregator = new UserActionsAggregator();
53
- this.addUserAction = aggregatedUserAction => {
52
+ addUserAction = aggregatedUserAction => {
54
53
  try {
55
54
  /** The aggregator process only returns an event when it is "done" aggregating -
56
55
  * so we still need to validate that an event was given to this method before we try to add */
@@ -92,21 +91,53 @@ export class Aggregate extends AggregateBase {
92
91
  };
93
92
  registerHandler('ua', evt => {
94
93
  /** the processor will return the previously aggregated event if it has been completed by processing the current event */
95
- this.addUserAction(this.userActionAggregator.process(evt));
94
+ addUserAction(this.userActionAggregator.process(evt));
96
95
  }, this.featureName, this.ee);
97
- preHarvestMethods.push((options = {}) => {
98
- /** send whatever UserActions have been aggregated up to this point
99
- * if we are in a final harvest. By accessing the aggregationEvent, the aggregation is then force-cleared */
100
- if (options.isFinalHarvest) this.addUserAction(this.userActionAggregator.aggregationEvent);
101
- });
102
96
  }
103
- this.harvestScheduler = new HarvestScheduler('ins', {
104
- onFinished: (...args) => this.onHarvestFinished(...args)
97
+
98
+ /**
99
+ * is it worth complicating the agent and skipping the POs for single repeating queries? maybe,
100
+ * but right now it was less desirable simply because it is a nice benefit of populating the event buffer
101
+ * immediately as events happen for payload evaluation purposes and that becomes a little more chaotic
102
+ * with an arbitrary query method. note: eventTypes: [...types] does not support the 'buffered' flag so we have
103
+ * to create up to two PO's here.
104
+ */
105
+ const performanceTypesToCapture = [...(agentRef.init.performance.capture_marks ? ['mark'] : []), ...(agentRef.init.performance.capture_measures ? ['measure'] : [])];
106
+ if (performanceTypesToCapture.length) {
107
+ try {
108
+ performanceTypesToCapture.forEach(type => {
109
+ if (PerformanceObserver.supportedEntryTypes.includes(type)) {
110
+ const observer = new PerformanceObserver(list => {
111
+ list.getEntries().forEach(entry => {
112
+ try {
113
+ this.addEvent({
114
+ eventType: 'BrowserPerformance',
115
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
116
+ entryName: entry.name,
117
+ entryDuration: entry.duration,
118
+ entryType: type,
119
+ ...(entry.detail && {
120
+ entryDetail: entry.detail
121
+ })
122
+ });
123
+ } catch (err) {}
124
+ });
125
+ });
126
+ observer.observe({
127
+ buffered: true,
128
+ type
129
+ });
130
+ }
131
+ });
132
+ } catch (err) {
133
+ // Something failed in our set up, likely the browser does not support PO's... do nothing
134
+ }
135
+ }
136
+ this.harvestScheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
137
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
138
+ onUnload: () => addUserAction?.(this.userActionAggregator.aggregationEvent)
105
139
  }, this);
106
- this.harvestScheduler.harvest.on('ins', (...args) => {
107
- preHarvestMethods.forEach(fn => fn(...args));
108
- return this.onHarvestStarted(...args);
109
- });
140
+ this.harvestScheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], options => this.makeHarvestPayload(options.retry));
110
141
  this.harvestScheduler.startTimer(this.harvestTimeSeconds, 0);
111
142
  this.drain();
112
143
  });
@@ -153,30 +184,20 @@ export class Aggregate extends AggregateBase {
153
184
  this.events.add(eventAttributes);
154
185
  this.checkEventLimits();
155
186
  }
156
- onHarvestStarted(options) {
157
- const {
158
- userAttributes,
159
- atts
160
- } = this.agentRef.info;
161
- if (!this.events.hasData) return;
162
- var payload = {
163
- qs: {
164
- ua: userAttributes,
165
- at: atts
166
- },
167
- body: applyFnToProps({
168
- ins: this.events.buffer
169
- }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
170
- };
171
- if (options.retry) this.events.hold();else this.events.clear();
172
- return payload;
187
+ serializer(eventBuffer) {
188
+ return applyFnToProps({
189
+ ins: eventBuffer
190
+ }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
173
191
  }
174
- onHarvestFinished(result) {
175
- if (result && result?.sent && result?.retry && this.events.held.hasData) this.events.unhold();else this.events.held.clear();
192
+ queryStringsBuilder() {
193
+ return {
194
+ ua: this.agentRef.info.userAttributes,
195
+ at: this.agentRef.info.atts
196
+ };
176
197
  }
177
198
  checkEventLimits() {
178
199
  // check if we've reached any harvest limits...
179
- if (this.events.bytes > IDEAL_PAYLOAD_SIZE) {
200
+ if (this.events.byteSize() > IDEAL_PAYLOAD_SIZE) {
180
201
  this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['GenericEvents/Harvest/Max/Seen']);
181
202
  this.harvestScheduler.runHarvest();
182
203
  }
@@ -11,7 +11,7 @@ export class Instrument extends InstrumentBase {
11
11
  static featureName = FEATURE_NAME;
12
12
  constructor(agentRef, auto = true) {
13
13
  super(agentRef, FEATURE_NAME, auto);
14
- const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.user_actions.enabled
14
+ const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.user_actions.enabled
15
15
  // other future generic event source configs to go here, like M&Ms, PageResouce, etc.
16
16
  ];
17
17
  if (isBrowserScope && agentRef.init.user_actions.enabled) {
@@ -13,9 +13,8 @@ import { stringify } from '../../../common/util/stringify';
13
13
  import { handle } from '../../../common/event-emitter/handle';
14
14
  import { globalScope } from '../../../common/constants/runtime';
15
15
  import { FEATURE_NAME } from '../constants';
16
- import { FEATURE_NAMES } from '../../../loaders/features/features';
16
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
17
17
  import { AggregateBase } from '../../utils/aggregate-base';
18
- import { getNREUMInitializedAgent } from '../../../common/window/nreum';
19
18
  import { now } from '../../../common/timing/now';
20
19
  import { applyFnToProps } from '../../../common/util/traverse';
21
20
  import { evaluateInternalError } from './internal-errors';
@@ -33,7 +32,6 @@ export class Aggregate extends AggregateBase {
33
32
  this.observedAt = {};
34
33
  this.pageviewReported = {};
35
34
  this.bufferedErrorsUnderSpa = {};
36
- this.currentBody = undefined;
37
35
  this.errorOnPage = false;
38
36
 
39
37
  // this will need to change to match whatever ee we use in the instrument
@@ -43,14 +41,19 @@ export class Aggregate extends AggregateBase {
43
41
  register('softNavFlush', (interactionId, wasFinished, softNavAttrs) => this.onSoftNavNotification(interactionId, wasFinished, softNavAttrs), this.featureName, this.ee); // when an ixn is done or cancelled
44
42
 
45
43
  const harvestTimeSeconds = agentRef.init.jserrors.harvestTimeSeconds || 10;
44
+ const aggregatorTypes = ['err', 'ierr', 'xhr']; // the types in EventAggregator this feature cares about
46
45
 
47
46
  // 0 == off, 1 == on
48
47
  this.waitForFlags(['err']).then(([errFlag]) => {
49
48
  if (errFlag) {
50
- const scheduler = new HarvestScheduler('jserrors', {
51
- onFinished: (...args) => this.onHarvestFinished(...args)
49
+ const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
50
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry, {
51
+ aggregatorTypes
52
+ })
52
53
  }, this);
53
- scheduler.harvest.on('jserrors', (...args) => this.onHarvestStarted(...args));
54
+ scheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], options => this.makeHarvestPayload(options.retry, {
55
+ aggregatorTypes
56
+ }));
54
57
  scheduler.startTimer(harvestTimeSeconds);
55
58
  this.drain();
56
59
  } else {
@@ -59,49 +62,22 @@ export class Aggregate extends AggregateBase {
59
62
  }
60
63
  });
61
64
  }
62
- onHarvestStarted(options) {
63
- // this gets rid of dependency in AJAX module
64
- var body = applyFnToProps(this.agentRef.sharedAggregator.take(['err', 'ierr', 'xhr']), this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
65
- if (options.retry) {
66
- this.currentBody = body;
67
- }
68
- var payload = {
69
- body,
70
- qs: {}
71
- };
72
- var releaseIds = stringify(this.agentRef.runtime.releaseIds);
73
- if (releaseIds !== '{}') {
74
- payload.qs.ri = releaseIds;
75
- }
76
- if (body && body.err && body.err.length) {
77
- this.#runCrossFeatureChecks(body.err);
65
+ serializer(aggregatorTypeToBucketsMap) {
66
+ return applyFnToProps(aggregatorTypeToBucketsMap, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
67
+ }
68
+ queryStringsBuilder(aggregatorTakeReturnedData) {
69
+ const qs = {};
70
+ const releaseIds = stringify(this.agentRef.runtime.releaseIds);
71
+ if (releaseIds !== '{}') qs.ri = releaseIds;
72
+ if (aggregatorTakeReturnedData?.err?.length) {
78
73
  if (!this.errorOnPage) {
79
- payload.qs.pve = '1';
74
+ qs.pve = '1';
80
75
  this.errorOnPage = true;
81
76
  }
77
+ // For assurance, erase any `hasReplay` flag from all errors if replay is not recording, not-yet imported, or not running at all.
78
+ if (!this.agentRef.features?.[FEATURE_NAMES.sessionReplay]?.featAggregate?.replayIsActive()) aggregatorTakeReturnedData.err.forEach(error => delete error.params.hasReplay);
82
79
  }
83
- return payload;
84
- }
85
- onHarvestFinished(result) {
86
- if (result.retry && this.currentBody) {
87
- Object.entries(this.currentBody || {}).forEach(([key, value]) => {
88
- for (var i = 0; i < value.length; i++) {
89
- var bucket = value[i];
90
- var name = this.getBucketName(key, bucket.params, bucket.custom);
91
- this.agentRef.sharedAggregator.merge(key, name, bucket.metrics, bucket.params, bucket.custom);
92
- }
93
- });
94
- this.currentBody = null;
95
- }
96
- }
97
- nameHash(params) {
98
- return stringHashCode("".concat(params.exceptionClass, "_").concat(params.message, "_").concat(params.stack_trace || params.browser_stack_hash));
99
- }
100
- getBucketName(objType, params, customParams) {
101
- if (objType === 'xhr') {
102
- return stringHashCode(stringify(params)) + ':' + stringHashCode(stringify(customParams));
103
- }
104
- return this.nameHash(params) + ':' + stringHashCode(stringify(customParams));
80
+ return qs;
105
81
  }
106
82
 
107
83
  /**
@@ -208,7 +184,7 @@ export class Aggregate extends AggregateBase {
208
184
  params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
209
185
  params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
210
186
  }
211
- const softNavInUse = Boolean(getNREUMInitializedAgent(this.agentIdentifier)?.features[FEATURE_NAMES.softNav]);
187
+ const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav]);
212
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.
213
189
  // They each will also tack on their respective properties to the params object as part of the decision flow.
214
190
  if (softNavInUse) handle('jserror', [params, time], undefined, FEATURE_NAMES.softNav, this.ee);else handle('spa-jserror', jsErrorEvent, undefined, FEATURE_NAMES.spa, this.ee);
@@ -243,7 +219,7 @@ export class Aggregate extends AggregateBase {
243
219
 
244
220
  const jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
245
221
  const aggregateHash = bucketHash + ':' + jsAttributesHash;
246
- this.agentRef.sharedAggregator.store(type, aggregateHash, params, newMetrics, allCustomAttrs);
222
+ this.events.add(type, aggregateHash, params, newMetrics, allCustomAttrs);
247
223
  function setCustom(key, val) {
248
224
  allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
249
225
  }
@@ -267,7 +243,7 @@ export class Aggregate extends AggregateBase {
267
243
  var hash = wasSaved ? item[1] + interaction.root.attrs.id : item[1];
268
244
  var jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
269
245
  var aggregateHash = hash + ':' + jsAttributesHash;
270
- this.agentRef.sharedAggregator.store(item[0], aggregateHash, params, item[3], allCustomAttrs);
246
+ this.events.add(item[0], aggregateHash, params, item[3], allCustomAttrs);
271
247
  function setCustom([key, val]) {
272
248
  allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
273
249
  }
@@ -280,26 +256,4 @@ export class Aggregate extends AggregateBase {
280
256
  );
281
257
  delete this.bufferedErrorsUnderSpa[interactionId]; // wipe the list of jserrors so they aren't duplicated by another call to the same id
282
258
  }
283
-
284
- /**
285
- * Dispatches a cross-feature communication event to allow other
286
- * features to provide flags and data that can be used to mutation
287
- * to the payload and to allow features to know about a feature
288
- * harvest happening.
289
- * @param {any[]} errors Array of errors from the payload body
290
- */
291
- #runCrossFeatureChecks(errors) {
292
- const errorHashes = errors.map(error => error.params.stackHash);
293
- const crossFeatureData = {
294
- errorHashes
295
- };
296
- this.ee.emit("cfc.".concat(this.featureName), [crossFeatureData]);
297
- let hasReplayFlag = errors.find(err => err.params.hasReplay);
298
- if (hasReplayFlag && !crossFeatureData.hasReplay) {
299
- // Some errors have `hasReplay` and a replay is not being recorded
300
- errors.forEach(error => {
301
- delete error.params.hasReplay;
302
- });
303
- }
304
- }
305
259
  }
@@ -10,20 +10,17 @@ import { Log } from '../shared/log';
10
10
  import { isValidLogLevel } from '../shared/utils';
11
11
  import { applyFnToProps } from '../../../common/util/traverse';
12
12
  import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
13
- import { EventBuffer } from '../../utils/event-buffer';
13
+ import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
14
14
  export class Aggregate extends AggregateBase {
15
15
  static featureName = FEATURE_NAME;
16
16
  constructor(agentRef) {
17
17
  super(agentRef, FEATURE_NAME);
18
-
19
- /** held logs before sending */
20
- this.bufferedLogs = new EventBuffer();
21
18
  this.harvestTimeSeconds = agentRef.init.logging.harvestTimeSeconds;
22
19
  this.waitForFlags([]).then(() => {
23
- this.scheduler = new HarvestScheduler('browser/logs', {
24
- onFinished: this.onHarvestFinished.bind(this),
20
+ this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
21
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
25
22
  retryDelay: this.harvestTimeSeconds,
26
- getPayload: this.prepareHarvest.bind(this),
23
+ getPayload: options => this.makeHarvestPayload(options.retry),
27
24
  raw: true
28
25
  }, this);
29
26
  /** emitted by instrument class (wrapped loggers) or the api methods directly */
@@ -56,63 +53,59 @@ export class Aggregate extends AggregateBase {
56
53
  const log = new Log(Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp)), message, attributes, level);
57
54
  const logBytes = log.message.length + stringify(log.attributes).length + log.level.length + 10; // timestamp == 10 chars
58
55
 
59
- if (!this.bufferedLogs.canMerge(logBytes)) {
60
- if (this.bufferedLogs.hasData) {
61
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Early/Seen', this.bufferedLogs.bytes + logBytes]);
62
- this.scheduler.runHarvest({});
63
- if (logBytes < MAX_PAYLOAD_SIZE) this.bufferedLogs.add(log);
64
- } else {
65
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Failed/Seen', logBytes]);
66
- warn(31, log.message.slice(0, 25) + '...');
67
- }
56
+ const failToHarvestMessage = 'Logging/Harvest/Failed/Seen';
57
+ if (logBytes > MAX_PAYLOAD_SIZE) {
58
+ // cannot possibly send this, even with an empty buffer
59
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [failToHarvestMessage, logBytes]);
60
+ warn(31, log.message.slice(0, 25) + '...');
68
61
  return;
69
62
  }
70
- this.bufferedLogs.add(log);
63
+ if (this.events.wouldExceedMaxSize(logBytes)) {
64
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Early/Seen', this.events.bytes + logBytes]);
65
+ this.scheduler.runHarvest(); // force a harvest to try adding again
66
+ }
67
+ if (!this.events.add(log)) {
68
+ // still failed after a harvest attempt despite not being too large would mean harvest failed with options.retry
69
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [failToHarvestMessage, logBytes]);
70
+ warn(31, log.message.slice(0, 25) + '...');
71
+ }
71
72
  }
72
- prepareHarvest(options = {}) {
73
- if (this.blocked || !this.bufferedLogs.hasData) return;
74
- /** These attributes are evaluated and dropped at ingest processing time and do not get stored on NRDB */
75
- const unbilledAttributes = {
76
- 'instrumentation.provider': 'browser',
77
- 'instrumentation.version': this.agentRef.runtime.version,
78
- 'instrumentation.name': this.agentRef.runtime.loaderType
79
- };
80
- /** see https://source.datanerd.us/agents/rum-specs/blob/main/browser/Log for logging spec */
81
- const payload = {
82
- qs: {
83
- browser_monitoring_key: this.agentRef.info.licenseKey
84
- },
85
- body: [{
86
- common: {
87
- /** Attributes in the `common` section are added to `all` logs generated in the payload */
88
- attributes: {
89
- 'entity.guid': this.agentRef.runtime.appMetadata?.agents?.[0]?.entityGuid,
90
- // browser entity guid as provided from RUM response
91
- session: this.agentRef.runtime.session?.state.value || '0',
73
+ serializer(eventBuffer) {
74
+ const sessionEntity = this.agentRef.runtime.session;
75
+ return [{
76
+ common: {
77
+ /** Attributes in the `common` section are added to `all` logs generated in the payload */
78
+ attributes: {
79
+ 'entity.guid': this.agentRef.runtime.appMetadata?.agents?.[0]?.entityGuid,
80
+ // browser entity guid as provided from RUM response
81
+ ...(sessionEntity && {
82
+ session: sessionEntity.state.value || '0',
92
83
  // The session ID that we generate and keep across page loads
93
- hasReplay: this.agentRef.runtime.session?.state.sessionReplayMode === 1,
84
+ hasReplay: sessionEntity.state.sessionReplayMode === 1,
94
85
  // True if a session replay recording is running
95
- hasTrace: this.agentRef.runtime.session?.state.sessionTraceMode === 1,
96
- // True if a session trace recording is running
97
- ptid: this.agentRef.runtime.ptid,
98
- // page trace id
99
- appId: this.agentRef.info.applicationID,
100
- // Application ID from info object,
101
- standalone: Boolean(this.agentRef.info.sa),
102
- // copy paste (true) vs APM (false)
103
- agentVersion: this.agentRef.runtime.version,
104
- // browser agent version
105
- ...unbilledAttributes
106
- }
107
- },
108
- /** logs section contains individual unique log entries */
109
- logs: applyFnToProps(this.bufferedLogs.buffer, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
110
- }]
111
- };
112
- if (options.retry) this.bufferedLogs.hold();else this.bufferedLogs.clear();
113
- return payload;
86
+ hasTrace: sessionEntity.state.sessionTraceMode === 1 // True if a session trace recording is running
87
+ }),
88
+ ptid: this.agentRef.runtime.ptid,
89
+ // page trace id
90
+ appId: this.agentRef.info.applicationID,
91
+ // Application ID from info object,
92
+ standalone: Boolean(this.agentRef.info.sa),
93
+ // copy paste (true) vs APM (false)
94
+ agentVersion: this.agentRef.runtime.version,
95
+ // browser agent version
96
+ // The following 3 attributes are evaluated and dropped at ingest processing time and do not get stored on NRDB:
97
+ 'instrumentation.provider': 'browser',
98
+ 'instrumentation.version': this.agentRef.runtime.version,
99
+ 'instrumentation.name': this.agentRef.runtime.loaderType
100
+ }
101
+ },
102
+ /** logs section contains individual unique log entries */
103
+ logs: applyFnToProps(eventBuffer, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
104
+ }];
114
105
  }
115
- onHarvestFinished(result) {
116
- if (result.retry) this.bufferedLogs.unhold();else this.bufferedLogs.held.clear();
106
+ queryStringsBuilder() {
107
+ return {
108
+ browser_monitoring_key: this.agentRef.info.licenseKey
109
+ };
117
110
  }
118
111
  }
@@ -7,6 +7,7 @@ import { onDOMContentLoaded } from '../../../common/window/load';
7
7
  import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts';
8
8
  import { isBrowserScope, isWorkerScope } from '../../../common/constants/runtime';
9
9
  import { AggregateBase } from '../../utils/aggregate-base';
10
+ import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
10
11
  import { isIFrameWindow } from '../../../common/dom/iframe';
11
12
  // import { WEBSOCKET_TAG } from '../../../common/wrap/wrap-websocket'
12
13
  // import { handleWebsocketEvents } from './websocket-detection'
@@ -15,15 +16,17 @@ export class Aggregate extends AggregateBase {
15
16
  static featureName = FEATURE_NAME;
16
17
  constructor(agentRef) {
17
18
  super(agentRef, FEATURE_NAME);
19
+ const aggregatorTypes = ['cm', 'sm']; // the types in EventAggregator this feature cares about
20
+
18
21
  this.waitForFlags(['err']).then(([errFlag]) => {
19
22
  if (errFlag) {
20
23
  // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
21
- const scheduler = new HarvestScheduler('jserrors', {
24
+ const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
22
25
  onUnload: () => this.unload()
23
26
  }, this);
24
27
  // this is needed to ensure EoL is "on" and sent
25
- scheduler.harvest.on('jserrors', () => ({
26
- body: this.agentRef.sharedAggregator.take(['cm', 'sm'])
28
+ scheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], () => this.makeHarvestPayload(undefined, {
29
+ aggregatorTypes
27
30
  }));
28
31
  this.drain();
29
32
  } else {
@@ -44,7 +47,7 @@ export class Aggregate extends AggregateBase {
44
47
  const params = {
45
48
  name
46
49
  };
47
- this.agentRef.sharedAggregator.storeMetric(type, name, params, value);
50
+ this.events.addMetric(type, name, params, value);
48
51
  }
49
52
  storeEventMetrics(name, metrics) {
50
53
  if (this.blocked) return;
@@ -52,7 +55,7 @@ export class Aggregate extends AggregateBase {
52
55
  const params = {
53
56
  name
54
57
  };
55
- this.agentRef.sharedAggregator.store(type, name, params, metrics);
58
+ this.events.add(type, name, params, metrics);
56
59
  }
57
60
  singleChecks() {
58
61
  // report loaderType
@@ -8,7 +8,7 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
8
8
  import { registerHandler } from '../../../common/event-emitter/register-handler';
9
9
  import { handle } from '../../../common/event-emitter/handle';
10
10
  import { FEATURE_NAME } from '../constants';
11
- import { FEATURE_NAMES } from '../../../loaders/features/features';
11
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
12
12
  import { AggregateBase } from '../../utils/aggregate-base';
13
13
  import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift';
14
14
  import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint';
@@ -19,7 +19,6 @@ import { largestContentfulPaint } from '../../../common/vitals/largest-contentfu
19
19
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
20
20
  import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
21
21
  import { VITAL_NAMES } from '../../../common/vitals/constants';
22
- import { EventBuffer } from '../../utils/event-buffer';
23
22
  export class Aggregate extends AggregateBase {
24
23
  static featureName = FEATURE_NAME;
25
24
  #handleVitalMetric = ({
@@ -31,7 +30,6 @@ export class Aggregate extends AggregateBase {
31
30
  };
32
31
  constructor(agentRef) {
33
32
  super(agentRef, FEATURE_NAME);
34
- this.timings = new EventBuffer();
35
33
  this.curSessEndRecorded = false;
36
34
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
37
35
  // Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
@@ -63,9 +61,9 @@ export class Aggregate extends AggregateBase {
63
61
  this.addTiming(name, value * 1000, attrs);
64
62
  }, true); // CLS node should only reports on vis change rather than on every change
65
63
 
66
- const scheduler = new HarvestScheduler('events', {
67
- onFinished: (...args) => this.onHarvestFinished(...args),
68
- getPayload: (...args) => this.prepareHarvest(...args)
64
+ const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
65
+ onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
66
+ getPayload: options => this.makeHarvestPayload(options.retry)
69
67
  }, this);
70
68
  scheduler.startTimer(harvestTimeSeconds);
71
69
  this.drain();
@@ -98,16 +96,13 @@ export class Aggregate extends AggregateBase {
98
96
  if (name !== VITAL_NAMES.CUMULATIVE_LAYOUT_SHIFT && cumulativeLayoutShift.current.value >= 0) {
99
97
  attrs.cls = cumulativeLayoutShift.current.value;
100
98
  }
101
- this.timings.add({
99
+ this.events.add({
102
100
  name,
103
101
  value,
104
102
  attrs
105
103
  });
106
104
  handle('pvtAdded', [name, value, attrs], undefined, FEATURE_NAMES.sessionTrace, this.ee);
107
105
  }
108
- onHarvestFinished(result) {
109
- if (result.retry && this.timings.held.hasData) this.timings.unhold();else this.timings.held.clear();
110
- }
111
106
  appendGlobalCustomAttributes(timing) {
112
107
  var timingAttributes = timing.attrs || {};
113
108
  var reservedAttributes = ['size', 'eid', 'cls', 'type', 'fid', 'elTag', 'elUrl', 'net-type', 'net-etype', 'net-rtt', 'net-dlink'];
@@ -118,24 +113,12 @@ export class Aggregate extends AggregateBase {
118
113
  });
119
114
  }
120
115
 
121
- // serialize and return current timing data, clear and save current data for retry
122
- prepareHarvest(options) {
123
- if (!this.timings.hasData) return;
124
- var payload = this.getPayload(this.timings.buffer);
125
- if (options.retry) this.timings.hold();else this.timings.clear();
126
- return {
127
- body: {
128
- e: payload
129
- }
130
- };
131
- }
132
-
133
116
  // serialize array of timing data
134
- getPayload(data) {
117
+ serializer(eventBuffer) {
135
118
  var addString = getAddStringContext(this.agentIdentifier);
136
119
  var payload = 'bel.6;';
137
- for (var i = 0; i < data.length; i++) {
138
- var timing = data[i];
120
+ for (var i = 0; i < eventBuffer.length; i++) {
121
+ var timing = eventBuffer[i];
139
122
  payload += 'e,';
140
123
  payload += addString(timing.name) + ',';
141
124
  payload += nullable(timing.value, numeric, false) + ',';
@@ -144,7 +127,7 @@ export class Aggregate extends AggregateBase {
144
127
  if (attrParts && attrParts.length > 0) {
145
128
  payload += numeric(attrParts.length) + ';' + attrParts.join(';');
146
129
  }
147
- if (i + 1 < data.length) payload += ';';
130
+ if (i + 1 < eventBuffer.length) payload += ';';
148
131
  }
149
132
  return payload;
150
133
  }
@@ -16,7 +16,7 @@ import { warn } from '../../../common/util/console';
16
16
  import { globalScope } from '../../../common/constants/runtime';
17
17
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
18
18
  import { handle } from '../../../common/event-emitter/handle';
19
- import { FEATURE_NAMES } from '../../../loaders/features/features';
19
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
20
20
  import { RRWEB_VERSION } from "../../../common/constants/env.npm";
21
21
  import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants';
22
22
  import { stringify } from '../../../common/util/stringify';
@@ -50,9 +50,6 @@ export class Aggregate extends AggregateBase {
50
50
  this.recorder = args?.recorder;
51
51
  this.errorNoticed = args?.errorNoticed || false;
52
52
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
53
- this.ee.on("cfc.".concat(FEATURE_NAMES.jserrors), crossFeatureData => {
54
- crossFeatureData.hasReplay = !!(this.scheduler?.started && this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled);
55
- });
56
53
 
57
54
  // 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.
58
55
  this.ee.on(SESSION_EVENTS.RESET, () => {
@@ -78,10 +75,13 @@ export class Aggregate extends AggregateBase {
78
75
  });
79
76
 
80
77
  // Bespoke logic for blobs endpoint.
81
- this.scheduler = new HarvestScheduler('browser/blobs', {
82
- onFinished: this.onHarvestFinished.bind(this),
78
+ this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
79
+ onFinished: result => this.postHarvestCleanup(result),
83
80
  retryDelay: this.harvestTimeSeconds,
84
- getPayload: this.prepareHarvest.bind(this),
81
+ getPayload: ({
82
+ retry,
83
+ ...opts
84
+ }) => this.makeHarvestPayload(retry, opts),
85
85
  raw: true
86
86
  }, this);
87
87
  registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
@@ -130,6 +130,9 @@ export class Aggregate extends AggregateBase {
130
130
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
131
131
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
132
132
  }
133
+ replayIsActive() {
134
+ return Boolean(this.scheduler?.started && this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled);
135
+ }
133
136
  handleError(e) {
134
137
  if (this.recorder) this.recorder.currentBufferTarget.hasError = true;
135
138
  // run once
@@ -237,9 +240,7 @@ export class Aggregate extends AggregateBase {
237
240
  // compressor failed to load, but we can still record without compression as a last ditch effort
238
241
  }
239
242
  }
240
- prepareHarvest({
241
- opts
242
- } = {}) {
243
+ makeHarvestPayload(shouldRetryOnFail, opts) {
243
244
  if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return;
244
245
  const recorderEvents = this.recorder.getEvents();
245
246
  // get the event type and use that to trigger another harvest if needed
@@ -375,7 +376,7 @@ export class Aggregate extends AggregateBase {
375
376
  body: events
376
377
  };
377
378
  }
378
- onHarvestFinished(result) {
379
+ postHarvestCleanup(result) {
379
380
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
380
381
  if (result.status === 429) {
381
382
  this.abort(ABORT_REASONS.TOO_MANY);