@newrelic/browser-agent 1.237.0 → 1.238.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/cjs/common/constants/env.cdn.js +1 -1
  2. package/dist/cjs/common/constants/env.npm.js +1 -1
  3. package/dist/cjs/common/deny-list/deny-list.js +1 -1
  4. package/dist/cjs/common/event-emitter/contextual-ee.js +18 -26
  5. package/dist/cjs/common/event-emitter/event-context.js +12 -0
  6. package/dist/cjs/common/harvest/harvest.js +4 -4
  7. package/dist/cjs/common/ids/bundle-id.js +19 -0
  8. package/dist/cjs/common/wrap/wrap-events.js +3 -2
  9. package/dist/cjs/common/wrap/wrap-fetch.js +1 -3
  10. package/dist/cjs/common/wrap/wrap-function.js +15 -46
  11. package/dist/cjs/common/wrap/wrap-mutation.js +1 -2
  12. package/dist/cjs/common/wrap/wrap-promise.js +2 -3
  13. package/dist/cjs/common/wrap/wrap-xhr.js +23 -27
  14. package/dist/cjs/features/ajax/instrument/distributed-tracing.js +0 -4
  15. package/dist/cjs/features/ajax/instrument/index.js +19 -9
  16. package/dist/cjs/features/metrics/aggregate/index.js +8 -0
  17. package/dist/cjs/features/session_replay/aggregate/index.js +2 -0
  18. package/dist/cjs/features/session_trace/aggregate/index.js +14 -8
  19. package/dist/cjs/features/spa/aggregate/index.js +1 -1
  20. package/dist/cjs/loaders/agent-base.js +12 -0
  21. package/dist/cjs/loaders/api/api.js +14 -1
  22. package/dist/cjs/loaders/api/interaction-types.js +11 -4
  23. package/dist/cjs/loaders/configure/configure.js +2 -1
  24. package/dist/esm/common/constants/env.cdn.js +1 -1
  25. package/dist/esm/common/constants/env.npm.js +1 -1
  26. package/dist/esm/common/deny-list/deny-list.js +1 -1
  27. package/dist/esm/common/event-emitter/contextual-ee.js +16 -23
  28. package/dist/esm/common/event-emitter/event-context.js +5 -0
  29. package/dist/esm/common/harvest/harvest.js +4 -4
  30. package/dist/esm/common/ids/bundle-id.js +13 -0
  31. package/dist/esm/common/wrap/wrap-events.js +4 -3
  32. package/dist/esm/common/wrap/wrap-fetch.js +2 -4
  33. package/dist/esm/common/wrap/wrap-function.js +15 -44
  34. package/dist/esm/common/wrap/wrap-mutation.js +2 -3
  35. package/dist/esm/common/wrap/wrap-promise.js +3 -4
  36. package/dist/esm/common/wrap/wrap-xhr.js +23 -27
  37. package/dist/esm/features/ajax/instrument/distributed-tracing.js +0 -4
  38. package/dist/esm/features/ajax/instrument/index.js +20 -10
  39. package/dist/esm/features/metrics/aggregate/index.js +8 -0
  40. package/dist/esm/features/session_replay/aggregate/index.js +2 -0
  41. package/dist/esm/features/session_trace/aggregate/index.js +14 -8
  42. package/dist/esm/features/spa/aggregate/index.js +1 -1
  43. package/dist/esm/loaders/agent-base.js +12 -0
  44. package/dist/esm/loaders/api/api.js +14 -1
  45. package/dist/esm/loaders/api/interaction-types.js +11 -4
  46. package/dist/esm/loaders/configure/configure.js +3 -2
  47. package/dist/types/common/event-emitter/contextual-ee.d.ts +22 -2
  48. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  49. package/dist/types/common/event-emitter/event-context.d.ts +5 -0
  50. package/dist/types/common/event-emitter/event-context.d.ts.map +1 -0
  51. package/dist/types/common/event-emitter/register-handler.d.ts +1 -1
  52. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  53. package/dist/types/common/ids/bundle-id.d.ts +5 -0
  54. package/dist/types/common/ids/bundle-id.d.ts.map +1 -0
  55. package/dist/types/common/session/session-entity.d.ts +6 -6
  56. package/dist/types/common/window/nreum.d.ts +2 -2
  57. package/dist/types/common/wrap/wrap-events.d.ts.map +1 -1
  58. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  59. package/dist/types/common/wrap/wrap-function.d.ts +1 -19
  60. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  61. package/dist/types/common/wrap/wrap-mutation.d.ts.map +1 -1
  62. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  63. package/dist/types/common/wrap/wrap-xhr.d.ts.map +1 -1
  64. package/dist/types/features/ajax/instrument/distributed-tracing.d.ts +1 -1
  65. package/dist/types/features/ajax/instrument/distributed-tracing.d.ts.map +1 -1
  66. package/dist/types/features/metrics/aggregate/endpoint-map.d.ts +5 -5
  67. package/dist/types/features/metrics/aggregate/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_trace/aggregate/index.d.ts +5 -0
  70. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/loaders/agent-base.d.ts +9 -0
  72. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  73. package/dist/types/loaders/api/api.d.ts +6 -0
  74. package/dist/types/loaders/api/api.d.ts.map +1 -1
  75. package/dist/types/loaders/api/interaction-types.d.ts +18 -7
  76. package/dist/types/loaders/api/interaction-types.d.ts.map +1 -1
  77. package/dist/types/loaders/configure/configure.d.ts +1 -0
  78. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  79. package/dist/types/loaders/features/features.d.ts +9 -9
  80. package/dist/types/loaders/micro-agent.d.ts +1 -1
  81. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  82. package/package.json +22 -27
  83. package/src/common/deny-list/deny-list.js +1 -1
  84. package/src/common/deny-list/deny-list.test.js +103 -30
  85. package/src/common/event-emitter/{contextual-ee.test.js → contextual-ee.component-test.js} +15 -32
  86. package/src/common/event-emitter/contextual-ee.js +20 -31
  87. package/src/common/event-emitter/event-context.js +5 -0
  88. package/src/common/harvest/harvest.js +4 -4
  89. package/src/common/harvest/harvest.test.js +1 -1
  90. package/src/common/ids/__mocks__/bundle-id.js +2 -0
  91. package/src/common/ids/__mocks__/unique-id.js +17 -0
  92. package/src/common/ids/bundle-id.js +13 -0
  93. package/src/common/url/__mocks__/parse-url.js +15 -0
  94. package/src/common/util/__mocks__/get-or-set.js +5 -0
  95. package/src/common/window/__mocks__/nreum.js +10 -0
  96. package/src/common/wrap/wrap-events.js +4 -3
  97. package/src/common/wrap/wrap-fetch.js +2 -4
  98. package/src/common/wrap/wrap-function.js +16 -44
  99. package/src/common/wrap/wrap-mutation.js +2 -3
  100. package/src/common/wrap/{wrap-promise.test.js → wrap-promise.component-test.js} +2 -32
  101. package/src/common/wrap/wrap-promise.js +3 -4
  102. package/src/common/wrap/wrap-xhr.js +24 -28
  103. package/src/features/ajax/instrument/distributed-tracing.js +0 -4
  104. package/src/features/ajax/instrument/distributed-tracing.test.js +375 -0
  105. package/src/features/ajax/instrument/index.js +22 -10
  106. package/src/features/metrics/aggregate/index.js +8 -0
  107. package/src/features/session_replay/aggregate/index.js +2 -0
  108. package/src/features/session_trace/aggregate/index.js +16 -7
  109. package/src/features/spa/aggregate/index.js +1 -1
  110. package/src/loaders/agent-base.js +12 -0
  111. package/src/loaders/api/api.component-test.js +45 -0
  112. package/src/loaders/api/api.js +14 -1
  113. package/src/loaders/api/interaction-types.js +11 -4
  114. package/src/loaders/configure/configure.js +11 -2
  115. /package/src/common/url/{encode.component-test.js → encode.test.js} +0 -0
  116. /package/src/common/url/{protocol.component-test.js → protocol.test.js} +0 -0
