@newrelic/browser-agent 1.302.0 → 1.303.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 (122) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/common/config/init-types.js +2 -0
  3. package/dist/cjs/common/config/init.js +3 -0
  4. package/dist/cjs/common/config/runtime.js +9 -0
  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/harvester.js +13 -9
  8. package/dist/cjs/common/harvest/types.js +0 -1
  9. package/dist/cjs/common/session/session-entity.js +4 -2
  10. package/dist/cjs/common/util/mfe.js +8 -5
  11. package/dist/cjs/common/wrap/wrap-promise.js +10 -5
  12. package/dist/cjs/features/generic_events/aggregate/index.js +4 -4
  13. package/dist/cjs/features/logging/aggregate/index.js +1 -2
  14. package/dist/cjs/features/page_view_event/aggregate/index.js +84 -22
  15. package/dist/cjs/features/page_view_event/instrument/index.js +0 -4
  16. package/dist/cjs/features/session_replay/aggregate/index.js +3 -2
  17. package/dist/cjs/features/session_replay/constants.js +2 -6
  18. package/dist/cjs/features/session_replay/instrument/index.js +3 -2
  19. package/dist/cjs/features/utils/agent-session.js +5 -0
  20. package/dist/cjs/features/utils/instrument-base.js +7 -8
  21. package/dist/cjs/interfaces/registered-entity.js +21 -0
  22. package/dist/cjs/loaders/agent.js +2 -0
  23. package/dist/cjs/loaders/api/consent.js +37 -0
  24. package/dist/cjs/loaders/api/constants.js +3 -2
  25. package/dist/cjs/loaders/api/measure.js +36 -35
  26. package/dist/cjs/loaders/api/recordCustomEvent.js +5 -3
  27. package/dist/cjs/loaders/api/register-api-types.js +14 -10
  28. package/dist/cjs/loaders/api/register.js +25 -10
  29. package/dist/cjs/loaders/api-base.js +14 -7
  30. package/dist/esm/common/config/init-types.js +2 -0
  31. package/dist/esm/common/config/init.js +3 -0
  32. package/dist/esm/common/config/runtime.js +9 -0
  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/harvest/harvester.js +13 -9
  36. package/dist/esm/common/harvest/types.js +0 -1
  37. package/dist/esm/common/session/session-entity.js +4 -2
  38. package/dist/esm/common/util/mfe.js +7 -5
  39. package/dist/esm/common/wrap/wrap-promise.js +10 -5
  40. package/dist/esm/features/generic_events/aggregate/index.js +4 -4
  41. package/dist/esm/features/logging/aggregate/index.js +1 -2
  42. package/dist/esm/features/page_view_event/aggregate/index.js +84 -22
  43. package/dist/esm/features/page_view_event/instrument/index.js +0 -4
  44. package/dist/esm/features/session_replay/aggregate/index.js +4 -3
  45. package/dist/esm/features/session_replay/constants.js +1 -5
  46. package/dist/esm/features/session_replay/instrument/index.js +4 -3
  47. package/dist/esm/features/utils/agent-session.js +5 -0
  48. package/dist/esm/features/utils/instrument-base.js +7 -8
  49. package/dist/esm/interfaces/registered-entity.js +21 -0
  50. package/dist/esm/loaders/agent.js +2 -0
  51. package/dist/esm/loaders/api/consent.js +30 -0
  52. package/dist/esm/loaders/api/constants.js +2 -1
  53. package/dist/esm/loaders/api/measure.js +35 -35
  54. package/dist/esm/loaders/api/recordCustomEvent.js +4 -3
  55. package/dist/esm/loaders/api/register-api-types.js +14 -10
  56. package/dist/esm/loaders/api/register.js +26 -10
  57. package/dist/esm/loaders/api-base.js +15 -8
  58. package/dist/tsconfig.tsbuildinfo +1 -1
  59. package/dist/types/common/config/init-types.d.ts +6 -0
  60. package/dist/types/common/config/init.d.ts.map +1 -1
  61. package/dist/types/common/config/runtime.d.ts.map +1 -1
  62. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  63. package/dist/types/common/harvest/types.d.ts +0 -2
  64. package/dist/types/common/harvest/types.d.ts.map +1 -1
  65. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  66. package/dist/types/common/util/mfe.d.ts +3 -0
  67. package/dist/types/common/util/mfe.d.ts.map +1 -1
  68. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  69. package/dist/types/features/page_view_event/aggregate/index.d.ts +22 -3
  70. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  72. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  73. package/dist/types/features/session_replay/constants.d.ts +1 -5
  74. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  75. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  76. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  77. package/dist/types/features/utils/instrument-base.d.ts +1 -0
  78. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  79. package/dist/types/interfaces/registered-entity.d.ts +25 -0
  80. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  81. package/dist/types/loaders/agent.d.ts.map +1 -1
  82. package/dist/types/loaders/api/consent.d.ts +2 -0
  83. package/dist/types/loaders/api/consent.d.ts.map +1 -0
  84. package/dist/types/loaders/api/constants.d.ts +1 -0
  85. package/dist/types/loaders/api/constants.d.ts.map +1 -1
  86. package/dist/types/loaders/api/measure.d.ts +3 -0
  87. package/dist/types/loaders/api/measure.d.ts.map +1 -1
  88. package/dist/types/loaders/api/recordCustomEvent.d.ts +1 -0
  89. package/dist/types/loaders/api/recordCustomEvent.d.ts.map +1 -1
  90. package/dist/types/loaders/api/register-api-types.d.ts +43 -12
  91. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  92. package/dist/types/loaders/api/register.d.ts +0 -16
  93. package/dist/types/loaders/api/register.d.ts.map +1 -1
  94. package/dist/types/loaders/api-base.d.ts +20 -15
  95. package/dist/types/loaders/api-base.d.ts.map +1 -1
  96. package/package.json +4 -4
  97. package/src/common/config/init-types.js +2 -0
  98. package/src/common/config/init.js +1 -0
  99. package/src/common/config/runtime.js +10 -0
  100. package/src/common/harvest/harvester.js +11 -8
  101. package/src/common/harvest/types.js +0 -1
  102. package/src/common/session/session-entity.js +6 -2
  103. package/src/common/util/mfe.js +10 -4
  104. package/src/common/wrap/wrap-promise.js +16 -6
  105. package/src/features/generic_events/aggregate/index.js +4 -4
  106. package/src/features/logging/aggregate/index.js +1 -1
  107. package/src/features/page_view_event/aggregate/index.js +79 -15
  108. package/src/features/page_view_event/instrument/index.js +0 -4
  109. package/src/features/session_replay/aggregate/index.js +4 -3
  110. package/src/features/session_replay/constants.js +1 -5
  111. package/src/features/session_replay/instrument/index.js +4 -3
  112. package/src/features/utils/agent-session.js +4 -0
  113. package/src/features/utils/instrument-base.js +7 -9
  114. package/src/interfaces/registered-entity.js +21 -0
  115. package/src/loaders/agent.js +2 -0
  116. package/src/loaders/api/consent.js +31 -0
  117. package/src/loaders/api/constants.js +1 -0
  118. package/src/loaders/api/measure.js +34 -33
  119. package/src/loaders/api/recordCustomEvent.js +5 -3
  120. package/src/loaders/api/register-api-types.js +14 -10
  121. package/src/loaders/api/register.js +17 -10
  122. package/src/loaders/api-base.js +15 -8
