@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
@@ -7,7 +7,7 @@ exports.buildRegisterApi = buildRegisterApi;
7
7
  exports.setupRegisterAPI = setupRegisterAPI;
8
8
  var _handle = require("../../common/event-emitter/handle");
9
9
  var _console = require("../../common/util/console");
10
- var _target = require("../../common/util/target");
10
+ var _mfe = require("../../common/util/mfe");
11
11
  var _features = require("../features/features");
12
12
  var _now = require("../../common/timing/now");
13
13
  var _constants = require("../../features/metrics/constants");
@@ -16,6 +16,7 @@ var _constants2 = require("./constants");
16
16
  var _log = require("./log");
17
17
  var _addPageAction = require("./addPageAction");
18
18
  var _noticeError = require("./noticeError");
19
+ var _invoke = require("../../common/util/invoke");
19
20
  /**
20
21
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
21
22
  * SPDX-License-Identifier: Apache-2.0
@@ -42,138 +43,120 @@ function setupRegisterAPI(agent) {
42
43
  * @param {Object} agentRef the reference to the base agent instance
43
44
  * @param {Object} handlers the shared handlers to be used by both the base agent's API and the external target's API
44
45
  * @param {Object} target the target information to be used by the external target's API to send data to the correct location
46
+ * @param {string} [target.licenseKey] the license key of the target to report data to
47
+ * @param {string} target.id the entity ID of the target to report data to
48
+ * @param {string} target.name the entity name of the target to report data to
45
49
  * @returns {RegisterAPI} the api object to be returned from the register api method
46
50
  */
