@newrelic/browser-agent 1.252.0 → 1.253.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 (217) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +6 -6
  3. package/dist/cjs/cdn/experimental.js +6 -2
  4. package/dist/cjs/cdn/spa.js +5 -3
  5. package/dist/cjs/common/aggregate/aggregator.js +1 -8
  6. package/dist/cjs/common/config/state/init.js +7 -0
  7. package/dist/cjs/common/constants/env.cdn.js +1 -1
  8. package/dist/cjs/common/constants/env.npm.js +1 -1
  9. package/dist/cjs/common/context/observation-context-manager.js +56 -0
  10. package/dist/cjs/common/event-emitter/contextual-ee.js +12 -9
  11. package/dist/cjs/common/session/constants.js +2 -1
  12. package/dist/cjs/common/session/session-entity.js +3 -1
  13. package/dist/cjs/common/timing/nav-timing.js +8 -3
  14. package/dist/cjs/common/timing/now.js +1 -1
  15. package/dist/cjs/common/util/feature-flags.js +1 -1
  16. package/dist/cjs/common/wrap/index.js +0 -7
  17. package/dist/cjs/common/wrap/wrap-events.js +2 -2
  18. package/dist/cjs/common/wrap/wrap-fetch.js +2 -1
  19. package/dist/cjs/common/wrap/wrap-function.js +5 -7
  20. package/dist/cjs/common/wrap/wrap-promise.js +2 -1
  21. package/dist/cjs/features/ajax/aggregate/index.js +34 -16
  22. package/dist/cjs/features/jserrors/aggregate/index.js +77 -66
  23. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  24. package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +1 -0
  25. package/dist/cjs/features/session_replay/aggregate/index.js +96 -94
  26. package/dist/cjs/features/session_replay/constants.js +5 -1
  27. package/dist/cjs/features/session_replay/instrument/index.js +24 -8
  28. package/dist/cjs/features/session_replay/shared/recorder.js +5 -4
  29. package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +8 -7
  30. package/dist/cjs/features/session_replay/shared/utils.js +26 -0
  31. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +50 -0
  32. package/dist/cjs/features/soft_navigations/aggregate/bel-node.js +29 -0
  33. package/dist/cjs/features/soft_navigations/aggregate/index.js +263 -0
  34. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +62 -0
  35. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +146 -0
  36. package/dist/cjs/features/soft_navigations/constants.js +31 -0
  37. package/dist/cjs/features/soft_navigations/index.js +12 -0
  38. package/dist/cjs/features/soft_navigations/instrument/index.js +79 -0
  39. package/dist/cjs/features/spa/aggregate/index.js +4 -4
  40. package/dist/cjs/features/utils/agent-session.js +2 -1
  41. package/dist/cjs/features/utils/instrument-base.js +6 -9
  42. package/dist/cjs/features/utils/lazy-feature-loader.js +2 -0
  43. package/dist/cjs/loaders/agent-base.js +18 -3
  44. package/dist/cjs/loaders/agent.js +15 -18
  45. package/dist/cjs/loaders/api/api-methods.js +9 -0
  46. package/dist/cjs/loaders/api/api.js +17 -18
  47. package/dist/cjs/loaders/configure/configure.js +5 -2
  48. package/dist/cjs/loaders/features/enabled-features.js +1 -1
  49. package/dist/cjs/loaders/features/features.js +3 -1
  50. package/dist/esm/cdn/experimental.js +5 -2
  51. package/dist/esm/cdn/spa.js +3 -1
  52. package/dist/esm/common/aggregate/aggregator.js +1 -8
  53. package/dist/esm/common/config/state/init.js +7 -0
  54. package/dist/esm/common/constants/env.cdn.js +1 -1
  55. package/dist/esm/common/constants/env.npm.js +1 -1
  56. package/dist/esm/common/context/observation-context-manager.js +49 -0
  57. package/dist/esm/common/event-emitter/contextual-ee.js +12 -9
  58. package/dist/esm/common/session/constants.js +1 -0
  59. package/dist/esm/common/session/session-entity.js +3 -1
  60. package/dist/esm/common/timing/nav-timing.js +8 -3
  61. package/dist/esm/common/timing/now.js +1 -1
  62. package/dist/esm/common/util/feature-flags.js +1 -1
  63. package/dist/esm/common/wrap/index.js +1 -2
  64. package/dist/esm/common/wrap/wrap-events.js +3 -3
  65. package/dist/esm/common/wrap/wrap-fetch.js +3 -2
  66. package/dist/esm/common/wrap/wrap-function.js +4 -5
  67. package/dist/esm/common/wrap/wrap-promise.js +3 -2
  68. package/dist/esm/features/ajax/aggregate/index.js +36 -18
  69. package/dist/esm/features/jserrors/aggregate/index.js +77 -66
  70. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  71. package/dist/esm/features/page_view_event/aggregate/initialized-features.js +1 -0
  72. package/dist/esm/features/session_replay/aggregate/index.js +97 -95
  73. package/dist/esm/features/session_replay/constants.js +4 -0
  74. package/dist/esm/features/session_replay/instrument/index.js +25 -9
  75. package/dist/esm/features/session_replay/shared/recorder.js +5 -4
  76. package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +8 -7
  77. package/dist/esm/features/session_replay/shared/utils.js +17 -0
  78. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +43 -0
  79. package/dist/esm/features/soft_navigations/aggregate/bel-node.js +22 -0
  80. package/dist/esm/features/soft_navigations/aggregate/index.js +256 -0
  81. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +55 -0
  82. package/dist/esm/features/soft_navigations/aggregate/interaction.js +140 -0
  83. package/dist/esm/features/soft_navigations/constants.js +25 -0
  84. package/dist/esm/features/soft_navigations/index.js +1 -0
  85. package/dist/esm/features/soft_navigations/instrument/index.js +73 -0
  86. package/dist/esm/features/spa/aggregate/index.js +4 -4
  87. package/dist/esm/features/utils/agent-session.js +2 -1
  88. package/dist/esm/features/utils/instrument-base.js +7 -10
  89. package/dist/esm/features/utils/lazy-feature-loader.js +2 -0
  90. package/dist/esm/loaders/agent-base.js +18 -3
  91. package/dist/esm/loaders/agent.js +15 -18
  92. package/dist/esm/loaders/api/api-methods.js +3 -0
  93. package/dist/esm/loaders/api/api.js +17 -17
  94. package/dist/esm/loaders/configure/configure.js +5 -2
  95. package/dist/esm/loaders/features/enabled-features.js +1 -1
  96. package/dist/esm/loaders/features/features.js +3 -1
  97. package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
  98. package/dist/types/common/config/state/init.d.ts.map +1 -1
  99. package/dist/types/common/context/event-context.d.ts.map +1 -0
  100. package/dist/types/common/context/observation-context-manager.d.ts +28 -0
  101. package/dist/types/common/context/observation-context-manager.d.ts.map +1 -0
  102. package/dist/types/common/event-emitter/contextual-ee.d.ts +2 -2
  103. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  104. package/dist/types/common/session/constants.d.ts +1 -0
  105. package/dist/types/common/session/constants.d.ts.map +1 -1
  106. package/dist/types/common/session/session-entity.d.ts +0 -1
  107. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  108. package/dist/types/common/timing/nav-timing.d.ts.map +1 -1
  109. package/dist/types/common/wrap/index.d.ts +1 -2
  110. package/dist/types/common/wrap/index.d.ts.map +1 -1
  111. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  112. package/dist/types/common/wrap/wrap-function.d.ts +0 -1
  113. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  114. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  115. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  116. package/dist/types/features/jserrors/aggregate/index.d.ts +4 -3
  117. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  118. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
  119. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  120. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  121. package/dist/types/features/session_replay/constants.d.ts +4 -0
  122. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  123. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  124. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  125. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
  126. package/dist/types/features/session_replay/shared/utils.d.ts +4 -0
  127. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -0
  128. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +19 -0
  129. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -0
  130. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts +16 -0
  131. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts.map +1 -0
  132. package/dist/types/features/soft_navigations/aggregate/index.d.ts +36 -0
  133. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -0
  134. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts +12 -0
  135. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -0
  136. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +50 -0
  137. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -0
  138. package/dist/types/features/soft_navigations/constants.d.ts +20 -0
  139. package/dist/types/features/soft_navigations/constants.d.ts.map +1 -0
  140. package/dist/types/features/soft_navigations/index.d.ts +2 -0
  141. package/dist/types/features/soft_navigations/index.d.ts.map +1 -0
  142. package/dist/types/features/soft_navigations/instrument/index.d.ts +7 -0
  143. package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -0
  144. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  145. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  146. package/dist/types/features/utils/instrument-base.d.ts +1 -7
  147. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  148. package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -1
  149. package/dist/types/loaders/agent-base.d.ts +5 -1
  150. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  151. package/dist/types/loaders/agent.d.ts +2 -2
  152. package/dist/types/loaders/agent.d.ts.map +1 -1
  153. package/dist/types/loaders/api/api-methods.d.ts +3 -0
  154. package/dist/types/loaders/api/api-methods.d.ts.map +1 -0
  155. package/dist/types/loaders/api/api.d.ts +3 -6
  156. package/dist/types/loaders/api/api.d.ts.map +1 -1
  157. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  158. package/dist/types/loaders/features/features.d.ts +1 -0
  159. package/dist/types/loaders/features/features.d.ts.map +1 -1
  160. package/dist/types/loaders/micro-agent.d.ts +0 -1
  161. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  162. package/package.json +1 -1
  163. package/src/cdn/experimental.js +4 -2
  164. package/src/cdn/spa.js +3 -1
  165. package/src/common/aggregate/aggregator.js +2 -11
  166. package/src/common/config/state/init.js +3 -1
  167. package/src/common/context/observation-context-manager.js +55 -0
  168. package/src/common/event-emitter/contextual-ee.js +20 -10
  169. package/src/common/session/constants.js +1 -0
  170. package/src/common/session/session-entity.js +3 -1
  171. package/src/common/timing/nav-timing.js +7 -3
  172. package/src/common/timing/now.js +1 -1
  173. package/src/common/util/feature-flags.js +1 -1
  174. package/src/common/wrap/index.js +1 -2
  175. package/src/common/wrap/wrap-events.js +3 -3
  176. package/src/common/wrap/wrap-fetch.js +3 -2
  177. package/src/common/wrap/wrap-function.js +4 -6
  178. package/src/common/wrap/wrap-promise.js +3 -2
  179. package/src/features/ajax/aggregate/index.js +36 -18
  180. package/src/features/jserrors/aggregate/index.js +70 -73
  181. package/src/features/page_view_event/aggregate/index.js +1 -1
  182. package/src/features/page_view_event/aggregate/initialized-features.js +1 -0
  183. package/src/features/session_replay/aggregate/index.js +92 -95
  184. package/src/features/session_replay/constants.js +5 -0
  185. package/src/features/session_replay/instrument/index.js +24 -9
  186. package/src/features/session_replay/shared/recorder.js +5 -4
  187. package/src/features/session_replay/shared/stylesheet-evaluator.js +8 -7
  188. package/src/features/session_replay/shared/utils.js +19 -0
  189. package/src/features/soft_navigations/aggregate/ajax-node.js +57 -0
  190. package/src/features/soft_navigations/aggregate/bel-node.js +26 -0
  191. package/src/features/soft_navigations/aggregate/index.js +254 -0
  192. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +53 -0
  193. package/src/features/soft_navigations/aggregate/interaction.js +159 -0
  194. package/src/features/soft_navigations/constants.js +29 -0
  195. package/src/features/soft_navigations/index.js +1 -0
  196. package/src/features/soft_navigations/instrument/index.js +67 -0
  197. package/src/features/spa/aggregate/index.js +5 -4
  198. package/src/features/utils/agent-session.js +2 -1
  199. package/src/features/utils/instrument-base.js +7 -10
  200. package/src/features/utils/lazy-feature-loader.js +2 -0
  201. package/src/loaders/agent-base.js +18 -3
  202. package/src/loaders/agent.js +18 -17
  203. package/src/loaders/api/api-methods.js +12 -0
  204. package/src/loaders/api/api.js +17 -28
  205. package/src/loaders/configure/configure.js +4 -1
  206. package/src/loaders/features/enabled-features.js +1 -1
  207. package/src/loaders/features/features.js +3 -1
  208. package/dist/cjs/common/wrap/wrap-raf.js +0 -55
  209. package/dist/esm/common/wrap/wrap-raf.js +0 -48
  210. package/dist/types/common/event-emitter/event-context.d.ts.map +0 -1
  211. package/dist/types/common/wrap/wrap-raf.d.ts +0 -16
  212. package/dist/types/common/wrap/wrap-raf.d.ts.map +0 -1
  213. package/src/common/wrap/wrap-raf.js +0 -52
  214. /package/dist/cjs/common/{event-emitter → context}/event-context.js +0 -0
  215. /package/dist/esm/common/{event-emitter → context}/event-context.js +0 -0
  216. /package/dist/types/common/{event-emitter → context}/event-context.d.ts +0 -0
  217. /package/src/common/{event-emitter → context}/event-context.js +0 -0