@@ -17,14 +17,19 @@ 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 { send } from '../../../common/harvest/harvester';
21
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
22
+ import { getSubmitMethod } from '../../../common/util/submit-data';
20
23
  export class Aggregate extends AggregateBase {
21
24
  static featureName = CONSTANTS.FEATURE_NAME;
22
25
  constructor(agentRef) {
23
26
  super(agentRef, CONSTANTS.FEATURE_NAME);
27
+ this.sentRum = false; // flag to facilitate calling sendRum() once externally (by the consent API in agent-session.js)
28
+
24
29
  this.timeToFirstByte = 0;
25
30
  this.firstByteToWindowLoad = 0; // our "frontend" duration
26
31
  this.firstByteToDomContent = 0; // our "dom processing" duration
27
-
32
+ this.retries = 0;
28
33
  if (!isValid(agentRef.info)) {
29
34
  this.ee.abort();
30
35
  return warn(43);
@@ -52,12 +57,8 @@ export class Aggregate extends AggregateBase {
52
57
  *
53
58
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
54
59
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
55
- * @param {*} target The target to harvest to
56
60
  */
57
- sendRum(customAttributes = this.agentRef.info.jsAttributes, target = {
58
- licenseKey: this.agentRef.info.licenseKey,
59
- applicationID: this.agentRef.info.applicationID
60
- }) {
61
+ sendRum(customAttributes = this.agentRef.info.jsAttributes) {
61
62
  const info = this.agentRef.info;
62
63
  const measures = {};
63
64
  if (info.queueTime) measures.qt = info.queueTime;
@@ -108,27 +109,30 @@ export class Aggregate extends AggregateBase {
108
109
  }
109
110
  queryParameters.fp = firstPaint.current.value;
110
111
  queryParameters.fcp = firstContentfulPaint.current.value;
111
- const timeKeeper = this.agentRef.runtime.timeKeeper;
112
- if (timeKeeper?.ready) {
113
- queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(now()));
114
- }
115
- this.rumStartTime = now();
116
- this.agentRef.runtime.harvester.triggerHarvestFor(this, {
117
- directSend: {
118
- target,
119
- payload: {
120
- qs: queryParameters,
121
- body
122
- }
123
- },
124
- needResponse: true,
112
+ this.queryStringsBuilder = () => {
113
+ // this will be called by AggregateBase.makeHarvestPayload every time harvest is triggered to be qs
114
+ this.rumStartTime = now(); // this should be reset at the beginning of each RUM call for proper timeKeeper calculation in coordination with postHarvestCleanup
115
+ const timeKeeper = this.agentRef.runtime.timeKeeper;
116
+ if (timeKeeper?.ready) {
117
+ queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(this.rumStartTime));
118
+ }
119
+ return queryParameters;
120
+ };
121
+ this.events.add(body);
122
+ if (this.agentRef.runtime.harvester.triggerHarvestFor(this, {
125
123
  sendEmptyBody: true
126
- });
124
+ }).ranSend) this.sentRum = true;
125
+ }
126
+ serializer(eventBuffer) {
127
+ // this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
128
+ return eventBuffer[0];
127
129
  }
128
130
  postHarvestCleanup({
131
+ sent,
129
132
  status,
130
133
  responseText,
131
- xhr
134
+ xhr,
135
+ retry
132
136
  }) {
133
137
  const rumEndTime = now();
134
138
  let app, flags;
@@ -141,8 +145,65 @@ export class Aggregate extends AggregateBase {
141
145
  // wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
142
146
  warn(53, error);
143
147
  }
148
+ super.postHarvestCleanup({
149
+ sent,
150
+ retry
151
+ }); // this will set isRetrying & re-buffer the body if request is to be retried
152
+ if (this.isRetrying && this.retries++ < 1) {
153
+ // Only retry once
154
+ setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
155
+ sendEmptyBody: true
156
+ }), 5000); // Retry sending the RUM event after 5 seconds
157
+ return;
158
+ }
144
159
  if (status >= 400 || status === 0) {
145
160
  warn(18, status);
161
+ this.blocked = true;
162
+
163
+ // Get estimated payload size of our backlog
164
+ const textEncoder = new TextEncoder();
165
+ const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
166
+ if (!value) return acc;
167
+ const encoded = textEncoder.encode(value);
168
+ return acc + encoded.byteLength;
169
+ }, 0);
170
+
171
+ // Send SMs about failed RUM request
172
+ const body = {
173
+ sm: [{
174
+ params: {
175
+ name: "Browser/Supportability/BCS/Error/".concat(status)
176
+ },
177
+ stats: {
178
+ c: 1
179
+ }
180
+ }, {
181
+ params: {
182
+ name: 'Browser/Supportability/BCS/Error/Dropped/Bytes'
183
+ },
184
+ stats: {
185
+ c: 1,
186
+ t: payloadSize
187
+ }
188
+ }, {
189
+ params: {
190
+ name: 'Browser/Supportability/BCS/Error/Duration/Ms'
191
+ },
192
+ stats: {
193
+ c: 1,
194
+ t: rumEndTime - this.rumStartTime
195
+ }
196
+ }]
197
+ };
198
+ send(this.agentRef, {
199
+ endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
200
+ payload: {
201
+ body
202
+ },
203
+ submitMethod: getSubmitMethod(),
204
+ featureName: FEATURE_NAMES.metrics
205
+ });
206
+
146
207
  // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