@@ -12,7 +12,7 @@ exports.unused = void 0;
12
12
  * @property {getContext} getContext
13
13
  * @property {ignore} ignore
14
14
  * @property {onEnd} onEnd
15
- * @property {onEnd} save
15
+ * @property {save} save
16
16
  * @property {setAttribute} setAttribute
17
17
  * @property {setName} setName
18
18
  */
@@ -30,8 +30,8 @@ exports.unused = void 0;
30
30
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/createtracer/}
31
31
  * @callback createTracer
32
32
  * @param {string} name This will be used as the name of the tracer.
33
- * @param {string} [callback] A callback that contains the synchronous work to run at the end of the async work. To execute this callback, call the wrapper function returned using createTracer().
34
- * @returns {Function} Returns a method that wraps the original callback. When this method is invoked, it calls the original callback and ends the async timing.
33
+ * @param {(...args: any[]) => any} [callback] A callback that contains the synchronous work to run at the end of the async work. To execute this callback, call the wrapper function returned using createTracer().
34
+ * @returns {(...args: any[]) => any} Returns a method that wraps the original callback. When this method is invoked, it calls the original callback and ends the async timing.
35
35
  */
36
36
 
37
37
  /**
@@ -49,6 +49,13 @@ exports.unused = void 0;
49
49
  * @returns {InteractionInstance} Returns the same interaction object allowing method chaining.
50
50
  */
51
51
 
