@newrelic/browser-agent 1.302.0-rc.0 → 1.302.0-rc.10

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 (116) hide show
  1. package/dist/cjs/common/config/init-types.js +2 -0
  2. package/dist/cjs/common/config/init.js +3 -0
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/harvest/harvester.js +13 -9
  6. package/dist/cjs/common/harvest/types.js +0 -1
  7. package/dist/cjs/common/session/session-entity.js +4 -2
  8. package/dist/cjs/common/util/mfe.js +4 -0
  9. package/dist/cjs/common/wrap/wrap-promise.js +10 -5
  10. package/dist/cjs/features/generic_events/aggregate/index.js +4 -4
  11. package/dist/cjs/features/logging/aggregate/index.js +1 -2
  12. package/dist/cjs/features/page_view_event/aggregate/index.js +84 -22
  13. package/dist/cjs/features/page_view_event/instrument/index.js +0 -4
  14. package/dist/cjs/features/session_replay/aggregate/index.js +3 -2
  15. package/dist/cjs/features/session_replay/constants.js +2 -6
  16. package/dist/cjs/features/session_replay/instrument/index.js +3 -2
  17. package/dist/cjs/features/utils/agent-session.js +13 -0
  18. package/dist/cjs/features/utils/instrument-base.js +7 -8
  19. package/dist/cjs/interfaces/registered-entity.js +21 -0
  20. package/dist/cjs/loaders/agent.js +2 -0
  21. package/dist/cjs/loaders/api/consent.js +24 -0
  22. package/dist/cjs/loaders/api/constants.js +3 -2
  23. package/dist/cjs/loaders/api/measure.js +36 -35
  24. package/dist/cjs/loaders/api/recordCustomEvent.js +5 -3
  25. package/dist/cjs/loaders/api/register-api-types.js +10 -9
  26. package/dist/cjs/loaders/api/register.js +16 -0
  27. package/dist/cjs/loaders/api-base.js +14 -7
  28. package/dist/esm/common/config/init-types.js +2 -0
  29. package/dist/esm/common/config/init.js +3 -0
  30. package/dist/esm/common/constants/env.cdn.js +1 -1
  31. package/dist/esm/common/constants/env.npm.js +1 -1
  32. package/dist/esm/common/harvest/harvester.js +13 -9
  33. package/dist/esm/common/harvest/types.js +0 -1
  34. package/dist/esm/common/session/session-entity.js +4 -2
  35. package/dist/esm/common/util/mfe.js +3 -0
  36. package/dist/esm/common/wrap/wrap-promise.js +10 -5
  37. package/dist/esm/features/generic_events/aggregate/index.js +4 -4
  38. package/dist/esm/features/logging/aggregate/index.js +1 -2
  39. package/dist/esm/features/page_view_event/aggregate/index.js +84 -22
  40. package/dist/esm/features/page_view_event/instrument/index.js +0 -4
  41. package/dist/esm/features/session_replay/aggregate/index.js +4 -3
  42. package/dist/esm/features/session_replay/constants.js +1 -5
  43. package/dist/esm/features/session_replay/instrument/index.js +4 -3
  44. package/dist/esm/features/utils/agent-session.js +13 -0
  45. package/dist/esm/features/utils/instrument-base.js +7 -8
  46. package/dist/esm/interfaces/registered-entity.js +21 -0
  47. package/dist/esm/loaders/agent.js +2 -0
  48. package/dist/esm/loaders/api/consent.js +17 -0
  49. package/dist/esm/loaders/api/constants.js +2 -1
  50. package/dist/esm/loaders/api/measure.js +35 -35
  51. package/dist/esm/loaders/api/recordCustomEvent.js +4 -3
  52. package/dist/esm/loaders/api/register-api-types.js +10 -9
  53. package/dist/esm/loaders/api/register.js +17 -1
  54. package/dist/esm/loaders/api-base.js +15 -8
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/dist/types/common/config/init-types.d.ts +6 -0
  57. package/dist/types/common/config/init.d.ts.map +1 -1
  58. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  59. package/dist/types/common/harvest/types.d.ts +0 -2
  60. package/dist/types/common/harvest/types.d.ts.map +1 -1
  61. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  62. package/dist/types/common/util/mfe.d.ts +1 -0
  63. package/dist/types/common/util/mfe.d.ts.map +1 -1
  64. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  65. package/dist/types/features/page_view_event/aggregate/index.d.ts +22 -3
  66. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  68. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/session_replay/constants.d.ts +1 -5
  70. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  71. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  72. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  73. package/dist/types/features/utils/instrument-base.d.ts +1 -0
  74. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  75. package/dist/types/interfaces/registered-entity.d.ts +25 -0
  76. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  77. package/dist/types/loaders/agent.d.ts.map +1 -1
  78. package/dist/types/loaders/api/consent.d.ts +2 -0
  79. package/dist/types/loaders/api/consent.d.ts.map +1 -0
  80. package/dist/types/loaders/api/constants.d.ts +1 -0
  81. package/dist/types/loaders/api/constants.d.ts.map +1 -1
  82. package/dist/types/loaders/api/measure.d.ts +3 -0
  83. package/dist/types/loaders/api/measure.d.ts.map +1 -1
  84. package/dist/types/loaders/api/recordCustomEvent.d.ts +1 -0
  85. package/dist/types/loaders/api/recordCustomEvent.d.ts.map +1 -1
  86. package/dist/types/loaders/api/register-api-types.d.ts +28 -11
  87. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  88. package/dist/types/loaders/api/register.d.ts.map +1 -1
  89. package/dist/types/loaders/api-base.d.ts +20 -15
  90. package/dist/types/loaders/api-base.d.ts.map +1 -1
  91. package/package.json +2 -2
  92. package/src/common/config/init-types.js +2 -0
  93. package/src/common/config/init.js +1 -0
  94. package/src/common/harvest/harvester.js +11 -8
  95. package/src/common/harvest/types.js +0 -1
  96. package/src/common/session/session-entity.js +6 -2
  97. package/src/common/util/mfe.js +4 -0
  98. package/src/common/wrap/wrap-promise.js +16 -6
  99. package/src/features/generic_events/aggregate/index.js +4 -4
  100. package/src/features/logging/aggregate/index.js +1 -1
  101. package/src/features/page_view_event/aggregate/index.js +79 -15
  102. package/src/features/page_view_event/instrument/index.js +0 -4
  103. package/src/features/session_replay/aggregate/index.js +4 -3
  104. package/src/features/session_replay/constants.js +1 -5
  105. package/src/features/session_replay/instrument/index.js +4 -3
  106. package/src/features/utils/agent-session.js +12 -0
  107. package/src/features/utils/instrument-base.js +7 -9
  108. package/src/interfaces/registered-entity.js +21 -0
  109. package/src/loaders/agent.js +2 -0
  110. package/src/loaders/api/consent.js +18 -0
  111. package/src/loaders/api/constants.js +1 -0
  112. package/src/loaders/api/measure.js +34 -33
  113. package/src/loaders/api/recordCustomEvent.js +5 -3
  114. package/src/loaders/api/register-api-types.js +10 -9
  115. package/src/loaders/api/register.js +8 -1
  116. package/src/loaders/api-base.js +15 -8
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.measure = measure;
6
7
  exports.setupMeasureAPI = setupMeasureAPI;