147
208
  this.ee.abort();
148
209
  return;
@@ -161,6 +222,7 @@ export class Aggregate extends AggregateBase {
161
222
  }
162
223
  } catch (error) {
163
224
  this.ee.abort();
225
+ this.blocked = true;
164
226
  warn(17, error);
165
227
  return;
166
228
  }
@@ -2,7 +2,6 @@
2
2
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { handle } from '../../../common/event-emitter/handle';
6
5
  import { setupSetPageViewNameAPI } from '../../../loaders/api/setPageViewName';
7
6
  import { InstrumentBase } from '../../utils/instrument-base';
8
7
  import * as CONSTANTS from '../constants';
@@ -19,9 +18,6 @@ export class Instrument extends InstrumentBase {
19
18
 
20
19
  /** feature specific APIs */
21
20
  setupSetPageViewNameAPI(agentRef);
22
-
23
- /** messages from the register API that can trigger a new RUM call */
24
- this.ee.on('api-send-rum', (attrs, target) => handle('send-rum', [attrs, target], undefined, this.featureName, this.ee));
25
21
  this.importAggregator(agentRef, () => import(/* webpackChunkName: "page_view_event-aggregate" */'../aggregate'));
26
22
  }
27
23
  setupInspectionEvents(agentIdentifier) {
@@ -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;
@@ -72,10 +73,10 @@ export class Aggregate extends AggregateBase {
72
73
  if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
73
74
  this.mode = data.sessionReplayMode;
74
75
  });