47
51
  function buildRegisterApi(agentRef, target) {
48
52
  const attrs = {};
49
53
  (0, _console.warn)(54, 'newrelic.register');
54
+ target ||= {};
55
+ 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.
56
+ target.blocked = false;
50
57
 
51
- /** @type {Function|undefined} a function that is set and reports when APIs are triggered -- warns the customer of the invalid state */
52
- let invalidApiResponse;
58
+ /** @type {Function} a function that is set and reports when APIs are triggered -- warns the customer of the invalid state */
59
+ let invalidApiResponse = () => {};
60
+ /** @type {Array} the array of registered target APIs */
61
+ const registeredEntities = agentRef.runtime.registeredEntities;
62
+
63
+ /** if we have already registered this target, go ahead and re-use it */
64
+ const preregisteredEntity = registeredEntities.find(({
65
+ metadata: {
66
+ target: {
67
+ id,
68
+ name
69
+ }
70
+ }
71
+ }) => id === target.id);
72
+ if (preregisteredEntity) {
73
+ if (preregisteredEntity.metadata.target.name !== target.name) preregisteredEntity.metadata.target.name = target.name;
74
+ return preregisteredEntity;
75
+ }
53
76
 
54
77
  /**
55
- * A promise that indicates when all needed connections for the registered child to be ready to report data
56
- * 1. The main agent to be ready (made a RUM call and got its entity guid)
57
- * 2. The child to be registered with the main agent (made its own RUM call and got its entity guid)
58
- * @type {Promise<RegisterAPI>}
59
- */
60
- let _connected;
61
- if (!agentRef.init.api.allow_registered_children) invalidApiResponse = () => (0, _console.warn)(55);
62
- if (!target || !(0, _target.isValidTarget)(target)) invalidApiResponse = () => (0, _console.warn)(48, target);
78
+ * Block the API, and supply a warning function to display a message to end users
79
+ * @param {Function} warning
80
+ */
81
+ const block = warning => {
82
+ target.blocked = true;
83
+ invalidApiResponse = warning;
84
+ };
85
+
86
+ /** primary cases that can block the register API from working at init time */
87
+ if (!agentRef.init.api.allow_registered_children) block((0, _invoke.single)(() => (0, _console.warn)(55)));
88
+ if (!(0, _mfe.isValidMFETarget)(target)) block((0, _invoke.single)(() => (0, _console.warn)(48, target)));
63
89
 
64
90
  /** @type {RegisterAPI} */
65
91
  const api = {
66
- addPageAction: (name, attributes = {}) => {
67
- report(_addPageAction.addPageAction, [name, {
92
+ addPageAction: (name, attributes = {}) => report(_addPageAction.addPageAction, [name, {
93
+ ...attrs,
94
+ ...attributes
95
+ }, agentRef], target),
96
+ log: (message, options = {}) => report(_log.log, [message, {
97
+ ...options,
98
+ customAttributes: {
68
99
  ...attrs,
69
- ...attributes
70
- }, agentRef], target);
71
- },
72
- log: (message, options = {}) => {
73
- report(_log.log, [message, {
74
- ...options,
75
- customAttributes: {
76
- ...attrs,
77
- ...(options.customAttributes || {})
78
- }
79
- }, agentRef], target);
80
- },
81
- noticeError: (error, attributes = {}) => {
82
- report(_noticeError.noticeError, [error, {
83
- ...attrs,
84
- ...attributes
85
- }, agentRef], target);
86
- },
87
- setApplicationVersion: value => {
88
- attrs['application.version'] = value;
89
- },
90
- setCustomAttribute: (key, value) => {
91
- attrs[key] = value;
92
- },
93
- setUserId: value => {
94
- attrs['enduser.id'] = value;
95
- },
100
+ ...(options.customAttributes || {})
101
+ }
102
+ }, agentRef], target),
103
+ noticeError: (error, attributes = {}) => report(_noticeError.noticeError, [error, {
104
+ ...attrs,
105
+ ...attributes
106
+ }, agentRef], target),
107
+ setApplicationVersion: value => setLocalValue('application.version', value),
108
+ setCustomAttribute: (key, value) => setLocalValue(key, value),
109
+ setUserId: value => setLocalValue('enduser.id', value),
96
110
  /** metadata */
97
111
  metadata: {
98
112
  customAttributes: attrs,
99
- target,
100
- /** set in a getter so that later access of the Promise is not polluted before customer is allowed to set a catch block */
101
- get connected() {
102
- return _connected || Promise.reject(new Error('Failed to connect'));
103
- }
113
+ target
104
114
  }
105
115
  };
106
- if (invalidApiResponse) {
107
- invalidApiResponse();
108
- } else {
109
- _connected = new Promise((resolve, reject) => {
110
- try {
111
- const entityManager = agentRef.runtime?.entityManager;
112
- /** check if main agent already has main agent entity guid */
113
- let mainAgentReady = !!entityManager?.get().entityGuid;
114
- /** check if registered target already has entity guid */
115
- let registeredEntityGuid = entityManager?.getEntityGuidFor(target.licenseKey, target.applicationID);
116
- let registrationReady = !!registeredEntityGuid;
117
-
118
- /** check if we can just resolve immediately without making another connect call */
119
- if (mainAgentReady && registrationReady) {
120
- target.entityGuid = registeredEntityGuid;
121
- resolve(api);
122
- } else {
123
- /** we need to make a new connection call since we dont already have a registered entity for this call */
124
116
 
125
- /** if the connect callback doesnt resolve in 15 seconds... reject */
126
- const timeout = setTimeout(() => reject(new Error('Failed to connect - Timeout')), 15000);
117
+ /**
118
+ * Check if the API is blocked and emit a warning message describing the blockage
119
+ * @returns {boolean}
120
+ */
121
+ const isBlocked = () => {
122
+ if (target.blocked) invalidApiResponse();
123
+ return target.blocked;
124
+ };
127
125
 
128
- // tell the main agent to send a rum call for this target
129
- // when the rum call resolves, it will emit an "entity-added" event, see below
130
- agentRef.ee.emit('api-send-rum', [attrs, target]);
126
+ /** only allow registered APIs to be tracked in the agent runtime */
127
+ if (!isBlocked()) registeredEntities.push(api);
131
128
 
132
- // wait for entity events to emit to see when main agent and/or API registration is ready
133
- agentRef.ee.on('entity-added', entityEventHandler);
134
- function entityEventHandler(entity) {
135
- if ((0, _target.isContainerAgentTarget)(entity, agentRef)) mainAgentReady ||= true;else {
136
- if (target.licenseKey === entity.licenseKey && target.applicationID === entity.applicationID) {
137
- registrationReady = true;
138
- target.entityGuid = entity.entityGuid;
139
- }
140
- }
141
- if (mainAgentReady && registrationReady) {
142
- clearTimeout(timeout);
143
- // unsubscribe from the event emitter
144
- agentRef.ee.removeEventListener('entity-added', entityEventHandler);
145
- resolve(api);
146
- }
147
- }
148
- }
149
- } catch (err) {
150
- reject(err);
151
- }
152
- });
153
- }
129
+ /**
130
+ * Sets a value local to the registered API attrs. Will do nothing if APIs are deregistered.
131
+ * @param {string} key The attribute key
132
+ * @param {*} value the attribute value
133
+ * @returns {void}
134
+ */
135
+ const setLocalValue = (key, value) => {
136
+ if (isBlocked()) return;
137
+ attrs[key] = value;
138
+ };
154
139
 
155
140
  /**
156
141
  * The reporter method that will be used to report the data to the container agent's API method. If invalid, will log a warning and not execute.
157
142
  * If the api.duplicate_registered_data configuration value is set to true, the data will be reported to BOTH the container and the external target
158
143
  * @param {*} methodToCall the container agent's API method to call
159
144
  * @param {*} args the arguments to supply to the container agent's API method
160
- * @param {string} targetEntityGuid the target entity guid, which looks up the target to report the data to from the entity manager. If undefined, will report to the container agent's target.
145
+ * @param {string} target the target to report the data to. If undefined, will report to the container agent's target.
161
146
  * @returns
162
147
  */
163
- const report = async (methodToCall, args, target) => {
164
- if (invalidApiResponse) return invalidApiResponse();
148
+ const report = (methodToCall, args, target) => {
149
+ if (isBlocked()) return;
165
150
  /** set the timestamp before the async part of waiting for the rum response for better accuracy */
166
151
  const timestamp = (0, _now.now)();
167
152
  (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ["API/register/".concat(methodToCall.name, "/called")], undefined, _features.FEATURE_NAMES.metrics, agentRef.ee);
168
153
  try {
169
- await _connected;
170
- // target should be decorated with entityGuid by the rum resp at this point
171
154
  const shouldDuplicate = agentRef.init.api.duplicate_registered_data;
172
- if (shouldDuplicate === true || Array.isArray(shouldDuplicate) && shouldDuplicate.includes(target.entityGuid)) {
155
+ if (shouldDuplicate === true || Array.isArray(shouldDuplicate)) {
173
156
  // also report to container by providing undefined target
174
157
  methodToCall(...args, undefined, timestamp);
175
158
  }
176
- methodToCall(...args, target.entityGuid, timestamp); // always report to target
159
+ return methodToCall(...args, target, timestamp); // always report to target
177
160
  } catch (err) {
178
161
  (0, _console.warn)(50, err);
179
162
  }
@@ -35,7 +35,7 @@
35
35
  * @property {Object} [metrics]
36
36
  * @property {boolean} [metrics.enabled] - Turn on/off the metrics feature (on by default).
37
37
  * @property {boolean} [metrics.autoStart] - If true, the agent will automatically start the metrics feature. Otherwise, it will be in a deferred state until the `start` API method is called.
38
- * @property {Array<Object>} [obfuscate] - Array of regexp and corresponding replacement patterns for obfuscating data.
38
+ * @property {{regex: RegExp | string, replacement: string}[]} [obfuscate] - Array of regexp and corresponding replacement patterns for obfuscating data.
39
39
  * @property {Object} [page_action]
40
40
  * @property {boolean} [page_action.enabled] - Must be true to allow PageAction events to be captured.
41
41
  * @property {Object} [page_view_event]
@@ -21,6 +21,7 @@ const InitModelFn = () => {
21
21
  const hiddenState = {
22
22
  feature_flags: [],
23
23
  experimental: {
24
+ allow_registered_children: false,
24
25
  resources: false
25
26
  },
26
27
  mask_selector: '*',
@@ -53,7 +54,12 @@ const InitModelFn = () => {
53
54
  autoStart: true
54
55
  },
55
56
  api: {
56
- allow_registered_children: true,
57
+ get allow_registered_children() {
58
+ return hiddenState.feature_flags.includes(FEATURE_FLAGS.REGISTER) || hiddenState.experimental.allow_registered_children;
59
+ },
60
+ set allow_registered_children(val) {
61
+ hiddenState.experimental.allow_registered_children = val;
62
+ },
57
63
  duplicate_registered_data: false
58
64
  },
59
65
  distributed_tracing: {
@@ -23,7 +23,6 @@ const RuntimeModel = {
23
23
  customTransaction: undefined,
24
24
  denyList: undefined,
25
25
  disabled: false,
26
- entityManager: undefined,
27
26
  harvester: undefined,
28
27
  isolatedBacklog: false,
29
28
  isRecording: false,
@@ -36,6 +35,7 @@ const RuntimeModel = {
36
35
  releaseIds: {},
37
36
  session: undefined,
38
37
  timeKeeper: undefined,
38
+ registeredEntities: [],
39
39
  /** a proxy is set in agent-session to track jsAttributes changes for harvesting mechanics */
40
40
  jsAttributesMetadata: {
41
41
  bytes: 0
@@ -2,7 +2,15 @@
2
2
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
+ import { FEATURE_NAMES } from '../../loaders/features/features';
5
6
  export const IDEAL_PAYLOAD_SIZE = 16000;
6
7
  export const MAX_PAYLOAD_SIZE = 1000000;
7
8
  export const DEFAULT_KEY = 'NR_CONTAINER_AGENT';
8
- export const SESSION_ERROR = 'SESSION_ERROR';
9
+ export const SESSION_ERROR = 'SESSION_ERROR';
10
+ export const SUPPORTS_REGISTERED_ENTITIES = {
11
+ [FEATURE_NAMES.logging]: true,
12
+ // flip other features here when they are supported by DEM consumers
13
+ [FEATURE_NAMES.genericEvents]: false,
14
+ [FEATURE_NAMES.jserrors]: false,
15
+ [FEATURE_NAMES.ajax]: false
16
+ };
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.301.0";
14
+ export const VERSION = "1.302.0-rc.1";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.301.0";
14
+ export const VERSION = "1.302.0-rc.1";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -122,7 +122,7 @@ function drainGroup(agentIdentifier, group, activateGroup = true) {
122
122
  }
123
123
  if (!baseEE.isolatedBacklog) delete handlers[group];
124
124
  baseEE.backlog[group] = null;
125
- baseEE.emit('drain-' + group, []); // TODO: Code exists purely for a unit test and needs to be refined
125
+ baseEE.emit('drain-' + group, []); // Informs the feature that it has drained its backlog of events, this kicks off an immediate harvest to capture any load-driven events that were buffered.
126
126
  }
127
127
 
128
128
  /**
@@ -53,35 +53,30 @@ export class Harvester {
53
53
  * @returns {boolean} True if 1+ network call was made. Note that this does not mean or guarantee that it was successful (or that all were in the case of more than 1).
54
54
  */
55
55
  triggerHarvestFor(aggregateInst, localOpts = {}) {
56
- if (aggregateInst.blocked) return false;
56
+ const output = {
57
+ ranSend: false,
58
+ payload: undefined,
59
+ endpointVersion: aggregateInst.harvestEndpointVersion || 1
60
+ };
61
+ if (aggregateInst.blocked) return output;
57
62
  const submitMethod = getSubmitMethod(localOpts);
58
- if (!submitMethod) return false;
63
+ if (!submitMethod) return output;
59
64
  const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === xhrMethod; // always retry all features harvests except for final
60
- let dataToSendArr;
61
- let ranSend = false;
62
- if (!localOpts.directSend) {
63
- // primarily used by rum call to bypass makeHarvestPayload by providing payload directly
64
- dataToSendArr = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts); // be sure the 'this' of makeHarvestPayload is the aggregate w/ access to its harvestOpts
65
- if (!dataToSendArr) return false; // can be undefined if storage is empty or preharvest checks failed
66
- } else dataToSendArr = [localOpts.directSend];
67
- dataToSendArr.forEach(({
68
- targetApp,
69
- payload
70
- }) => {
71
- if (!payload) return;
72
- send(this.agentRef, {
73
- endpoint: FEATURE_TO_ENDPOINT[aggregateInst.featureName],
74
- targetApp,
75
- payload,
76
- localOpts,
77
- submitMethod,
78
- cbFinished,
79
- raw: aggregateInst.harvestOpts.raw,
80
- featureName: aggregateInst.featureName
81
- });
82
- ranSend = true;
65
+ output.payload = !localOpts.directSend ? aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts) : localOpts.directSend?.payload; // features like PVE can define the payload directly, bypassing the makeHarvestPayload logic
66
+
67
+ if (!output.payload) return output;
68
+ send(this.agentRef, {
69
+ endpoint: FEATURE_TO_ENDPOINT[aggregateInst.featureName],
70
+ payload: output.payload,
71
+ localOpts,
72
+ submitMethod,
73
+ cbFinished,
74
+ raw: aggregateInst.harvestOpts.raw,
75
+ featureName: aggregateInst.featureName,
76
+ endpointVersion: output.endpointVersion
83
77
  });
84
- return ranSend;
78
+ output.ranSend = true;
79
+ return output;
85
80
 
86
81
  /**
87
82
  * This is executed immediately after harvest sends the data via XHR, or if there's nothing to send. Note that this excludes on unloading / sendBeacon.
@@ -114,13 +109,13 @@ const warnings = {};
114
109
  */
115
110
  export function send(agentRef, {
116
111
  endpoint,
117
- targetApp,
118
112
  payload,
119
113
  localOpts = {},
120
114
  submitMethod,
121
115
  cbFinished,
122
116
  raw,
123
- featureName
117
+ featureName,
118
+ endpointVersion = 1
124
119
  }) {
125
120
  if (!agentRef.info.errorBeacon) return false;
126
121
  let {
@@ -130,15 +125,14 @@ export function send(agentRef, {
130
125
  if (Object.keys(body).length === 0 && !localOpts.sendEmptyBody) {
131
126
  // if there's no body to send, just run onfinish stuff and return
132
127
  if (cbFinished) cbFinished({
133
- sent: false,
134
- targetApp
128
+ sent: false
135
129
  });
136
130
  return false;
137
131
  }
138
132
  const protocol = agentRef.init.ssl === false ? 'http' : 'https';
139
133
  const perceivedBeacon = agentRef.init.proxy.beacon || agentRef.info.errorBeacon;
140
- const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(endpoint !== RUM ? '/' + endpoint : '', "/1/").concat(targetApp.licenseKey);
141
- const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint, targetApp.applicationID) : '';
134
+ const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(endpoint !== RUM ? '/' + endpoint : '', "/").concat(endpointVersion, "/").concat(agentRef.info.licenseKey);
135
+ const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint) : '';
142
136
  let payloadParams = obj(qs, agentRef.runtime.maxBytes);
143
137
  if (baseParams === '' && payloadParams.startsWith('&')) {
144
138
  payloadParams = payloadParams.substring(1);
@@ -181,8 +175,7 @@ export function send(agentRef, {
181
175
  status: this.status,
182
176
  retry: shouldRetry(this.status),
183
177
  fullUrl,
184
- xhr: this,
185
- targetApp
178
+ xhr: this
186
179
  };
187
180
  if (localOpts.needResponse) cbResult.responseText = this.responseText;
188
181
  cbFinished(cbResult);
@@ -198,8 +191,7 @@ export function send(agentRef, {
198
191
  status,
199
192
  retry: shouldRetry(status),
200
193
  fullUrl,
201
- fetchResponse: response,
202
- targetApp
194
+ fetchResponse: response
203
195
  };
204
196
  if (localOpts.needResponse) cbResult.responseText = await response.text();
205
197
  cbFinished(cbResult);
@@ -240,7 +232,6 @@ export function send(agentRef, {
240
232
  data: {
241
233
  endpoint,
242
234
  headers,
243
- targetApp,
244
235
  payload,
245
236
  submitMethod: getSubmitMethodName(),
246
237
  raw,
@@ -290,12 +281,12 @@ function cleanPayload(payload = {}) {
290
281
  }
291
282
 
292
283
  // The stuff that gets sent every time.
293
- function baseQueryString(agentRef, qs, endpoint, applicationID) {
284
+ function baseQueryString(agentRef, qs, endpoint) {
294
285
  const ref = agentRef.runtime.obfuscator.obfuscateString(cleanURL('' + globalScope.location));
295
286
  const session = agentRef.runtime.session;
296
287
  const hr = !!session?.state.sessionReplaySentFirstChunk && session?.state.sessionReplayMode === 1 && endpoint !== JSERRORS;
297
288
  const ht = !!session?.state.traceHarvestStarted && session?.state.sessionTraceMode === 1 && ![LOGS, BLOBS].includes(endpoint);
298
- const qps = ['a=' + applicationID, param('sa', agentRef.info.sa ? '' + agentRef.info.sa : ''), param('v', VERSION), transactionNameParam(), param('ct', agentRef.runtime.customTransaction), '&rst=' + now(), '&ck=0',
289
+ const qps = ['a=' + agentRef.info.applicationID, param('sa', agentRef.info.sa ? '' + agentRef.info.sa : ''), param('v', VERSION), transactionNameParam(), param('ct', agentRef.runtime.customTransaction), '&rst=' + now(), '&ck=0',
299
290
  // ck param DEPRECATED - still expected by backend
300
291
  '&s=' + (session?.state.value || '0'),
301
292
  // the 0 id encaps all untrackable and default traffic
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * @param {Object} [target] - the target to be validated
8
+ * @returns {boolean}
9
+ */
10
+ export function isValidMFETarget(target = {}) {
11
+ return !!(target.id && target.name);
12
+ }
13
+
14
+ /**
15
+ * When given a valid target, returns an object with the MFE payload attributes. Returns an empty object otherwise.
16
+ * @param {Object} [target] the registered target
17
+ * @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
18
+ * @returns {{'mfe.id': *, 'mfe.name': String}|{}} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
19
+ */
20
+ export function getVersion2Attributes(target, aggregateInstance) {
21
+ if (aggregateInstance?.harvestEndpointVersion !== 2) return {};
22
+ const containerAgentEntityGuid = aggregateInstance.agentRef.runtime.appMetadata.agents[0].entityGuid;
23
+ if (!isValidMFETarget(target)) {
24
+ return {
25
+ 'entity.guid': containerAgentEntityGuid,
26
+ appId: aggregateInstance.agentRef.info.applicationID
27
+ };
28
+ }
29
+ return {
30
+ 'mfe.id': target.id,
31
+ // these field names may change as the schema is finalized
32
+ 'mfe.name': target.name,
33
+ // these field names may change as the schema is finalized
34
+ eventSource: 'MicroFrontendBrowserAgent',
35
+ // these field names may change as the schema is finalized
36
+ 'parent.id': containerAgentEntityGuid
37
+ };
38
+ }
@@ -124,9 +124,13 @@ export class Aggregate extends AggregateBase {
124
124
  if (!eventBuffer.length) return;
125
125
  const addString = getAddStringContext(this.agentRef.runtime.obfuscator);
126
126
  let payload = 'bel.7;';
127
+ let firstTimestamp = 0;
127
128
  for (let i = 0; i < eventBuffer.length; i++) {
128
129
  const event = eventBuffer[i];
129
- const fields = [numeric(event.startTime), numeric(event.endTime - event.startTime), numeric(0),
130
+ // ajax nodes are relative to the first node (or page origin if no previous node), so we need to calculate the relative start time
131
+ const relativeStartTime = event.startTime - firstTimestamp;
132
+ if (i === 0) firstTimestamp = event.startTime;
133
+ const fields = [numeric(relativeStartTime), numeric(event.endTime - event.startTime), numeric(0),
130
134
  // callbackEnd
131
135
  numeric(0),
132
136
  // no callbackDuration for non-SPA events
@@ -14,6 +14,7 @@ import { applyFnToProps } from '../../../common/util/traverse';
14
14
  import { UserActionsAggregator } from './user-actions/user-actions-aggregator';
15
15
  import { isIFrameWindow } from '../../../common/dom/iframe';
16
16
  import { isPureObject } from '../../../common/util/type-check';
17
+ import { getVersion2Attributes } from '../../../common/util/mfe';
17
18
  export class Aggregate extends AggregateBase {
18
19
  static featureName = FEATURE_NAME;
19
20
  #userActionAggregator;
@@ -36,8 +37,7 @@ export class Aggregate extends AggregateBase {
36
37
  });
37
38
  }, this.featureName, this.ee);
38
39
  if (agentRef.init.page_action.enabled) {
39
- registerHandler('api-addPageAction', (timestamp, name, attributes, targetEntityGuid) => {
40
- if (!this.agentRef.runtime.entityManager.get(targetEntityGuid)) return warn(56, this.featureName);
40
+ registerHandler('api-addPageAction', (timestamp, name, attributes, target) => {
41
41
  this.addEvent({
42
42
  ...attributes,
43
43
  eventType: 'PageAction',
@@ -49,7 +49,7 @@ export class Aggregate extends AggregateBase {
49
49
  browserWidth: window.document.documentElement?.clientWidth,
50
50
  browserHeight: window.document.documentElement?.clientHeight
51
51
  })
52
- }, targetEntityGuid);
52
+ }, target);
53
53
  }, this.featureName, this.ee);
54
54
  }
55
55
  let addUserAction = () => {/** no-op */};
@@ -247,7 +247,6 @@ export class Aggregate extends AggregateBase {
247
247
  };
248
248
  this.addEvent(event);
249
249
  }, this.featureName, this.ee);
250
- agentRef.runtime.harvester.triggerHarvestFor(this);
251
250
  this.drain();
252
251
  });
253
252
  }
@@ -263,10 +262,10 @@ export class Aggregate extends AggregateBase {
263
262
  * * sessionTraceId: set by the `ptid=` query param
264
263
  * * userAgent*: set by the userAgent header
265
264
  * @param {object=} obj the event object for storing in the event buffer
266
- * @param {string=} targetEntityGuid the target entity guid for the event to scope buffering and harvesting. Defaults to agent config if undefined
265
+ * @param {string=} target the target metadata for the event to scope buffering and harvesting. Defaults to container agent config if undefined
267
266
  * @returns void
268
267
  */
269
- addEvent(obj = {}, targetEntityGuid) {
268
+ addEvent(obj = {}, target) {
270
269
  if (!obj || !Object.keys(obj).length) return;
271
270
  if (!obj.eventType) {
272
271
  warn(44);
@@ -281,7 +280,9 @@ export class Aggregate extends AggregateBase {
281
280
  timestamp: Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(now())),
282
281
  /** all generic events require pageUrl(s) */
283
282
  pageUrl: cleanURL('' + initialLocation),
284
- currentUrl: cleanURL('' + location)
283
+ currentUrl: cleanURL('' + location),
284
+ /** Specific attributes only supplied if harvesting to endpoint version 2 */
285
+ ...getVersion2Attributes(target, this)
285
286
  };
286
287
  const eventAttributes = {
287
288
  /** Agent-level custom attributes */
@@ -291,7 +292,7 @@ export class Aggregate extends AggregateBase {
291
292
  /** Event-specific attributes take precedence over agent-level custom attributes and fallbacks */
292
293
  ...obj
293
294
  };
294
- this.events.add(eventAttributes, targetEntityGuid);
295
+ this.events.add(eventAttributes);
295
296
  }
296
297
  serializer(eventBuffer) {
297
298
  return applyFnToProps({
@@ -11,5 +11,7 @@ export const RAGE_CLICK_THRESHOLD_MS = 1000;
11
11
  export const FRUSTRATION_TIMEOUT_MS = 2000;
12
12
  export const RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance'];
13
13
  export const FEATURE_FLAGS = {
14
- RESOURCES: 'experimental.resources'
14
+ RESOURCES: 'experimental.resources',
15
+ REGISTER: 'register'
16
+ // register.jserrors and register.generic_events are also used, but not referenced directly so no need to represent here
15
17
  };