7
8
  var _handle = require("../../common/event-emitter/handle");
8
9
  var _now = require("../../common/timing/now");
@@ -16,45 +17,45 @@ var _sharedHandlers = require("./sharedHandlers");
16
17
  */
17
18
 
18
19
  function setupMeasureAPI(agent) {
19
- (0, _sharedHandlers.setupAPI)(_constants.MEASURE, function (name, options) {
20
- const n = (0, _now.now)();
21
- const {
22
- start,
23
- end,
24
- customAttributes
25
- } = options || {};
26
- const returnObj = {
27
- customAttributes: customAttributes || {}
28
- };
29
- if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
30
- (0, _console.warn)(57);
31
- return;
32
- }
20
+ (0, _sharedHandlers.setupAPI)(_constants.MEASURE, (name, options) => measure(name, options, agent), agent);
21
+ }
22
+ function measure(name, options, agentRef, target, timestamp = (0, _now.now)()) {
23
+ const {
24
+ start,
25
+ end,
26
+ customAttributes
27
+ } = options || {};
28
+ const returnObj = {
29
+ customAttributes: customAttributes || {}
30
+ };
31
+ if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
32
+ (0, _console.warn)(57);
33
+ return;
34
+ }
33
35
 
34
- /**
36
+ /**
35
37
  * getValueFromTiming - Helper function to extract a numeric value from a supplied option.
36
38
  * @param {Number|PerformanceMark} [timing] The timing value
37
39
  * @param {Number} [d] The default value to return if timing is invalid
38
40
  * @returns {Number} The timing value or the default value
39
41
  */
40
- const getValueFromTiming = (timing, d) => {
41
- if (timing == null) return d;
42
- if (typeof timing === 'number') return timing;
43
- if (timing instanceof PerformanceMark) return timing.startTime;
44
- return Number.NaN;
45
- };
46
- returnObj.start = getValueFromTiming(start, 0);
47
- returnObj.end = getValueFromTiming(end, n);
48
- if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
49
- (0, _console.warn)(57);
50
- return;
51
- }
52
- returnObj.duration = returnObj.end - returnObj.start;
53
- if (returnObj.duration < 0) {
54
- (0, _console.warn)(58);
55
- return;
56
- }
57
- (0, _handle.handle)(_constants.prefix + _constants.MEASURE, [returnObj, name], undefined, _features.FEATURE_NAMES.genericEvents, agent.ee);
58
- return returnObj;
59
- }, agent);
42
+ const getValueFromTiming = (timing, d) => {
43
+ if (timing == null) return d;
44
+ if (typeof timing === 'number') return timing;
45
+ if (timing instanceof PerformanceMark) return timing.startTime;
46
+ return Number.NaN;
47
+ };
48
+ returnObj.start = getValueFromTiming(start, 0);
49
+ returnObj.end = getValueFromTiming(end, timestamp);
50
+ if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
51
+ (0, _console.warn)(57);
52
+ return;
53
+ }
54
+ returnObj.duration = returnObj.end - returnObj.start;
55
+ if (returnObj.duration < 0) {
56
+ (0, _console.warn)(58);
57
+ return;
58
+ }
59
+ (0, _handle.handle)(_constants.prefix + _constants.MEASURE, [returnObj, name, target], undefined, _features.FEATURE_NAMES.genericEvents, agentRef.ee);
60
+ return returnObj;
60
61
  }
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.recordCustomEvent = recordCustomEvent;
6
7
  exports.setupRecordCustomEventAPI = setupRecordCustomEventAPI;