@@ -6,11 +6,9 @@
6
6
  import { gosNREUM } from '../window/nreum';
7
7
  import { getOrSet } from '../util/get-or-set';
8
8
  import { getRuntime } from '../config/config';
9
- import { EventContext } from './event-context';
10
- import { bundleId } from '../ids/bundle-id';
9
+ import { EventContext } from '../context/event-context';
10
+ import { ObservationContextManager } from '../context/observation-context-manager';
11
11
 
12
- // create a unique id to store event context data for the current agent bundle
13
- const contextId = "nr@context:".concat(bundleId);
14
12
  // create global emitter instance that can be shared among bundles
15
13
  const globalInstance = ee(undefined, 'globalEE');
16
14
 
@@ -19,7 +17,7 @@ const nr = gosNREUM();
19
17
  if (!nr.ee) {
20
18
  nr.ee = globalInstance;
21
19
  }
22
- export { globalInstance as ee, contextId };
20
+ export { globalInstance as ee };
23
21
  function ee(old, debugId) {
24
22
  var handlers = {};
25
23
  var bufferGroupMap = {};
@@ -49,16 +47,17 @@ function ee(old, debugId) {
49
47
  aborted: false,
50
48
  isBuffering,
51
49
  debugId,
52
- backlog: isolatedBacklog ? {} : old && typeof old.backlog === 'object' ? old.backlog : {}
50
+ backlog: isolatedBacklog ? {} : old && typeof old.backlog === 'object' ? old.backlog : {},
51
+ observationContextManager: null
53
52
  };
54
53
  return emitter;
55
54
  function context(contextOrStore) {
56
55
  if (contextOrStore && contextOrStore instanceof EventContext) {
57
56
  return contextOrStore;
58
57
  } else if (contextOrStore) {
59
- return getOrSet(contextOrStore, contextId, () => new EventContext(contextId));
58
+ return getOrSet(contextOrStore, ObservationContextManager.contextId, () => emitter.observationContextManager ? emitter.observationContextManager.getCreateContext(contextOrStore) : new EventContext(ObservationContextManager.contextId));
60
59
  } else {
61
- return new EventContext(contextId);
60
+ return emitter.observationContextManager ? emitter.observationContextManager.getCreateContext({}) : new EventContext(ObservationContextManager.contextId);
62
61
  }
63
62
  }
64
63
  function emit(type, args, contextOrStore, force, bubble) {
@@ -102,7 +101,11 @@ function ee(old, debugId) {
102
101
  return handlers[type] || [];
103
102
  }
104
103
  function getOrCreate(name) {
105
- return emitters[name] = emitters[name] || ee(emitter, name);
104
+ const newEventEmitter = emitters[name] = emitters[name] || ee(emitter, name);
105
+ if (!newEventEmitter.observationContextManager && emitter.observationContextManager) {
106
+ newEventEmitter.observationContextManager = emitter.observationContextManager;
107
+ }
108
+ return newEventEmitter;
106
109
  }
107
110
  function bufferEventsByGroup(types, group) {
108
111
  const eventBuffer = getBuffer();
@@ -1,4 +1,5 @@
1
1
  export const PREFIX = 'NRBA';
2
+ export const DEFAULT_KEY = 'SESSION';
2
3
  export const DEFAULT_EXPIRES_MS = 14400000;
3
4
  export const DEFAULT_INACTIVE_MS = 1800000;
4
5
  export const SESSION_EVENTS = {
@@ -130,7 +130,9 @@ export class SessionEntity {
130
130
 
131
131
  // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
132
132
  // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
133
- this.isNew = !Object.keys(initialRead).length;
133
+ /* [NR-230914] 02/2024 - the logical OR assignment is used so that isNew remains 'true' if it was already set as such. This fixes the expires and inactive timestamps timing out in localStorage
134
+ while no page for a given domain is in-use and the session resetting upon user returning to the page as part of a fresh session. */
135
+ this.isNew ||= !Object.keys(initialRead).length;
134
136
  // if its a "new" session, we write to storage API with the default values. These values may change over the lifespan of the agent run.
135
137
  // we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
136
138
  if (this.isNew) this.write(getModeledObject(this.state, model), true);else this.sync(initialRead);
@@ -24,7 +24,7 @@ var REQUEST = 'request';
24
24
  var RESPONSE = 'response';
25
25
  var LOAD_EVENT = 'loadEvent';
26
26
  var DOM_CONTENT_LOAD_EVENT = 'domContentLoadedEvent';
27
- export var navTimingValues = [];
27
+ export const navTimingValues = [];
28
28
  function getPntType(type) {
29
29
  if (typeof type === 'number') return type;
30
30
  const types = {
@@ -70,6 +70,11 @@ export function addPN(pn, v) {
70
70
  handleValue(pn.redirectCount, v, 'rc');
71
71
  return v;
72
72
  }
73
+
74
+ /**
75
+ * By side effect, this modifies 'obj' with a mapping of the 'prop' provided to a 'value', and invalid values are not added.
76
+ * On the other hand, the local navTimingValues array gets the value appended if valid and 'undefined' appended if invalid, regardless.
77
+ */
73
78
  function handleValue(value, obj, prop, isOldApi) {
74
79
  /*
75
80
  For L2 Timing API, the value will already be a relative-to-previous-document DOMHighResTimeStamp.
@@ -84,6 +89,6 @@ function handleValue(value, obj, prop, isOldApi) {
84
89
  }
85
90
  value = Math.round(value);
86
91
  obj[prop] = value;
87
- }
88
- navTimingValues.push(value);
92
+ navTimingValues.push(value);
93
+ } else navTimingValues.push(undefined);
89
94
  }
@@ -5,5 +5,5 @@
5
5
 
6
6
  // This is our own layer around performance.now. It's not strictly necessary, but we keep it in case of future mod-ing of the value for refactor purpose.
7
7
  export function now() {
8
- return Math.round(performance.now());
8
+ return Math.floor(performance.now());
9
9
  }
@@ -10,7 +10,7 @@ const bucketMap = {
10
10
  stn: [FEATURE_NAMES.sessionTrace],
11
11
  err: [FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics],
12
12
  ins: [FEATURE_NAMES.pageAction],
13
- spa: [FEATURE_NAMES.spa],
13
+ spa: [FEATURE_NAMES.spa, FEATURE_NAMES.softNav],
14
14
  sr: [FEATURE_NAMES.sessionReplay, FEATURE_NAMES.sessionTrace]
15
15
  };
16
16
  const sentIds = new Set();
@@ -8,7 +8,6 @@ import { wrapHistory } from './wrap-history';
8
8
  import { wrapJsonP } from './wrap-jsonp';
9
9
  import { wrapMutation } from './wrap-mutation';
10
10
  import { wrapPromise } from './wrap-promise';
11
- import { wrapRaf } from './wrap-raf';
12
11
  import { wrapTimer } from './wrap-timer';
13
12
  import { wrapXhr } from './wrap-xhr';
14
- export { wrapEvents, wrapFetch, wrapHistory, wrapJsonP, wrapMutation, wrapPromise, wrapRaf, wrapTimer, wrapXhr };
13
+ export { wrapEvents, wrapFetch, wrapHistory, wrapJsonP, wrapMutation, wrapPromise, wrapTimer, wrapXhr };
@@ -7,15 +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, contextId } from '../event-emitter/contextual-ee';
10
+ import { ee as baseEE } 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
+ import { ObservationContextManager } from '../context/observation-context-manager';
14
15
  const wrapped = {};
15
16
  const XHR = globalScope.XMLHttpRequest;
16
17
  const ADD_EVENT_LISTENER = 'addEventListener';
17
18
  const REMOVE_EVENT_LISTENER = 'removeEventListener';
18
- const flag = "nr@wrapped:".concat(contextId);
19
19
 
20
20
  /**
21
21
  * Wraps `addEventListener` and `removeEventListener` on: global scope; the prototype of `XMLHttpRequest`, and
@@ -44,7 +44,7 @@ export function wrapEvents(sharedEE) {
44
44
  if (originalListener === null || typeof originalListener !== 'function' && typeof originalListener !== 'object') {
45
45
  return;
46
46
  }
47
- var wrapped = getOrSet(originalListener, flag, function () {
47
+ var wrapped = getOrSet(originalListener, ObservationContextManager.contextWrappedId, function () {
48
48
  var listener = {
49
49
  object: wrapHandleEvent,
50
50
  function: originalListener
@@ -6,8 +6,9 @@
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, contextId } from '../event-emitter/contextual-ee';
9
+ import { ee as baseEE } from '../event-emitter/contextual-ee';
10
10
  import { globalScope } from '../constants/runtime';
11
+ import { ObservationContextManager } from '../context/observation-context-manager';
11
12
  var prefix = 'fetch-';
12
13
  var bodyPrefix = prefix + 'body-';
13
14
  var bodyMethods = ['arrayBuffer', 'blob', 'json', 'text', 'formData'];
@@ -70,7 +71,7 @@ export function wrapFetch(sharedEE) {
70
71
  // we are wrapping args in an array so we can preserve the reference
71
72
  ee.emit(prefix + 'before-start', [args], ctx);
72
73
  var dtPayload;
73
- if (ctx[contextId] && ctx[contextId].dt) dtPayload = ctx[contextId].dt;
74
+ if (ctx[ObservationContextManager.contextId] && ctx[ObservationContextManager.contextId].dt) dtPayload = ctx[ObservationContextManager.contextId].dt;
74
75
  var origPromiseFromFetch = fn.apply(this, args);
75
76
  ee.emit(prefix + 'start', [args, dtPayload], origPromiseFromFetch);
76
77
 
@@ -7,8 +7,7 @@
7
7
  */
8
8
 
9
9
  import { ee } from '../event-emitter/contextual-ee';
10
- import { bundleId } from '../ids/bundle-id';
11
- export const flag = "nr@original:".concat(bundleId);
10
+ import { ObservationContextManager } from '../context/observation-context-manager';
12
11
 
13
12
  /**
14
13
  * A convenience alias of `hasOwnProperty`.
@@ -40,7 +39,7 @@ export function createWrapperWithEmitter(emitter, always) {
40
39
  * As a property on a wrapped function, contains the original function.
41
40
  * @type {string}
42
41
  */
43
- wrapFn.flag = flag;
42
+ wrapFn.flag = ObservationContextManager.contextOriginalId;
44
43
  return wrapFn;
45
44
 
46
45
  /**
@@ -56,7 +55,7 @@ export function createWrapperWithEmitter(emitter, always) {
56
55
  // Unless fn is both wrappable and unwrapped, return it unchanged.
57
56
  if (notWrappable(fn)) return fn;
58
57
  if (!prefix) prefix = '';
59
- nrWrapper[flag] = fn;
58
+ nrWrapper[ObservationContextManager.contextOriginalId] = fn;
60
59
  copy(fn, nrWrapper, emitter);
61
60
  return nrWrapper;
62
61
 
@@ -210,5 +209,5 @@ function copy(from, to, emitter) {
210
209
  * @returns {boolean} Whether the passed function is ineligible to be wrapped.
211
210
  */
212
211
  function notWrappable(fn) {
213
- return !(fn && typeof fn === 'function' && fn.apply && !fn[flag]);
212
+ return !(fn && typeof fn === 'function' && fn.apply && !fn[ObservationContextManager.contextOriginalId]);
214
213
  }
@@ -7,9 +7,10 @@
7
7
  * This module is used by: spa.
8
8
  */
9
9
 
10
- import { createWrapperWithEmitter as wrapFn, flag } from './wrap-function';
10
+ import { createWrapperWithEmitter as wrapFn } from './wrap-function';
11
11
  import { ee as baseEE } from '../event-emitter/contextual-ee';
12
12
  import { globalScope } from '../constants/runtime';
13
+ import { ObservationContextManager } from '../context/observation-context-manager';
13
14
  const wrapped = {};
14
15
 
15
16
  /**
@@ -118,7 +119,7 @@ export function wrapPromise(sharedEE) {
118
119
  promiseEE.emit('propagate', [originalThis, true], origFnCallWithThis, false, false);
119
120
  return origFnCallWithThis;
120
121
  };
121
- prevPromiseObj.prototype.then[flag] = prevPromiseOrigThen;
122
+ prevPromiseObj.prototype.then[ObservationContextManager.contextOriginalId] = prevPromiseOrigThen;
122
123
  promiseEE.on('executor-start', function (args) {
123
124
  args[0] = promiseWrapper(args[0], 'resolve-', this, null, false);
124
125
  args[1] = promiseWrapper(args[1], 'resolve-', this, null, false);
@@ -2,7 +2,7 @@
2
2
  * Copyright 2020 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { registerHandler as register } from '../../../common/event-emitter/register-handler';
5
+ import { registerHandler } from '../../../common/event-emitter/register-handler';
6
6
  import { stringify } from '../../../common/util/stringify';
7
7
  import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer';
8
8
  import { handle } from '../../../common/event-emitter/handle';
@@ -14,13 +14,14 @@ import { FEATURE_NAMES } from '../../../loaders/features/features';
14
14
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
15
15
  import { AggregateBase } from '../../utils/aggregate-base';
16
16
  import { parseGQL } from './gql';
17
+ import { getNREUMInitializedAgent } from '../../../common/window/nreum';
17
18
  export class Aggregate extends AggregateBase {
18
19
  static featureName = FEATURE_NAME;
19
20
  constructor(agentIdentifier, aggregator) {
20
21
  super(agentIdentifier, aggregator, FEATURE_NAME);
21
22
  const agentInit = getConfiguration(agentIdentifier);
22
23
  const allAjaxIsEnabled = agentInit.ajax.enabled !== false;
23
- register('xhr', storeXhr, this.featureName, this.ee);
24
+ registerHandler('xhr', storeXhr, this.featureName, this.ee);
24
25
  if (!allAjaxIsEnabled) {
25
26
  this.drain();
26
27
  return; // feature will only collect timeslice metrics & ajax trace nodes if it's not fully enabled
@@ -43,19 +44,23 @@ export class Aggregate extends AggregateBase {
43
44
  spaAjaxEvents
44
45
  };
45
46
  };
46
- ee.on('interactionSaved', interaction => {
47
- if (!spaAjaxEvents[interaction.id]) return;
48
- // remove from the spaAjaxEvents buffer, and let spa harvest it
49
- delete spaAjaxEvents[interaction.id];
50
- });
51
- ee.on('interactionDiscarded', interaction => {
47
+
48
+ // --- v Used by old spa feature
49
+ ee.on('interactionDone', (interaction, wasSaved) => {
52
50
  if (!spaAjaxEvents[interaction.id]) return;
53
- spaAjaxEvents[interaction.id].forEach(function (item) {
54
- // move it from the spaAjaxEvents buffer to the ajaxEvents buffer for harvesting here
55
- ajaxEvents.push(item);
56
- });
51
+ if (!wasSaved) {
52
+ // if the ixn was saved, then its ajax reqs are part of the payload whereas if it was discarded, it should still be harvested in the ajax feature itself
53
+ spaAjaxEvents[interaction.id].forEach(function (item) {
54
+ ajaxEvents.push(item);
55
+ });
56
+ }
57
57
  delete spaAjaxEvents[interaction.id];
58
58
  });
59
+ // --- ^
60
+ // --- v Used by new soft nav
61
+ registerHandler('returnAjax', event => ajaxEvents.push(event), this.featureName, this.ee);
62
+ // --- ^
63
+
59
64
  const scheduler = new HarvestScheduler('events', {
60
65
  onFinished: onEventsHarvestFinished,
61
66
  getPayload: prepareHarvest
@@ -76,16 +81,26 @@ export class Aggregate extends AggregateBase {
76
81
  } else {
77
82
  hash = stringify([params.status, params.host, params.pathname]);
78
83
  }
84
+ const shouldCollect = shouldCollectEvent(params);
85
+ const ajaxMetricDenyListEnabled = agentInit.feature_flags?.includes('ajax_metrics_deny_list');
79
86
 
80
87
  // store as metric
81
- aggregator.store('xhr', hash, params, metrics);
88
+ if (shouldCollect || !ajaxMetricDenyListEnabled) {
89
+ aggregator.store('xhr', hash, params, metrics);
90
+ }
82
91
  if (!allAjaxIsEnabled) return;
83
- if (!shouldCollectEvent(params)) {
92
+ if (!shouldCollect) {
84
93
  if (params.hostname === beacon || proxyBeacon && params.hostname === proxyBeacon) {
85
94
  // This doesn't make a distinction if the same-domain request is going to a different port or path...
86
95
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/Agent'], undefined, FEATURE_NAMES.metrics, ee);
96
+ if (ajaxMetricDenyListEnabled) {
97
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Metrics/Excluded/Agent'], undefined, FEATURE_NAMES.metrics, ee);
98
+ }
87
99
  } else {
88
100
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/App'], undefined, FEATURE_NAMES.metrics, ee);
101
+ if (ajaxMetricDenyListEnabled) {
102
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Metrics/Excluded/App'], undefined, FEATURE_NAMES.metrics, ee);
103
+ }
89
104
  }
90
105
  return;
91
106
  }
@@ -115,10 +130,13 @@ export class Aggregate extends AggregateBase {
115
130
  query: this?.parsedOrigin?.search
116
131
  });
117
132
  if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, ee);
118
-
119
- // if the ajax happened inside an interaction, hold it until the interaction finishes
120
- if (this.spaNode) {
121
- var interactionId = this.spaNode.interaction.id;
133
+ const softNavInUse = Boolean(getNREUMInitializedAgent(agentIdentifier)?.features?.[FEATURE_NAMES.softNav]);
134
+ if (softNavInUse) {
135
+ // For newer soft nav (when running), pass the event to it for evaluation -- either part of an interaction or is given back
136
+ handle('ajax', [event], undefined, FEATURE_NAMES.softNav, ee);
137
+ } else if (this.spaNode) {
138
+ // For old spa (when running), if the ajax happened inside an interaction, hold it until the interaction finishes
139
+ const interactionId = this.spaNode.interaction.id;
122
140
  spaAjaxEvents[interactionId] = spaAjaxEvents[interactionId] || [];
123
141
  spaAjaxEvents[interactionId].push(event);
124
142
  } else {
@@ -18,6 +18,7 @@ import { globalScope } from '../../../common/constants/runtime';
18
18
  import { FEATURE_NAME } from '../constants';
19
19
  import { FEATURE_NAMES } from '../../../loaders/features/features';
20
20
  import { AggregateBase } from '../../utils/aggregate-base';
21
+ import { getNREUMInitializedAgent } from '../../../common/window/nreum';
21
22
 
22
23
  /**
23
24
  * @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
@@ -32,21 +33,20 @@ export class Aggregate extends AggregateBase {
32
33
  this.stackReported = {};
33
34
  this.observedAt = {};
34
35
  this.pageviewReported = {};
35
- this.errorCache = {};
36
+ this.bufferedErrorsUnderSpa = {};
36
37
  this.currentBody = undefined;
37
38
  this.errorOnPage = false;
38
39
 
39
40
  // this will need to change to match whatever ee we use in the instrument
40
- this.ee.on('interactionSaved', interaction => this.onInteractionSaved(interaction));
41
-
42
- // this will need to change to match whatever ee we use in the instrument
43
- this.ee.on('interactionDiscarded', interaction => this.onInteractionDiscarded(interaction));
41
+ this.ee.on('interactionDone', (interaction, wasSaved) => this.onInteractionDone(interaction, wasSaved));
44
42
  register('err', function () {
45
43
  return _this.storeError(...arguments);
46
44
  }, this.featureName, this.ee);
47
45
  register('ierr', function () {
48
46
  return _this.storeError(...arguments);
49
47
  }, this.featureName, this.ee);
48
+ register('softNavFlush', (interactionId, wasFinished, softNavAttrs) => this.onSoftNavNotification(interactionId, wasFinished, softNavAttrs), this.featureName, this.ee); // when an ixn is done or cancelled
49
+
50
50
  const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'jserrors.harvestTimeSeconds') || 10;
51
51
  const scheduler = new HarvestScheduler('jserrors', {
52
52
  onFinished: function () {
@@ -188,81 +188,92 @@ export class Aggregate extends AggregateBase {
188
188
  time
189
189
  };
190
190
 
191
- // sr, stn and spa aggregators listen to this event - stn sends the error in its payload,
192
- // and spa annotates the error with interaction info
193
- const msg = [type, bucketHash, params, newMetrics];
194
- handle('errorAgg', msg, undefined, FEATURE_NAMES.sessionTrace, this.ee);
195
- handle('errorAgg', msg, undefined, FEATURE_NAMES.spa, this.ee);
196
- handle('errorAgg', msg, undefined, FEATURE_NAMES.sessionReplay, this.ee);
197
-
191
+ // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
192
+ const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
193
+ handle('errorAgg', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
194
+ handle('errorAgg', jsErrorEvent, undefined, FEATURE_NAMES.sessionReplay, this.ee);
198
195
  // still send EE events for other features such as above, but stop this one from aggregating internal data
199
196
  if (this.blocked) return;
200
- var att = getInfo(this.agentIdentifier).jsAttributes;
201
- if (params._interactionId != null) {
202
- // hold on to the error until the interaction finishes
203
- this.errorCache[params._interactionId] = this.errorCache[params._interactionId] || [];
204
- this.errorCache[params._interactionId].push([type, bucketHash, params, newMetrics, att, customAttributes]);
197
+ const softNavInUse = Boolean(getNREUMInitializedAgent(this.agentIdentifier)?.features[FEATURE_NAMES.softNav]);
198
+ // Note: the following are subject to potential race cond wherein if the other feature aren't fully initialized, it'll be treated as there being no associated interaction.
199
+ // They each will also tack on their respective properties to the params object as part of the decision flow.
200
+ if (softNavInUse) handle('jserror', [params, time], undefined, FEATURE_NAMES.softNav, this.ee);else handle('errorAgg', jsErrorEvent, undefined, FEATURE_NAMES.spa, this.ee);
201
+ if (params.browserInteractionId && !params._softNavFinished) {
202
+ // hold onto the error until the in-progress interaction is done, eithered saved or discarded
203
+ this.bufferedErrorsUnderSpa[params.browserInteractionId] ??= [];
204
+ this.bufferedErrorsUnderSpa[params.browserInteractionId].push(jsErrorEvent);
205
+ } else if (params._interactionId != null) {
206
+ // same as above, except tailored for the way old spa does it
207
+ this.bufferedErrorsUnderSpa[params._interactionId] = this.bufferedErrorsUnderSpa[params._interactionId] || [];
208
+ this.bufferedErrorsUnderSpa[params._interactionId].push(jsErrorEvent);
205
209
  } else {
206
- // store custom attributes
207
- var customParams = {};
208
- mapOwn(att, setCustom);
209
- if (customAttributes) {
210
- mapOwn(customAttributes, setCustom);
211
- }
212
- var jsAttributesHash = stringHashCode(stringify(customParams));
213
- var aggregateHash = bucketHash + ':' + jsAttributesHash;
214
- this.aggregator.store(type, aggregateHash, params, newMetrics, customParams);
210
+ // Either there is no interaction (then all these params properties will be undefined) OR there's a related soft navigation that's already completed.
211
+ // The old spa does not look up completed interactions at all, so there's no need to consider it.
212
+ this.#storeJserrorForHarvest(jsErrorEvent, params.browserInteractionId !== undefined, params._softNavAttributes);
215
213
  }
214
+ }
215
+ #storeJserrorForHarvest(errorInfoArr, softNavOccurredFinished) {
216
+ let softNavCustomAttrs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
217
+ let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr;
218
+ const allCustomAttrs = {};
219
+ if (softNavOccurredFinished) {
220
+ Object.entries(softNavCustomAttrs).forEach(_ref => {
221
+ let [k, v] = _ref;
222
+ return setCustom(k, v);
223
+ }); // when an ixn finishes, it'll include stuff in jsAttributes + attrs specific to the ixn
224
+ bucketHash += params.browserInteractionId;
225
+ delete params._softNavAttributes; // cleanup temp properties from synchronous evaluation; this is harmless when async from soft nav (properties DNE)
226
+ delete params._softNavFinished;
227
+ } else {
228
+ // interaction was cancelled -> error should not be associated OR there was no interaction
229
+ Object.entries(getInfo(this.agentIdentifier).jsAttributes).forEach(_ref2 => {
230
+ let [k, v] = _ref2;
231
+ return setCustom(k, v);
232
+ });
233
+ delete params.browserInteractionId;
234
+ }
235
+ if (localAttrs) Object.entries(localAttrs).forEach(_ref3 => {
236
+ let [k, v] = _ref3;
237
+ return setCustom(k, v);
238
+ }); // local custom attrs are applied in either case with the highest precedence
239
+
240
+ const jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
241
+ const aggregateHash = bucketHash + ':' + jsAttributesHash;
242
+ this.aggregator.store(type, aggregateHash, params, newMetrics, allCustomAttrs);
216
243
  function setCustom(key, val) {
217
- customParams[key] = val && typeof val === 'object' ? stringify(val) : val;
244
+ allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
218
245
  }
219
246
  }
220
- onInteractionSaved(interaction) {
221
- if (!this.errorCache[interaction.id] || this.blocked) return;
222
- this.errorCache[interaction.id].forEach(item => {
223
- var customParams = {};
224
- var globalCustomParams = item[4];
225
- var localCustomParams = item[5];
226
- mapOwn(globalCustomParams, setCustom);
227
- mapOwn(interaction.root.attrs.custom, setCustom);
228
- mapOwn(localCustomParams, setCustom);
247
+
248
+ // TO-DO: Remove this function when old spa is taken out. #storeJserrorForHarvest handles the work with the softnav feature.
249
+ onInteractionDone(interaction, wasSaved) {
250
+ if (!this.bufferedErrorsUnderSpa[interaction.id] || this.blocked) return;
251
+ this.bufferedErrorsUnderSpa[interaction.id].forEach(item => {
252
+ var allCustomAttrs = {};
253
+ const localCustomAttrs = item[4];
254
+ mapOwn(interaction.root.attrs.custom, setCustom); // tack on custom attrs from the interaction
255
+ mapOwn(localCustomAttrs, setCustom);
229
256
  var params = item[2];
230
- params.browserInteractionId = interaction.root.attrs.id;
231
- delete params._interactionId;
232
- if (params._interactionNodeId) {
233
- params.parentNodeId = params._interactionNodeId.toString();
234
- delete params._interactionNodeId;
235
- }
236
- var hash = item[1] + interaction.root.attrs.id;
237
- var jsAttributesHash = stringHashCode(stringify(customParams));
238
- var aggregateHash = hash + ':' + jsAttributesHash;
239
- this.aggregator.store(item[0], aggregateHash, params, item[3], customParams);
240
- function setCustom(key, val) {
241
- customParams[key] = val && typeof val === 'object' ? stringify(val) : val;
257
+ if (wasSaved) {
258
+ params.browserInteractionId = interaction.root.attrs.id;
259
+ if (params._interactionNodeId) params.parentNodeId = params._interactionNodeId.toString();
242
260
  }
243
- });
244
- delete this.errorCache[interaction.id];
245
- }
246
- onInteractionDiscarded(interaction) {
247
- if (!this.errorCache || !this.errorCache[interaction.id] || this.blocked) return;
248
- this.errorCache[interaction.id].forEach(item => {
249
- var customParams = {};
250
- var globalCustomParams = item[4];
251
- var localCustomParams = item[5];
252
- mapOwn(globalCustomParams, setCustom);
253
- mapOwn(interaction.root.attrs.custom, setCustom);
254
- mapOwn(localCustomParams, setCustom);
255
- var params = item[2];
256
261
  delete params._interactionId;
257
262
  delete params._interactionNodeId;
258
- var hash = item[1];
259
- var jsAttributesHash = stringHashCode(stringify(customParams));
263
+ var hash = wasSaved ? item[1] + interaction.root.attrs.id : item[1];
264
+ var jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
260
265
  var aggregateHash = hash + ':' + jsAttributesHash;
261
- this.aggregator.store(item[0], aggregateHash, item[2], item[3], customParams);
266
+ this.aggregator.store(item[0], aggregateHash, params, item[3], allCustomAttrs);
262
267
  function setCustom(key, val) {
263
- customParams[key] = val && typeof val === 'object' ? stringify(val) : val;
268
+ allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
264
269
  }
265
270
  });
266
- delete this.errorCache[interaction.id];
271
+ delete this.bufferedErrorsUnderSpa[interaction.id];
272
+ }
273
+ onSoftNavNotification(interactionId, wasFinished, softNavAttrs) {
274
+ if (this.blocked) return;
275
+ this.bufferedErrorsUnderSpa[interactionId]?.forEach(jsErrorEvent => this.#storeJserrorForHarvest(jsErrorEvent, wasFinished, softNavAttrs) // this should not modify the re-used softNavAttrs contents
276
+ );
277
+ delete this.bufferedErrorsUnderSpa[interactionId]; // wipe the list of jserrors so they aren't duplicated by another call to the same id
267
278
  }
268
279
  }
@@ -129,7 +129,7 @@ export class Aggregate extends AggregateBase {
129
129
  this.drain();
130
130
  } catch (err) {
131
131
  this.ee.abort();
132
- warn('RUM call failed. Agent shutting down.');
132
+ warn('RUM call failed. Agent shutting down.', err);
133
133
  }
134
134
  }
135
135
  });
@@ -25,6 +25,7 @@ export function getActivatedFeaturesFlags(agentId) {
25
25
  case FEATURE_NAMES.sessionTrace:
26
26
  flagArr.push('stn');
27
27
  break;
28
+ case FEATURE_NAMES.softNav:
28
29
  case FEATURE_NAMES.spa:
29
30
  flagArr.push('spa');
30
31
  break;