75
- registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
76
+ registerHandler(PAUSE_REPLAY, () => {
76
77
  this.forceStop(this.mode === MODE.FULL);
77
78
  }, this.featureName, this.ee);
78
- registerHandler(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
79
+ registerHandler(ERROR_DURING_REPLAY, e => {
79
80
  this.handleError(e);
80
81
  }, this.featureName, this.ee);
81
82
  const {
@@ -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
  }
@@ -52,6 +52,11 @@ export function setupAgentSession(agentRef) {
52
52
  registerHandler('api-setUserId', (time, key, value) => {
53
53
  agentRef.runtime.session.syncCustomAttribute(key, value);
54
54
  }, 'session', sharedEE);
55
+ registerHandler('api-consent', accept => {
56
+ agentRef.runtime.session.write({
57
+ consent: accept === undefined ? true : accept
58
+ });
59
+ }, 'session', sharedEE);
55
60
  drain(agentRef.agentIdentifier, 'session');
56
61
  return agentRef.runtime.session;
57
62
  }
@@ -48,7 +48,10 @@ export class InstrumentBase extends FeatureBase {
48
48
  * @type {Promise} Assigned immediately after @see importAggregator runs. Serves as a signal for when the inner async fn finishes execution. Useful for features to await
49
49
  * one another if there are inter-features dependencies.
50
50
  */
51
- this.onAggregateImported = undefined;
51
+ this.loadedSuccessfully = undefined;
52
+ this.onAggregateImported = new Promise(resolve => {
53
+ this.loadedSuccessfully = resolve;
54
+ });
52
55
 
53
56
  /**
54
57
  * used in conjunction with newrelic.start() to defer harvesting in features
@@ -80,10 +83,6 @@ export class InstrumentBase extends FeatureBase {
80
83
  */
81
84
  importAggregator(agentRef, fetchAggregator, argsObjFromInstrument = {}) {
82
85
  if (this.featAggregate) return;
83
- let loadedSuccessfully;
84
- this.onAggregateImported = new Promise(resolve => {
85
- loadedSuccessfully = resolve;
86
- });
87
86
  const importLater = async () => {
88
87
  // wait for the deferred promise to resolve before proceeding
89
88
  // this will resolve immediately if the feature is auto-started,
@@ -111,7 +110,7 @@ export class InstrumentBase extends FeatureBase {
111
110
  try {
112
111
  if (!this.#shouldImportAgg(this.featureName, session, agentRef.init)) {
113
112
  drain(this.agentIdentifier, this.featureName);
114
- loadedSuccessfully(false); // aggregate module isn't loaded at all
113
+ this.loadedSuccessfully(false); // aggregate module isn't loaded at all
115
114
  return;
116
115
  }
117
116
  const {
@@ -119,13 +118,13 @@ export class InstrumentBase extends FeatureBase {
119
118
  } = await fetchAggregator();
120
119
  this.featAggregate = new Aggregate(agentRef, argsObjFromInstrument);
121
120
  agentRef.runtime.harvester.initializedAggregates.push(this.featAggregate); // "subscribe" the feature to future harvest intervals (PVE will start the timer)
122
- loadedSuccessfully(true);
121
+ this.loadedSuccessfully(true);
123
122
  } catch (e) {
124
123
  warn(34, e);
125
124
  this.abortHandler?.(); // undo any important alterations made to the page
126
125
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
127
126
  drain(this.agentIdentifier, this.featureName, true);
128
- loadedSuccessfully(false);
127
+ this.loadedSuccessfully(false);
129
128
  if (this.ee) this.ee.abort();
130
129
  }
131
130
  };
@@ -48,6 +48,27 @@ export class RegisteredEntity {
48
48
  warn(35, 'addPageAction');
49
49
  }
50
50
 
51
+ /**
52
+ * Records a custom event with a specified eventType and attributes.
53
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
54
+ * @param {string} eventType The eventType to store the event as.
55
+ * @param {Object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
56
+ */
57
+ recordCustomEvent(eventType, attributes) {
58
+ warn(35, 'recordCustomEvent');
59
+ }
60
+
61
+ /**
62
+ * Measures a task that is recorded as a BrowserPerformance event.
63
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
64
+ * @param {string} name The name of the task
65
+ * @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
66
+ * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
67
+ */
68
+ measure(name, options) {
69
+ warn(35, 'measure');
70
+ }
71
+
51
72
  /**
52
73
  * Adds a user-defined attribute name and value to subsequent events on the page for the registered target. Note -- the persist flag does not work with the register API.
53
74
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setcustomattribute/}
@@ -23,6 +23,7 @@ import { setupSetCustomAttributeAPI } from './api/setCustomAttribute';
23
23
  import { setupSetUserIdAPI } from './api/setUserId';
24
24
  import { setupSetApplicationVersionAPI } from './api/setApplicationVersion';
25
25
  import { setupStartAPI } from './api/start';
26
+ import { setupConsentAPI } from './api/consent';
26
27
 
27
28
  /**
28
29
  * @typedef {Object} AgentOptions
@@ -67,6 +68,7 @@ export class Agent extends AgentBase {
67
68
  setupSetUserIdAPI(this);
68
69
  setupSetApplicationVersionAPI(this);
69
70
  setupStartAPI(this);
71
+ setupConsentAPI(this);
70
72
  this.run();
71
73
  }
72
74
  get config() {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { prefix, CONSENT } from './constants';
6
+ import { setupAPI } from './sharedHandlers';
7
+ import { handle } from '../../common/event-emitter/handle';
8
+ import { warn } from '../../common/util/console';
9
+ export function setupConsentAPI(agent) {
10
+ setupAPI(CONSENT, function (accept = true) {
11
+ if (typeof accept !== 'boolean') {
12
+ warn(65, typeof accept);
13
+ return;
14
+ }
15
+ /** harvester, by way of "consented" getter, checks session state first, and falls back on runtime state if not available. Set both here */
16
+ handle(prefix + CONSENT, [accept], undefined, 'session', agent.ee); // sets session state (if available)
17
+ agent.runtime.consented = accept; // sets runtime state
18
+
19
+ /** if consent is granted, attempt to make a PageView event harvest if one has not already been made */
20
+ if (accept) {
21
+ const pveInst = agent.features.page_view_event;
22
+ pveInst.onAggregateImported.then(loaded => {
23
+ const pveAgg = pveInst.featAggregate;
24
+ if (loaded && !pveAgg.sentRum) {
25
+ pveAgg.sendRum();
26
+ }
27
+ });
28
+ }
29
+ }, agent);
30
+ }
@@ -23,4 +23,5 @@ export const SET_PAGE_VIEW_NAME = 'setPageViewName';
23
23
  export const SET_USER_ID = 'setUserId';
24
24
  export const START = 'start';
25
25
  export const WRAP_LOGGER = 'wrapLogger';
26
- export const MEASURE = 'measure';
26
+ export const MEASURE = 'measure';
27
+ export const CONSENT = 'consent';
@@ -9,45 +9,45 @@ import { FEATURE_NAMES } from '../features/features';
9
9
  import { prefix, MEASURE } from './constants';
10
10
  import { setupAPI } from './sharedHandlers';
11
11
  export function setupMeasureAPI(agent) {
12
- setupAPI(MEASURE, function (name, options) {
13
- const n = now();
14
- const {
15
- start,
16
- end,
17
- customAttributes
18
- } = options || {};
19
- const returnObj = {
20
- customAttributes: customAttributes || {}
21
- };
22
- if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
23
- warn(57);
24
- return;
25
- }
12
+ setupAPI(MEASURE, (name, options) => measure(name, options, agent), agent);
13
+ }
14
+ export function measure(name, options, agentRef, target, timestamp = now()) {
15
+ const {
16
+ start,
17
+ end,
18
+ customAttributes
19
+ } = options || {};
20
+ const returnObj = {
21
+ customAttributes: customAttributes || {}
22
+ };
23
+ if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
24
+ warn(57);
25
+ return;
26
+ }
26
27
 
27
- /**
28
+ /**
28
29
  * getValueFromTiming - Helper function to extract a numeric value from a supplied option.
29
30
  * @param {Number|PerformanceMark} [timing] The timing value
30
31
  * @param {Number} [d] The default value to return if timing is invalid
31
32
  * @returns {Number} The timing value or the default value
32
33
  */
33
- const getValueFromTiming = (timing, d) => {
34
- if (timing == null) return d;
35
- if (typeof timing === 'number') return timing;
36
- if (timing instanceof PerformanceMark) return timing.startTime;
37
- return Number.NaN;
38
- };
39
- returnObj.start = getValueFromTiming(start, 0);
40
- returnObj.end = getValueFromTiming(end, n);
41
- if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
42
- warn(57);
43
- return;
44
- }
45
- returnObj.duration = returnObj.end - returnObj.start;
46
- if (returnObj.duration < 0) {
47
- warn(58);
48
- return;
49
- }
50
- handle(prefix + MEASURE, [returnObj, name], undefined, FEATURE_NAMES.genericEvents, agent.ee);
51
- return returnObj;
52
- }, agent);
34
+ const getValueFromTiming = (timing, d) => {
35
+ if (timing == null) return d;
36
+ if (typeof timing === 'number') return timing;
37
+ if (timing instanceof PerformanceMark) return timing.startTime;
38
+ return Number.NaN;
39
+ };
40
+ returnObj.start = getValueFromTiming(start, 0);
41
+ returnObj.end = getValueFromTiming(end, timestamp);
42
+ if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
43
+ warn(57);
44
+ return;
45
+ }
46
+ returnObj.duration = returnObj.end - returnObj.start;
47
+ if (returnObj.duration < 0) {
48
+ warn(58);
49
+ return;
50
+ }
51
+ handle(prefix + MEASURE, [returnObj, name, target], undefined, FEATURE_NAMES.genericEvents, agentRef.ee);
52
+ return returnObj;
53
53
  }