7
8
  var _handle = require("../../common/event-emitter/handle");
8
9
  var _now = require("../../common/timing/now");
@@ -15,7 +16,8 @@ var _sharedHandlers = require("./sharedHandlers");
15
16
  */
16
17
 
17
18
  function setupRecordCustomEventAPI(agent) {
18
- (0, _sharedHandlers.setupAPI)(_constants.RECORD_CUSTOM_EVENT, function () {
19
- (0, _handle.handle)(_constants.prefix + _constants.RECORD_CUSTOM_EVENT, [(0, _now.now)(), ...arguments], undefined, _features.FEATURE_NAMES.genericEvents, agent.ee);
20
- }, agent);
19
+ (0, _sharedHandlers.setupAPI)(_constants.RECORD_CUSTOM_EVENT, (eventType, attributes) => recordCustomEvent(eventType, attributes, agent), agent);
20
+ }
21
+ function recordCustomEvent(eventType, attributes = {}, agentRef, target, timestamp = (0, _now.now)()) {
22
+ (0, _handle.handle)(_constants.prefix + _constants.RECORD_CUSTOM_EVENT, [timestamp, eventType, attributes, target], undefined, _features.FEATURE_NAMES.genericEvents, agentRef.ee);
21
23
  }
@@ -10,19 +10,20 @@ exports.default = void 0;
10
10
  */