52
+ /**
53
+ * Overrides other SPA save() calls; ignores an interaction so it is not saved or sent to New Relic.
54
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/ignore/}
55
+ * @callback ignore
56
+ * @returns {InteractionInstance} Returns the same interaction object allowing method chaining.
57
+ */
58
+
52
59
  /**
53
60
  * Change the values associated with a SPA interaction before the interaction is saved.
54
61
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/onend/}
@@ -69,7 +76,7 @@ exports.unused = void 0;
69
76
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setattribute/}
70
77
  * @callback setAttribute
71
78
  * @param {string} key Used as the attribute name on the BrowserInteraction event.
72
- * @param {any} key Used as the attribute value on the BrowserInteraction event. This can be a string, number, boolean, or object. If it is an object, New Relic serializes it to a JSON string.
79
+ * @param {any} value Used as the attribute value on the BrowserInteraction event. This can be a string, number, boolean, or object. If it is an object, New Relic serializes it to a JSON string.
73
80
  * @returns {InteractionInstance} Returns the same interaction object allowing method chaining.
74
81
  */
75
82
 
@@ -36,7 +36,8 @@ function configure(agentIdentifier) {
36
36
  info.jsAttributes.isWorker = true;
37
37
  }
38
38
  (0, _config.setInfo)(agentIdentifier, info);
39
- runtime.denyList = init.ajax?.block_internal ? (init.ajax.deny_list || []).concat(info.beacon, info.errorBeacon) : init.ajax?.deny_list;
39
+ const updatedInit = (0, _config.getConfiguration)(agentIdentifier);
40
+ runtime.denyList = [...(updatedInit.ajax?.deny_list || []), ...(updatedInit.ajax?.block_internal ? [info.beacon, info.errorBeacon] : [])];
40
41
  (0, _config.setRuntime)(agentIdentifier, runtime);
41
42
  (0, _api.setTopLevelCallers)();
42
43
  const api = (0, _api.setAPI)(agentIdentifier, forceDrain);
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.237.0";
9
+ export const VERSION = "1.238.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.237.0";
9
+ export const VERSION = "1.238.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -57,7 +57,7 @@ export function setDenyList(denyListConfig) {
57
57
  host = url;
58
58
  pathname = '';
59
59
  }