@@ -8,7 +8,8 @@ import { FEATURE_NAMES } from '../features/features';
8
8
  import { prefix, RECORD_CUSTOM_EVENT } from './constants';
9
9
  import { setupAPI } from './sharedHandlers';
10
10
  export function setupRecordCustomEventAPI(agent) {
11
- setupAPI(RECORD_CUSTOM_EVENT, function () {
12
- handle(prefix + RECORD_CUSTOM_EVENT, [now(), ...arguments], undefined, FEATURE_NAMES.genericEvents, agent.ee);
13
- }, agent);
11
+ setupAPI(RECORD_CUSTOM_EVENT, (eventType, attributes) => recordCustomEvent(eventType, attributes, agent), agent);
12
+ }
13
+ export function recordCustomEvent(eventType, attributes = {}, agentRef, target, timestamp = now()) {
14
+ handle(prefix + RECORD_CUSTOM_EVENT, [timestamp, eventType, attributes, target], undefined, FEATURE_NAMES.genericEvents, agentRef.ee);
14
15
  }
@@ -5,29 +5,33 @@
5
5
 
6
6
  /**
7
7
  * @typedef {Object} RegisterAPI
8
- * @property {Function} addPageAction - Add a page action for the registered entity.
9
- * @property {Function} log - Capture a log for the registered entity.
10
- * @property {Function} noticeError - Notice an error for the registered entity.
11
- * @property {Function} setApplicationVersion - Add an application.version attribute to all outgoing data for the registered entity.
12
- * @property {Function} setCustomAttribute - Add a custom attribute to outgoing data for the registered entity.
13
- * @property {Function} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity.
8
+ * @property {(name: string, attributes?: object) => void} addPageAction - Add a page action for the registered entity.
9
+ * @property {(message: string, options?: { customAttributes?: object, level?: 'ERROR' | 'TRACE' | 'DEBUG' | 'INFO' | 'WARN'}) => void} log - Capture a log for the registered entity.
10
+ * @property {(error: Error | string, customAttributes?: object) => void} noticeError - Notice an error for the registered entity.
11
+ * @property {(target: RegisterAPIConstructor) => RegisterAPI} register - Record a custom event for the registered entity.
12
+ * @property {(eventType: string, attributes?: Object) => void} recordCustomEvent - Record a custom event for the registered entity.
13
+ * @property {(eventType: string, options?: {start: number, end: number, duration: number, customAttributes: object}) => ({start: number, end: number, duration: number, customAttributes: object})} measure - Measures a task that is recorded as a BrowserPerformance event.
14
+ * @property {(value: string | null) => void} setApplicationVersion - Add an application.version attribute to all outgoing data for the registered entity.
15
+ * @property {(name: string, value: string | number | boolean | null, persist?: boolean) => void} setCustomAttribute - Add a custom attribute to outgoing data for the registered entity.
16
+ * @property {(value: string | null) => void} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity.
14
17
  * @property {RegisterAPIMetadata} metadata - The metadata object containing the custom attributes and target information for the registered entity.
15
18
  */