11
11
  /**
12
12
  * @typedef {Object} RegisterAPI
13
- * @property {Function} addPageAction - Add a page action for the registered entity.
14
- * @property {Function} log - Capture a log for the registered entity.
15
- * @property {Function} noticeError - Notice an error for the registered entity.
16
- * @property {Function} setApplicationVersion - Add an application.version attribute to all outgoing data for the registered entity.
17
- * @property {Function} setCustomAttribute - Add a custom attribute to outgoing data for the registered entity.
18
- * @property {Function} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity.
13
+ * @property {(name: string, attributes?: object) => void} addPageAction - Add a page action for the registered entity.
14
+ * @property {(message: string, options?: { customAttributes?: object, level?: 'ERROR' | 'TRACE' | 'DEBUG' | 'INFO' | 'WARN'}) => void} log - Capture a log for the registered entity.
15
+ * @property {(error: Error | string, customAttributes?: object) => void} noticeError - Notice an error for the registered entity.
16
+ * @property {(eventType: string, attributes?: Object) => void} recordCustomEvent - Record a custom event for the registered entity.
17
+ * @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.
18
+ * @property {(value: string | null) => void} setApplicationVersion - Add an application.version attribute to all outgoing data for the registered entity.
19
+ * @property {(name: string, value: string | number | boolean | null, persist?: boolean) => void} setCustomAttribute - Add a custom attribute to outgoing data for the registered entity.
20
+ * @property {(value: string | null) => void} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity.
19
21
  * @property {RegisterAPIMetadata} metadata - The metadata object containing the custom attributes and target information for the registered entity.
20
22
  */
21
23
  /**
22
24
  * @typedef {Object} RegisterAPIConstructor
23
- * @property {Object} opts - The options for the registered entity.
24
- * @property {string} opts.id - The unique id for the registered entity. This will be assigned to any synthesized entities.
25
- * @property {string} opts.name - The readable name for the registered entity. This will be assigned to any synthesized entities.
25
+ * @property {string|number} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
26
+ * @property {string} name - The readable name for the registered entity. This will be assigned to any synthesized entities.
26
27
  */
27
28
  /**
28
29
  * @typedef {Object} RegisterAPIMetadata
@@ -17,6 +17,8 @@ var _log = require("./log");
17
17
  var _addPageAction = require("./addPageAction");
18
18
  var _noticeError = require("./noticeError");
19
19
  var _invoke = require("../../common/util/invoke");
20
+ var _measure = require("./measure");
21
+ var _recordCustomEvent = require("./recordCustomEvent");
20
22
  /**
21
23
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
22
24
  * SPDX-License-Identifier: Apache-2.0
@@ -86,6 +88,9 @@ function buildRegisterApi(agentRef, target) {
86
88
  /** primary cases that can block the register API from working at init time */
87
89
  if (!agentRef.init.api.allow_registered_children) block((0, _invoke.single)(() => (0, _console.warn)(55)));
88
90
  if (!(0, _mfe.isValidMFETarget)(target)) block((0, _invoke.single)(() => (0, _console.warn)(48, target)));
91
+ if (!(0, _mfe.hasValidValue)(target.id) || !(0, _mfe.hasValidValue)(target.name)) {
92
+ block((0, _invoke.single)(() => (0, _console.warn)(48, target)));
93
+ }
89
94
 
90
95
  /** @type {RegisterAPI} */
91
96
  const api = {
@@ -100,10 +105,21 @@ function buildRegisterApi(agentRef, target) {
100
105
  ...(options.customAttributes || {})
101
106
  }
102
107
  }, agentRef], target),
108
+ measure: (name, options = {}) => report(_measure.measure, [name, {
109
+ ...options,
110
+ customAttributes: {
111
+ ...attrs,
112
+ ...(options.customAttributes || {})
113
+ }
114
+ }, agentRef], target),
103
115
  noticeError: (error, attributes = {}) => report(_noticeError.noticeError, [error, {
104
116
  ...attrs,
105
117
  ...attributes
106
118
  }, agentRef], target),
119
+ recordCustomEvent: (eventType, attributes = {}) => report(_recordCustomEvent.recordCustomEvent, [eventType, {
120
+ ...attrs,
121
+ ...attributes
122
+ }, agentRef], target),
107
123
  setApplicationVersion: value => setLocalValue('application.version', value),