60
- let [hostname, port] = host.split(':');
60
+ let [hostname] = host.split(':');
61
61
  denyList.push({
62
62
  hostname,
63
63
  pathname
@@ -5,21 +5,21 @@
5
5
 
6
6
  import { gosNREUM } from '../window/nreum';
7
7
  import { getOrSet } from '../util/get-or-set';
8
- import { mapOwn } from '../util/map-own';
9
8
  import { getRuntime } from '../config/config';
10
- var ctxId = 'nr@context';
9
+ import { EventContext } from './event-context';
10
+ import { bundleId } from '../ids/bundle-id';
11
11
 
12
+ // create a unique id to store event context data for the current agent bundle
13
+ const contextId = "nr@context:".concat(bundleId);
12
14
  // create global emitter instance that can be shared among bundles
13
- let nr = gosNREUM();
14
- var globalInstance;
15
- if (nr.ee) {
16
- globalInstance = nr.ee;
17
- } else {
18
- globalInstance = ee(undefined, 'globalEE');
15
+ const globalInstance = ee(undefined, 'globalEE');
16
+
17
+ // Only override the exposed event emitter if one has not already been exposed
18
+ const nr = gosNREUM();
19
+ if (!nr.ee) {
19
20
  nr.ee = globalInstance;
20
21
  }
21
- export { globalInstance as ee };
22
- function EventContext() {}
22
+ export { globalInstance as ee, contextId };
23
23
  function ee(old, debugId) {
24
24
  var handlers = {};
25
25
  var bufferGroupMap = {};
@@ -56,9 +56,9 @@ function ee(old, debugId) {
56
56
  if (contextOrStore && contextOrStore instanceof EventContext) {
57
57
  return contextOrStore;
58
58
  } else if (contextOrStore) {
59
- return getOrSet(contextOrStore, ctxId, getNewContext);
59
+ return getOrSet(contextOrStore, contextId, () => new EventContext(contextId));
60
60
  } else {
61
- return getNewContext();
61
+ return new EventContext(contextId);
62
62
  }
63
63
  }
64
64
  function emit(type, args, contextOrStore, force, bubble) {
@@ -105,12 +105,13 @@ function ee(old, debugId) {
105
105
  return emitters[name] = emitters[name] || ee(emitter, name);
106
106
  }
107
107
  function bufferEventsByGroup(types, group) {
108
- var eventBuffer = getBuffer();
108
+ const eventBuffer = getBuffer();
109
+ group = group || 'feature';
109
110
 
110
111
  // do not buffer events if agent has been aborted
111
112
  if (emitter.aborted) return;
112
- mapOwn(types, function (i, type) {
113
- group = group || 'feature';
113
+ Object.entries(types || {}).forEach(_ref => {
114
+ let [_, type] = _ref;
114
115
  bufferGroupMap[type] = group;
115
116
  if (!(group in eventBuffer)) {
116
117
  eventBuffer[group] = [];
@@ -128,14 +129,6 @@ function ee(old, debugId) {
128
129
  return emitter.backlog;
129
130
  }
130
131
  }
131
-
132
- // get context object from store object, or create if does not exist
133
- export function getOrSetContext(obj) {
134
- return getOrSet(obj, ctxId, getNewContext);
135
- }
136
- function getNewContext() {
137
- return new EventContext();
138
- }
139
132
  function abort() {
140
133
  globalInstance.aborted = true;
141
134
  globalInstance.backlog = {};
@@ -0,0 +1,5 @@
1
+ export class EventContext {
2
+ constructor(contextId) {
3
+ this.contextId = contextId;
4
+ }
5
+ }
@@ -48,7 +48,8 @@ export class Harvest extends SharedContext {
48
48
  isFinalHarvest: spec.opts?.unload
49
49
  });
50
50
  const options = {
51
- retry: !spec.opts?.unload && submitMethod === submitData.xhr
51
+ retry: !spec.opts?.unload && submitMethod === submitData.xhr,
52
+ isFinalHarvest: spec.opts?.unload === true
52
53
  };
53
54
  const payload = this.createPayload(spec.endpoint, options);
54
55
  const caller = this.obfuscator.shouldObfuscate() ? this.obfuscateAndSend.bind(this) : this._send.bind(this);
@@ -250,9 +251,8 @@ export class Harvest extends SharedContext {
250
251
  cleanPayload() {
251
252
  let payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
252
253
  const clean = input => {
253
- if (typeof Uint8Array !== 'undefined' && input instanceof Uint8Array || typeof input === 'string') {
254
- return input.length > 0 ? input : null;
255
- }
254
+ if (typeof Uint8Array !== 'undefined' && input instanceof Uint8Array || Array.isArray(input)) return input;
255
+ if (typeof input === 'string') return input.length > 0 ? input : null;
256
256
  return Object.entries(input || {}).reduce((accumulator, _ref2) => {
257
257
  let [key, value] = _ref2;
258
258
  if (typeof value === 'number' || typeof value === 'string' && value.length > 0 || typeof value === 'object' && Object.keys(value || {}).length > 0) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file Contains a unique identifier for the running agent bundle
3
+ * when loaded.
4
+ * @copyright 2023 New Relic Corporation. All rights reserved.
5
+ * @license Apache-2.0
6
+ */
7
+
8
+ import { generateUuid } from './unique-id';
9
+
10
+ /**
11
+ * Provides a unique id for the current agent bundle
12
+ */
13
+ export const bundleId = generateUuid();
@@ -7,14 +7,15 @@
7
7
  * This module is used directly by: session_trace.
8
8
  * It is also called by -> wrapXhr <-, so see "wrap-xhr.js" for features that use this indirectly.
9
9
  */
10
- import { ee as baseEE } from '../event-emitter/contextual-ee';
10
+ import { ee as baseEE, contextId } from '../event-emitter/contextual-ee';
11
11
  import { createWrapperWithEmitter as wfn } from './wrap-function';
12
12
  import { getOrSet } from '../util/get-or-set';
13
13
  import { globalScope, isBrowserScope } from '../constants/runtime';
14
14
  const wrapped = {};
15
- const XHR = XMLHttpRequest;
15
+ const XHR = globalScope.XMLHttpRequest;
16
16
  const ADD_EVENT_LISTENER = 'addEventListener';
17
17
  const REMOVE_EVENT_LISTENER = 'removeEventListener';
18
+ const flag = "nr@wrapped:".concat(contextId);
18
19
 
19
20
  /**
20
21
  * Wraps `addEventListener` and `removeEventListener` on: global scope; the prototype of `XMLHttpRequest`, and
@@ -43,7 +44,7 @@ export function wrapEvents(sharedEE) {
43
44
  if (originalListener === null || typeof originalListener !== 'function' && typeof originalListener !== 'object') {
44
45
  return;
45
46
  }
46
- var wrapped = getOrSet(originalListener, 'nr@wrapped', function () {
47
+ var wrapped = getOrSet(originalListener, flag, function () {
47
48
  var listener = {
48
49
  object: wrapHandleEvent,
49
50
  function: originalListener
@@ -6,16 +6,14 @@
6
6
  * @file Wraps `fetch` and related methods for instrumentation.
7
7
  * This module is used by: ajax, spa.
8
8
  */
9
- import { ee as baseEE } from '../event-emitter/contextual-ee';
9
+ import { ee as baseEE, contextId } from '../event-emitter/contextual-ee';
10
10
  import { globalScope } from '../constants/runtime';
11
- import { flag } from './wrap-function';
12
11
  var prefix = 'fetch-';
13
12
  var bodyPrefix = prefix + 'body-';
14
13
  var bodyMethods = ['arrayBuffer', 'blob', 'json', 'text', 'formData'];
15
14
  var Req = globalScope.Request;
16
15
  var Res = globalScope.Response;
17
16
  var proto = 'prototype';
18
- var ctxId = 'nr@context';
19
17
  const wrapped = {};
20
18
 
21
19
  /**
@@ -72,7 +70,7 @@ export function wrapFetch(sharedEE) {
72
70
  // we are wrapping args in an array so we can preserve the reference
73
71
  ee.emit(prefix + 'before-start', [args], ctx);
74
72
  var dtPayload;
75
- if (ctx[ctxId] && ctx[ctxId].dt) dtPayload = ctx[ctxId].dt;
73
+ if (ctx[contextId] && ctx[contextId].dt) dtPayload = ctx[contextId].dt;
76
74
  var origPromiseFromFetch = fn.apply(this, args);
77
75
  ee.emit(prefix + 'start', [args, dtPayload], origPromiseFromFetch);
78
76
 
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import { ee } from '../event-emitter/contextual-ee';
10
- export const flag = 'nr@original';
10
+ import { bundleId } from '../ids/bundle-id';
11
+ export const flag = "nr@original:".concat(bundleId);
11
12
 
12
13
  /**
13
14
  * A convenience alias of `hasOwnProperty`.
@@ -100,25 +101,23 @@ export function createWrapperWithEmitter(emitter, always) {
100
101
 
101
102
  /**
102
103
  * Creates wrapper functions around each of an array of methods on a specified object.
103
- * @param {Object} obj - The object to which the specified methods belong.
104
- * @param {string[]} methods - An array of method names to be wrapped.
105
- * @param {string} [prefix=''] - A prefix to add to the names of emitted events. If `-` is the first character, also
106
- * adds the method name before the prefix.
107
- * @param {function|object} [getContext] - The function or object that will serve as the 'this' context for handlers
108
- * of events emitted by this wrapper.
109
- * @param {boolean} [bubble=false] - If `true`, emitted events should also bubble up to the old emitter upon which
110
- * the `emitter` in the current scope was based (if it defines one).
104
+ * @param {Object} obj The object to which the specified methods belong.
105
+ * @param {string[]} methods An array of method names to be wrapped.
106
+ * @param {string} [prefix=''] A prefix to add to the names of emitted events. If `-` is the first character, also
107
+ * adds the method name before the prefix.
108
+ * @param {function|object} [getContext] The function or object that will serve as the 'this' context for handlers
109
+ * of events emitted by this wrapper.
110
+ * @param {boolean} [bubble=false] If `true`, emitted events should also bubble up to the old emitter upon which
111
+ * the `emitter` in the current scope was based (if it defines one).
111
112
  */
112
113
  function inPlace(obj, methods, prefix, getContext, bubble) {
113
114
  if (!prefix) prefix = '';
115
+
114
116
  // If prefix starts with '-' set this boolean to add the method name to the prefix before passing each one to wrap.
115
- var prependMethodPrefix = prefix.charAt(0) === '-';
116
- var fn;
117
- var method;
118
- var i;
119
- for (i = 0; i < methods.length; i++) {
120
- method = methods[i];
121
- fn = obj[method];
117
+ const prependMethodPrefix = prefix.charAt(0) === '-';
118
+ for (let i = 0; i < methods.length; i++) {
119
+ const method = methods[i];
120
+ const fn = obj[method];
122
121
 
123
122
  // Unless fn is both wrappable and unwrapped, bail so we don't add extra properties with undefined values.
124
123
  if (notWrappable(fn)) continue;
@@ -212,32 +211,4 @@ function copy(from, to, emitter) {
212
211
  */
213
212
  function notWrappable(fn) {
214
213
  return !(fn && fn instanceof Function && fn.apply && !fn[flag]);
215
- }
216
-
217
- /**
218
- * Creates a wrapped version of a function using a specified wrapper function. The new wrapped function references the
219
- * original function. Also copies the properties of the original function to the wrapped function.
220
- * @param {function} fn - A function to be wrapped.
221
- * @param {function} wrapper - A higher order function that returns a new function, which executes the function passed
222
- * to the higher order function as an argument.
223
- * @returns {function} A wrapped function with an internal reference to the original function.
224
- */
225
- export function wrapFunction(fn, wrapper) {
226
- var wrapped = wrapper(fn);
227
- wrapped[flag] = fn;
228
- copy(fn, wrapped, ee);
229
- return wrapped;
230
- }
231
-
232
- /**
233
- * Replaces a function with a wrapped version of itself. To preserve object references, rather than taking the function
234
- * itself as an argument, takes the object on which the particular named function is a property.
235
- * @param {Object} obj - The object on which the named function is a property.
236
- * @param {string} fnName - The name of the function to be wrapped.
237
- * @param {function} wrapper - A higher order function that returns a new function, which executes the function passed
238
- * to the higher order function as an argument.
239
- */
240
- export function wrapInPlace(obj, fnName, wrapper) {
241
- var fn = obj[fnName];
242
- obj[fnName] = wrapFunction(fn, wrapper);
243
214
  }
@@ -9,8 +9,7 @@
9
9
 
10
10
  import { ee as baseEE } from '../event-emitter/contextual-ee';
11
11
  import { createWrapperWithEmitter as wfn } from './wrap-function';
12
- import { originals } from '../config/config';
13
- import { isBrowserScope } from '../constants/runtime';
12
+ import { globalScope, isBrowserScope } from '../constants/runtime';
14
13
  const wrapped = {};
15
14
 
16
15
  /**
@@ -29,7 +28,7 @@ export function wrapMutation(sharedEE) {
29
28
  wrapped[ee.debugId] = true; // otherwise, first feature to wrap mutations
30
29
 
31
30
  var wrapFn = wfn(ee);
32
- var OriginalObserver = originals.MO;
31
+ var OriginalObserver = globalScope.MutationObserver;
33
32
  if (OriginalObserver) {
34
33
  window.MutationObserver = function WrappedMutationObserver(cb) {
35
34
  if (this instanceof OriginalObserver) {
@@ -8,8 +8,7 @@
8
8
  */
9
9
 
10
10
  import { createWrapperWithEmitter as wrapFn, flag } from './wrap-function';
11
- import { ee as baseEE, getOrSetContext } from '../event-emitter/contextual-ee';
12
- import { originals } from '../config/config';
11
+ import { ee as baseEE } from '../event-emitter/contextual-ee';
13
12
  import { globalScope } from '../constants/runtime';
14
13
  const wrapped = {};
15
14
 
@@ -28,9 +27,9 @@ export function wrapPromise(sharedEE) {
28
27
  if (wrapped[promiseEE.debugId]) return promiseEE;
29
28
  wrapped[promiseEE.debugId] = true; // otherwise, first feature to wrap promise
30
29
 
31
- var getContext = getOrSetContext;
30
+ var getContext = promiseEE.context;
32
31
  var promiseWrapper = wrapFn(promiseEE);
33
- var prevPromiseObj = originals.PR;
32
+ var prevPromiseObj = globalScope.Promise;
34
33
  if (prevPromiseObj) {
35
34
  // ensure there's a Promise API (native or otherwise) to even wrap
36
35
  wrap();
@@ -11,7 +11,6 @@ import { wrapEvents } from './wrap-events';
11
11
  import { ee as contextualEE } from '../event-emitter/contextual-ee';
12
12
  import { eventListenerOpts } from '../event-listener/event-listener-opts';
13
13
  import { createWrapperWithEmitter as wfn } from './wrap-function';
14
- import { originals } from '../config/config';
15
14
  import { globalScope } from '../constants/runtime';
16
15
  import { warn } from '../util/console';
17
16
  const wrapped = {};
@@ -35,32 +34,28 @@ export function wrapXhr(sharedEE) {
35
34
 
36
35
  wrapEvents(baseEE); // wrap-events patches XMLHttpRequest.prototype.addEventListener for us
37
36
  var wrapFn = wfn(ee);
38
- var OrigXHR = originals.XHR;
39
- var MutationObserver = originals.MO;
40
- var Promise = originals.PR;
41
- var setImmediate = originals.SI;
37
+ var OrigXHR = globalScope.XMLHttpRequest;
38
+ var MutationObserver = globalScope.MutationObserver;
39
+ var Promise = globalScope.Promise;
40
+ var setImmediate = globalScope.setInterval;
42
41
  var READY_STATE_CHANGE = 'readystatechange';
43
42
  var handlers = ['onload', 'onerror', 'onabort', 'onloadstart', 'onloadend', 'onprogress', 'ontimeout'];
44
43
  var pendingXhrs = [];
45
- var activeListeners = globalScope.XMLHttpRequest.listeners;
46
44
  var XHR = globalScope.XMLHttpRequest = newXHR;
47
45
  function newXHR(opts) {
48
- var xhr = new OrigXHR(opts);
49
- this.listeners = activeListeners ? [...activeListeners, intercept] : [intercept];
50
- function intercept() {
46
+ const xhr = new OrigXHR(opts);
47
+ const context = ee.context(xhr);
48
+ try {
49
+ ee.emit('new-xhr', [xhr], context);
50
+ xhr.addEventListener(READY_STATE_CHANGE, wrapXHR(context), eventListenerOpts(false));
51
+ } catch (e) {
52
+ warn('An error occurred while intercepting XHR', e);
51
53
  try {
52
- ee.emit('new-xhr', [xhr], xhr);
53
- xhr.addEventListener(READY_STATE_CHANGE, wrapXHR, eventListenerOpts(false));
54
- } catch (e) {
55
- warn('An error occured while intercepting XHR', e);
56
- try {
57
- ee.emit('internal-error', [e]);
58
- } catch (err) {
59
- // do nothing
60
- }
54
+ ee.emit('internal-error', [e]);
55
+ } catch (err) {
56
+ // do nothing
61
57
  }
62
58
  }
63
- this.listeners.forEach(listener => listener());
64
59
  return xhr;
65
60
  }
66
61
  copy(OrigXHR, XHR);
@@ -74,14 +69,15 @@ export function wrapXhr(sharedEE) {
74
69
  function wrapOnreadystatechange(args, xhr) {
75
70
  wrapFn.inPlace(xhr, ['onreadystatechange'], 'fn-', getObject);
76
71
  }
77
- function wrapXHR() {
78
- var xhr = this;
79
- var ctx = ee.context(xhr);
80
- if (xhr.readyState > 3 && !ctx.resolved) {
81
- ctx.resolved = true;
82
- ee.emit('xhr-resolved', [], xhr);
83
- }
84
- wrapFn.inPlace(xhr, handlers, 'fn-', getObject);
72
+ function wrapXHR(ctx) {
73
+ return function () {
74
+ var xhr = this;
75
+ if (xhr.readyState > 3 && !ctx.resolved) {
76
+ ctx.resolved = true;
77
+ ee.emit('xhr-resolved', [], xhr);
78
+ }
79
+ wrapFn.inPlace(xhr, handlers, 'fn-', getObject);
80
+ };
85
81
  }
86
82
 
87
83
  // Wrapping the onreadystatechange property of XHRs takes some special tricks.
@@ -10,10 +10,6 @@ import { stringify } from '../../../common/util/stringify';
10
10
  export class DT {
11
11
  constructor(agentIdentifier) {
12
12
  this.agentIdentifier = agentIdentifier;
13
- // Binds this class instance context to the following fn used in an external module (exported);
14
- // Alternatively, can make them class field arrow functions, but requires experimental features/plugin for eslint.
15
- this.generateTracePayload = this.generateTracePayload.bind(this);
16
- this.shouldGenerateTrace = this.shouldGenerateTrace.bind(this);
17
13
  }
18
14
  generateTracePayload(parsedOrigin) {
19
15
  if (!this.shouldGenerateTrace(parsedOrigin)) {
@@ -5,7 +5,7 @@
5
5
  import { originals, getLoaderConfig, getRuntime } from '../../../common/config/config';
6
6
  import { handle } from '../../../common/event-emitter/handle';
7
7
  import { id } from '../../../common/ids/id';
8
- import { ffVersion, globalScope } from '../../../common/constants/runtime';
8
+ import { ffVersion, globalScope, isBrowserScope } from '../../../common/constants/runtime';
9
9
  import { dataSize } from '../../../common/util/data-size';
10
10
  import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
11
11
  import { now } from '../../../common/timing/now';
@@ -19,7 +19,7 @@ import { FEATURE_NAMES } from '../../../loaders/features/features';
19
19
  var handlers = ['load', 'error', 'abort', 'timeout'];
20
20
  var handlersLen = handlers.length;
21
21
  var origRequest = originals.REQ;
22
- var origXHR = globalScope.XMLHttpRequest;
22
+ var origXHR = originals.XHR;
23
23
  export class Instrument extends InstrumentBase {
24
24
  static featureName = FEATURE_NAME;
25
25
  constructor(agentIdentifier, aggregator) {
@@ -185,15 +185,25 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
185
185
  function onFetchBeforeStart(args) {
186
186
  var opts = args[1] || {};
187
187
  var url;
188
- // argument is USVString
189
188
  if (typeof args[0] === 'string') {
189
+ // argument is USVString
190
190
  url = args[0];
191
- // argument is Request object
191
+ if (url.length === 0 && isBrowserScope) {
192
+ url = '' + globalScope.location.href;
193
+ }
192
194
  } else if (args[0] && args[0].url) {
195
+ // argument is Request object
193
196
  url = args[0].url;
194
- // argument is URL object
195
197
  } else if (globalScope?.URL && args[0] && args[0] instanceof URL) {
198
+ // argument is URL object
196
199
  url = args[0].href;
200
+ } else if (typeof args[0].toString === 'function') {
201
+ url = args[0].toString();
202
+ }
203
+ if (typeof url !== 'string' || url.length === 0) {
204
+ // Short-circuit DT since we could not determine the URL of the fetch call
205
+ // this is very unlikely to happen
206
+ return;
197
207
  }
198
208
  if (url) {
199
209
  this.parsedOrigin = parseUrl(url);
@@ -203,7 +213,11 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
203
213
  if (!payload || !payload.newrelicHeader && !payload.traceContextParentHeader) {
204
214
  return;
205
215
  }
206
- if (typeof args[0] === 'string' || globalScope?.URL && args[0] && args[0] instanceof URL) {
216
+ if (args[0] && args[0].headers) {
217
+ if (addHeaders(args[0].headers, payload)) {
218
+ this.dt = payload;
219
+ }
220
+ } else {
207
221
  var clone = {};
208
222
  for (var key in opts) {
209
223
  clone[key] = opts[key];
@@ -217,10 +231,6 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
217
231
  } else {
218
232
  args.push(clone);
219
233
  }
220
- } else if (args[0] && args[0].headers) {
221
- if (addHeaders(args[0].headers, payload)) {
222
- this.dt = payload;
223
- }
224
234
  }
225
235
  function addHeaders(headersObj, payload) {
226
236
  var added = false;
@@ -148,6 +148,14 @@ export class Aggregate extends AggregateBase {
148
148
  // Capture metrics for size of custom attributes
149
149
  const jsAttributes = stringify(info.jsAttributes);
150
150
  this.storeSupportabilityMetrics('PageSession/Feature/CustomData/Bytes', jsAttributes === '{}' ? 0 : jsAttributes.length);
151
+
152
+ // Capture metrics for performance markers and measures
153
+ if (typeof performance !== 'undefined') {
154
+ const markers = performance.getEntriesByType('mark');
155
+ const measures = performance.getEntriesByType('measure');
156
+ this.storeSupportabilityMetrics('Generic/Performance/Mark/Seen', markers.length);
157
+ this.storeSupportabilityMetrics('Generic/Performance/Measure/Seen', measures.length);
158
+ }
151
159
  } catch (e) {
152
160
  // do nothing
153
161
  }
@@ -174,11 +174,13 @@ export class Aggregate extends AggregateBase {
174
174
  this.scheduler.startTimer(this.harvestTimeSeconds);
175
175
  }
176
176
  try {
177
+ // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
177
178
  recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
178
179
  } catch (err) {
179
180
  return this.abort();
180
181
  }
181
182
  try {
183
+ // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
182
184
  const {
183
185
  gzipSync,
184
186
  strToU8
@@ -57,9 +57,13 @@ export class Aggregate extends AggregateBase {
57
57
  this.sentTrace = null;
58
58
  this.harvestTimeSeconds = getConfigurationValue(agentIdentifier, 'session_trace.harvestTimeSeconds') || 10;
59
59
  this.maxNodesPerHarvest = getConfigurationValue(agentIdentifier, 'session_trace.maxNodesPerHarvest') || 1000;
60
+ /**
61
+ * Standalone (mode) refers to the legacy version of ST before the idea of 'session' or the Replay feature existed.
62
+ * It has some different behavior vs when used in tandem with replay. */
60
63
  this.isStandalone = false;
61
64
  const operationalGate = new HandlerCache(); // acts as a controller-intermediary that can enable or disable this feature's collection dynamically
62
65
  const sessionEntity = this.agentRuntime.session;
66
+ this.operationalGate = operationalGate;
63
67
 
64
68
  /* --- The following section deals with user sessions concept & contains non-trivial control flow. --- */
65
69
  const controlTraceOp = traceMode => {
@@ -243,17 +247,16 @@ export class Aggregate extends AggregateBase {
243
247
  }
244
248
  }
245
249
  #prepareHarvest(options) {
246
- /* Standalone refers to the legacy version of ST before the idea of 'session' or the Replay feature existed.
247
- It has a different behavior on returning a payload for harvest than when used in tandem with either of those concepts. */
248
250
  if (this.isStandalone) {
249
- if (now() > MAX_TRACE_DURATION) {
250
- // been collecting for over the longest duration we should run for, empty trace object so ST has nothing to send
251
- this.#scheduler.stopTimer();
252
- this.trace = {};
251
+ if (this.ptid && now() >= MAX_TRACE_DURATION) {
252
+ // Perform a final harvest once we hit or exceed the max session trace time
253
+ options.isFinalHarvest = true;
254
+ this.operationalGate.permanentlyDecide(false);
255
+ this.#scheduler.stopTimer(true);
256
+ } else if (this.ptid && this.nodeCount <= REQ_THRESHOLD_TO_SEND && !options.isFinalHarvest) {
257
+ // Only harvest when more than some threshold of nodes are pending, after the very first harvest, with the exception of the last outgoing harvest.
253
258
  return;
254
259
  }
255
- // Only harvest when more than some threshold of nodes are pending, after the very first harvest.
256
- if (this.ptid && this.nodeCount <= REQ_THRESHOLD_TO_SEND) return;
257
260
  } else {
258
261
  // -- *cli May '26 - Update: Not rate limiting backgrounded pages either for now.
259
262
  // if (this.ptid && document.visibilityState === 'hidden' && this.nodeCount <= REQ_THRESHOLD_TO_SEND) return
@@ -443,6 +446,9 @@ export class Aggregate extends AggregateBase {
443
446
  const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
444
447
  if (openedSpace == 0) return;
445
448
  }
449
+ if (this.isStandalone && now() >= MAX_TRACE_DURATION) {
450
+ return;
451
+ }
446
452
  if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
447
453
  this.nodeCount++;
448
454
  }
@@ -398,7 +398,7 @@ export class Aggregate extends AggregateBase {
398
398
  register(FETCH_DONE, function (err, res) {
399
399
  var node = this[SPA_NODE];
400
400
  if (node) {
401
- if (err) {
401
+ if (err || !shouldCollectEvent(this.params)) {
402
402
  node.cancel();
403
403
  return;
404
404
  }