16
19
 
17
20
  /**
18
21
  * @typedef {Object} RegisterAPIConstructor
19
- * @property {Object} opts - The options for the registered entity.
20
- * @property {string} opts.id - The unique id for the registered entity. This will be assigned to any synthesized entities.
21
- * @property {string} opts.name - The readable name for the registered entity. This will be assigned to any synthesized entities.
22
+ * @property {string|number} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
23
+ * @property {string} name - The readable name for the registered entity. This will be assigned to any synthesized entities.
24
+ * @property {string} [parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
22
25
  */
23
26
 
24
27
  /**
25
28
  * @typedef {Object} RegisterAPIMetadata
26
29
  * @property {Object} customAttributes - The custom attributes for the registered entity.
27
30
  * @property {Object} target - The options for the registered entity.
28
- * @property {string} target.licenseKey - The license key for the registered entity. If none was supplied, it will assume the license key from the main agent.
31
+ * @property {string} [target.licenseKey] - The license key for the registered entity. If none was supplied, it will assume the license key from the main agent.
29
32
  * @property {string} target.id - The ID for the registered entity.
30
33
  * @property {string} target.name - The name returned for the registered entity.
34
+ * @property {string} [target.parentId] - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
31
35
  */
32
36
 
33
37
  export default {};
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { handle } from '../../common/event-emitter/handle';
6
6
  import { warn } from '../../common/util/console';