108
124
  setCustomAttribute: (key, value) => setLocalValue(key, value),
109
125
  setUserId: value => setLocalValue('enduser.id', value),
@@ -37,11 +37,8 @@ class ApiBase {
37
37
  * It is not recommended for use in production environments and will not receive support for issues.
38
38
  *
39
39
  * Registers an external caller to report through the base agent to a different target than the base agent.
40
- * @param {object} target the target object to report data to
41
- * @param {string} target.licenseKey The licenseKey to report data to
42
- * @param {string} target.applicationID The applicationID to report data to
43
- * @param {string=} target.entityGuid The entityGuid to report data to
44
- * @returns {object} Returns an object that contains the available API methods and configurations to use with the external caller. See loaders/api/api.js for more information.
40
+ * @param {import('./api/register-api-types').RegisterAPIConstructor} target the target object to report data to
41
+ @returns {import('./api/register-api-types').RegisterAPI} Returns an object that contains the available API methods and configurations to use with the external caller. See loaders/api/api.js for more information.
45
42
  */
46
43
  register(target) {
47
44
  return this.#callMethod(_constants.REGISTER, target);
@@ -51,7 +48,7 @@ class ApiBase {
51
48
  * Records a custom event with a specified eventType and attributes.
52
49
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
53
50
  * @param {string} eventType The eventType to store the event as.
54
- * @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
51
+ * @param {Object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
55
52
  */
56
53
  recordCustomEvent(eventType, attributes) {
57
54
  return this.#callMethod(_constants.RECORD_CUSTOM_EVENT, eventType, attributes);
@@ -227,11 +224,21 @@ class ApiBase {
227
224
  * Measures a task that is recorded as a BrowserPerformance event.
228
225
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
229
226
  * @param {string} name The name of the task
230
- * @param {object?} options An object used to control the way the measure API operates
227
+ * @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
231
228
  * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
232
229
  */
233
230
  measure(name, options) {
234
231
  return this.#callMethod(_constants.MEASURE, name, options);
235
232
  }
233
+
234
+ /**
235
+ * Accepts or rejects consent when the agent is configured to require consent before harvesting.
236
+ * The consent state is stored in session storage inside the NRBA_SESSION object.
237
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/consent/}
238
+ * @param {boolean?} accept Whether to accept or reject consent. Defaults to true (accept) if left undefined.
239
+ */
240
+ consent(accept) {
241
+ return this.#callMethod(_constants.CONSENT, accept);
242
+ }
236
243
  }
237
244
  exports.ApiBase = ApiBase;
@@ -84,6 +84,8 @@
84
84
  * @property {boolean} [spa.enabled] - Turn on/off the single page application feature (on by default). NOTE: the SPA feature is deprecated and under removal procedure.
85
85
  * @property {boolean} [spa.autoStart] - If true, the agent will automatically start the single page application feature. Otherwise, it will be in a deferred state until the `start` API method is called.
86
86
  * @property {boolean} [ssl] - If explicitly false, the agent will use HTTP instead of HTTPS. This setting should NOT be used.
87
+ * @property {Object} [browser_consent_mode]
88
+ * @property {boolean} [browser_consent_mode.enabled] - If true, the agent will use consent mode for whether to allow or disallow data harvest.
87
89
  * @property {Object} [user_actions]
88
90
  * @property {boolean} [user_actions.enabled] - Must be true to allow UserAction events to be captured.
89
91
  * @property {Array<string>} [user_actions.elementAttributes] - List of HTML Element properties to be captured with UserAction events' target elements. This may help to identify the source element being interacted with in the UI.
@@ -62,6 +62,9 @@ const InitModelFn = () => {
62
62
  },
63
63
  duplicate_registered_data: false
64
64
  },
65
+ browser_consent_mode: {
66
+ enabled: false
67
+ },
65
68
  distributed_tracing: {
66
69
  enabled: undefined,
67
70
  exclude_newrelic_header: undefined,
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.302.0-rc.0";
14
+ export const VERSION = "1.302.0-rc.10";
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.302.0-rc.0";
14
+ export const VERSION = "1.302.0-rc.10";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -17,8 +17,10 @@ import { stringify } from '../util/stringify';
17
17
  import { getSubmitMethod, xhr as xhrMethod, xhrFetch as fetchMethod } from '../util/submit-data';
18
18
  import { activatedFeatures } from '../util/feature-flags';
19
19
  import { dispatchGlobalEvent } from '../dispatch/global-event';
20
- const RETRY_FAILED = 'Harvester/Retry/Failed/';
21
- const RETRY_SUCCEEDED = 'Harvester/Retry/Succeeded/';
20
+ const RETRY = 'Harvester/Retry/';
21
+ const RETRY_ATTEMPTED = RETRY + 'Attempted/';
22
+ const RETRY_FAILED = RETRY + 'Failed/';
23
+ const RETRY_SUCCEEDED = RETRY + 'Succeeded/';
22
24
  export class Harvester {
23
25
  #started = false;
24
26
  initializedAggregates = [];
@@ -59,11 +61,11 @@ export class Harvester {
59
61
  endpointVersion: aggregateInst.harvestEndpointVersion || 1
60
62
  };
61
63
  if (aggregateInst.blocked) return output;
64
+ if (this.agentRef.init?.browser_consent_mode?.enabled && !this.agentRef.runtime?.session?.state?.consent) return output;
62
65
  const submitMethod = getSubmitMethod(localOpts);
63
66
  if (!submitMethod) return output;
64
67
  const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === xhrMethod; // always retry all features harvests except for final
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
-
68
+ output.payload = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts);
67
69
  if (!output.payload) return output;
68
70
  send(this.agentRef, {
69
71
  endpoint: FEATURE_TO_ENDPOINT[aggregateInst.featureName],
@@ -85,7 +87,9 @@ export class Harvester {
85
87
  function cbFinished(result) {
86
88
  if (aggregateInst.harvestOpts.prevAttemptCode) {
87
89
  // this means we just retried a harvest that last failed
88
- handle(SUPPORTABILITY_METRIC_CHANNEL, [(result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode], undefined, FEATURE_NAMES.metrics, aggregateInst.ee);
90
+ const reportSM = message => handle(SUPPORTABILITY_METRIC_CHANNEL, [message], undefined, FEATURE_NAMES.metrics, aggregateInst.ee);
91
+ reportSM(RETRY_ATTEMPTED + aggregateInst.featureName);
92
+ reportSM((result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode);
89
93
  delete aggregateInst.harvestOpts.prevAttemptCode; // always reset last observation so we don't falsely report again next harvest
90
94
  // In case this re-attempt failed again, that'll be handled (re-marked again) next.
91
95
  }
@@ -175,9 +179,9 @@ export function send(agentRef, {
175
179
  status: this.status,
176
180
  retry: shouldRetry(this.status),
177
181
  fullUrl,
178
- xhr: this
182
+ xhr: this,
183
+ responseText: this.responseText
179
184
  };
180
- if (localOpts.needResponse) cbResult.responseText = this.responseText;
181
185
  cbFinished(cbResult);
182
186
 
183
187
  /** temporary audit of consistency of harvest metadata flags */
@@ -191,9 +195,9 @@ export function send(agentRef, {
191
195
  status,
192
196
  retry: shouldRetry(status),
193
197
  fullUrl,
194
- fetchResponse: response
198
+ fetchResponse: response,
199
+ responseText: await response.text()
195
200
  };
196
- if (localOpts.needResponse) cbResult.responseText = await response.text();
197
201
  cbFinished(cbResult);
198
202
  /** temporary audit of consistency of harvest metadata flags */
199
203
  if (!shouldRetry(status)) trackHarvestMetadata();
@@ -24,7 +24,6 @@
24
24
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
25
25
  * @property {HarvestPayload} payload Object representing payload.
26
26
  * @property {object} localOpts Additional options for sending data
27
- * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
28
27
  * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
29
28
  * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
30
29
  * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
@@ -33,7 +33,8 @@ const model = {
33
33
  serverTimeDiff: null,
34
34
  // set by TimeKeeper; "undefined" value will not be stringified and stored but "null" will
35
35
  custom: {},
36
- numOfResets: 0
36
+ numOfResets: 0,
37
+ consent: false // set by consent() API call
37
38
  };
38
39
  export class SessionEntity {
39
40
  /**
@@ -84,7 +85,8 @@ export class SessionEntity {
84
85
  }) {
85
86
  /** Ensure that certain properties are preserved across a reset if already set */
86
87
  const persistentAttributes = {
87
- serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
88
+ serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff,
89
+ consent: this.state.consent || model.consent
88
90
  };
89
91
  this.state = {};
90
92
  this.sync({
@@ -10,6 +10,9 @@
10
10
  export function isValidMFETarget(target = {}) {
11
11
  return !!(target.id && target.name);
12
12
  }
13
+ export function hasValidValue(val) {
14
+ return typeof val === 'string' && val.trim().length < 501 || typeof val === 'number';
15
+ }
13
16
 
14
17
  /**
15
18
  * When given a valid target, returns an object with the MFE payload attributes. Returns an empty object otherwise.
@@ -129,12 +129,17 @@ export function wrapPromise(sharedEE) {
129
129
  });
130
130
  promiseEE.on('propagate', function (val, overwrite, trigger) {
131
131
  if (!this.getCtx || overwrite) {
132
- this.getCtx = function () {
133
- // eslint-disable-next-line
134
- if (val instanceof Promise) {
135
- var store = promiseEE.context(val);
132
+ const selfStore = this;
133
+ const parentStore = val instanceof Promise ? promiseEE.context(val) : null;
134
+ let cachedCtx;
135
+ this.getCtx = function getCtx() {
136
+ if (cachedCtx) return cachedCtx;
137
+ if (parentStore && parentStore !== selfStore) {
138
+ cachedCtx = typeof parentStore.getCtx === 'function' ? parentStore.getCtx() : parentStore;
139
+ } else {
140
+ cachedCtx = selfStore;
136
141
  }
137
- return store && store.getCtx ? store.getCtx() : this;
142
+ return cachedCtx;
138
143
  };
139
144
  }
140
145
  });
@@ -28,13 +28,13 @@ export class Aggregate extends AggregateBase {
28
28
  return;
29
29
  }
30
30
  this.#trackSupportabilityMetrics();
31
- registerHandler('api-recordCustomEvent', (timestamp, eventType, attributes) => {
31
+ registerHandler('api-recordCustomEvent', (timestamp, eventType, attributes, target) => {
32
32
  if (RESERVED_EVENT_TYPES.includes(eventType)) return warn(46);
33
33
  this.addEvent({
34
34
  eventType,
35
35
  timestamp: this.toEpoch(timestamp),
36
36
  ...attributes
37
- });
37
+ }, target);
38
38
  }, this.featureName, this.ee);
39
39
  if (agentRef.init.page_action.enabled) {
40
40
  registerHandler('api-addPageAction', (timestamp, name, attributes, target) => {
@@ -231,7 +231,7 @@ export class Aggregate extends AggregateBase {
231
231
  }
232
232
  }, this.featureName, this.ee);
233
233
  }
234
- registerHandler('api-measure', (args, n) => {
234
+ registerHandler('api-measure', (args, n, target) => {
235
235
  const {
236
236
  start,
237
237
  duration,
@@ -245,7 +245,7 @@ export class Aggregate extends AggregateBase {
245
245
  entryDuration: duration,
246
246
  entryType: 'measure'
247
247
  };
248
- this.addEvent(event);
248
+ this.addEvent(event, target);
249
249
  }, this.featureName, this.ee);
250
250
  this.drain();
251
251
  });
@@ -97,8 +97,7 @@ export class Aggregate extends AggregateBase {
97
97
  common: {
98
98
  /** Attributes in the `common` section are added to `all` logs generated in the payload */
99
99
  attributes: {
100
- ...this.agentRef.info.jsAttributes,
101
- // user-provided custom attributes
100
+ ...applyFnToProps(this.agentRef.info.jsAttributes, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string'),
102
101
  ...(this.harvestEndpointVersion === 1 && {
103
102
  'entity.guid': this.agentRef.runtime.appMetadata.agents[0].entityGuid,
104
103
  appId: this.agentRef.info.applicationID
@@ -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 {