7
- import { isValidMFETarget } from '../../common/util/mfe';
7
+ import { hasValidValue, isValidMFETarget } from '../../common/util/mfe';
8
8
  import { FEATURE_NAMES } from '../features/features';
9
9
  import { now } from '../../common/timing/now';
10
10
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
@@ -14,6 +14,8 @@ import { log } from './log';
14
14
  import { addPageAction } from './addPageAction';
15
15
  import { noticeError } from './noticeError';
16
16
  import { single } from '../../common/util/invoke';
17
+ import { measure } from './measure';
18
+ import { recordCustomEvent } from './recordCustomEvent';
17
19
 
18
20
  /**
19
21
  * @typedef {import('./register-api-types').RegisterAPI} RegisterAPI
@@ -26,7 +28,7 @@ import { single } from '../../common/util/invoke';
26
28
  */
27
29
  export function setupRegisterAPI(agent) {
28
30
  setupAPI(REGISTER, function (target) {
29
- return buildRegisterApi(agent, target);
31
+ return register(agent, target);
30
32
  }, agent);
31
33
  }
32
34
 
@@ -34,19 +36,18 @@ export function setupRegisterAPI(agent) {
34
36
  * Builds the api object that will be returned from the register api method.
35
37
  * Also conducts certain side-effects, such as harvesting a PageView event when triggered and gathering metadata for the registered entity.
36
38
  * @param {Object} agentRef the reference to the base agent instance
37
- * @param {Object} handlers the shared handlers to be used by both the base agent's API and the external target's API
38
- * @param {Object} target the target information to be used by the external target's API to send data to the correct location
39
- * @param {string} [target.licenseKey] the license key of the target to report data to
40
- * @param {string} target.id the entity ID of the target to report data to
41
- * @param {string} target.name the entity name of the target to report data to
39
+ * @param {import('./register-api-types').RegisterAPIConstructor} target
40
+ * @param {import('./register-api-types').RegisterAPIConstructor} [parent]
42
41
  * @returns {RegisterAPI} the api object to be returned from the register api method
43
42
  */
44
- export function buildRegisterApi(agentRef, target) {
43
+ function register(agentRef, target, parent) {
45
44
  const attrs = {};
46
45
  warn(54, 'newrelic.register');
47
46
  target ||= {};
47
+ target.eventSource = 'MicroFrontendBrowserAgent';
48
48
  target.licenseKey ||= agentRef.info.licenseKey; // will inherit the license key from the container agent if not provided for brevity. A future state may dictate that we need different license keys to do different things.
49
49
  target.blocked = false;
50
+ target.parent = parent || {};
50
51
 
51
52
  /** @type {Function} a function that is set and reports when APIs are triggered -- warns the customer of the invalid state */
52
53
  let invalidApiResponse = () => {};
@@ -79,6 +80,9 @@ export function buildRegisterApi(agentRef, target) {
79
80
  /** primary cases that can block the register API from working at init time */
80
81
  if (!agentRef.init.api.allow_registered_children) block(single(() => warn(55)));
81
82
  if (!isValidMFETarget(target)) block(single(() => warn(48, target)));
83
+ if (!hasValidValue(target.id) || !hasValidValue(target.name)) {
84
+ block(single(() => warn(48, target)));
85
+ }
82
86
 
83
87
  /** @type {RegisterAPI} */
84
88
  const api = {
@@ -93,10 +97,22 @@ export function buildRegisterApi(agentRef, target) {
93
97
  ...(options.customAttributes || {})
94
98
  }
95
99
  }, agentRef], target),
100
+ measure: (name, options = {}) => report(measure, [name, {
101
+ ...options,
102
+ customAttributes: {
103
+ ...attrs,
104
+ ...(options.customAttributes || {})
105
+ }
106
+ }, agentRef], target),
96
107
  noticeError: (error, attributes = {}) => report(noticeError, [error, {
97
108
  ...attrs,
98
109
  ...attributes
99
110
  }, agentRef], target),
111
+ register: (target = {}) => report(register, [agentRef, target], api.metadata.target),
112
+ recordCustomEvent: (eventType, attributes = {}) => report(recordCustomEvent, [eventType, {
113
+ ...attrs,
114
+ ...attributes
115
+ }, agentRef], target),
100
116
  setApplicationVersion: value => setLocalValue('application.version', value),
101
117
  setCustomAttribute: (key, value) => setLocalValue(key, value),
102
118
  setUserId: value => setLocalValue('enduser.id', value),
@@ -144,8 +160,8 @@ export function buildRegisterApi(agentRef, target) {
144
160
  const timestamp = now();
145
161
  handle(SUPPORTABILITY_METRIC_CHANNEL, ["API/register/".concat(methodToCall.name, "/called")], undefined, FEATURE_NAMES.metrics, agentRef.ee);
146
162
  try {
147
- const shouldDuplicate = agentRef.init.api.duplicate_registered_data;
148
- if (shouldDuplicate === true || Array.isArray(shouldDuplicate)) {
163
+ const shouldDuplicate = agentRef.init.api.duplicate_registered_data && methodToCall.name !== 'register';
164
+ if (shouldDuplicate) {
149
165
  // also report to container by providing undefined target
150
166
  methodToCall(...args, undefined, timestamp);
151
167
  }