@newrelic/browser-agent 1.253.0 → 1.254.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 (159) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/dist/cjs/cdn/polyfills.js +2 -1
  4. package/dist/cjs/common/config/state/runtime.js +4 -1
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/drain/drain.js +41 -27
  8. package/dist/cjs/common/event-emitter/contextual-ee.js +24 -22
  9. package/dist/cjs/common/harvest/harvest.js +5 -1
  10. package/dist/cjs/common/timing/time-keeper.js +94 -0
  11. package/dist/cjs/common/util/feature-flags.js +14 -31
  12. package/dist/cjs/common/wrap/wrap-events.js +2 -2
  13. package/dist/cjs/common/wrap/wrap-fetch.js +1 -2
  14. package/dist/cjs/common/wrap/wrap-function.js +7 -5
  15. package/dist/cjs/common/wrap/wrap-promise.js +1 -2
  16. package/dist/cjs/features/ajax/aggregate/index.js +7 -13
  17. package/dist/cjs/features/jserrors/aggregate/index.js +25 -24
  18. package/dist/cjs/features/metrics/aggregate/index.js +25 -24
  19. package/dist/cjs/features/page_action/aggregate/index.js +6 -4
  20. package/dist/cjs/features/page_view_event/aggregate/index.js +22 -2
  21. package/dist/cjs/features/page_view_timing/aggregate/index.js +15 -16
  22. package/dist/cjs/features/session_replay/aggregate/index.js +12 -4
  23. package/dist/cjs/features/session_trace/aggregate/index.js +11 -8
  24. package/dist/cjs/features/soft_navigations/aggregate/index.js +17 -12
  25. package/dist/cjs/features/spa/aggregate/index.js +19 -14
  26. package/dist/cjs/features/utils/aggregate-base.js +18 -5
  27. package/dist/cjs/features/utils/feature-base.js +2 -0
  28. package/dist/cjs/features/utils/instrument-base.js +1 -0
  29. package/dist/cjs/loaders/agent-base.js +2 -7
  30. package/dist/cjs/loaders/agent.js +4 -4
  31. package/dist/cjs/loaders/api/api.js +1 -1
  32. package/dist/cjs/loaders/configure/configure.js +1 -1
  33. package/dist/cjs/loaders/configure/nonce.cdn.js +13 -0
  34. package/dist/cjs/loaders/configure/nonce.js +2 -13
  35. package/dist/cjs/loaders/configure/public-path.cdn.js +16 -0
  36. package/dist/cjs/loaders/configure/public-path.js +2 -8
  37. package/dist/esm/cdn/polyfills.js +2 -1
  38. package/dist/esm/common/config/state/runtime.js +4 -1
  39. package/dist/esm/common/constants/env.cdn.js +1 -1
  40. package/dist/esm/common/constants/env.npm.js +1 -1
  41. package/dist/esm/common/drain/drain.js +40 -27
  42. package/dist/esm/common/event-emitter/contextual-ee.js +24 -22
  43. package/dist/esm/common/harvest/harvest.js +5 -1
  44. package/dist/esm/common/timing/time-keeper.js +88 -0
  45. package/dist/esm/common/util/feature-flags.js +14 -31
  46. package/dist/esm/common/wrap/wrap-events.js +3 -3
  47. package/dist/esm/common/wrap/wrap-fetch.js +2 -3
  48. package/dist/esm/common/wrap/wrap-function.js +5 -4
  49. package/dist/esm/common/wrap/wrap-promise.js +2 -3
  50. package/dist/esm/features/ajax/aggregate/index.js +7 -13
  51. package/dist/esm/features/jserrors/aggregate/index.js +25 -24
  52. package/dist/esm/features/metrics/aggregate/index.js +25 -24
  53. package/dist/esm/features/page_action/aggregate/index.js +6 -4
  54. package/dist/esm/features/page_view_event/aggregate/index.js +22 -2
  55. package/dist/esm/features/page_view_timing/aggregate/index.js +15 -16
  56. package/dist/esm/features/session_replay/aggregate/index.js +12 -4
  57. package/dist/esm/features/session_trace/aggregate/index.js +11 -8
  58. package/dist/esm/features/soft_navigations/aggregate/index.js +17 -12
  59. package/dist/esm/features/spa/aggregate/index.js +19 -14
  60. package/dist/esm/features/utils/aggregate-base.js +18 -5
  61. package/dist/esm/features/utils/feature-base.js +2 -0
  62. package/dist/esm/features/utils/instrument-base.js +1 -0
  63. package/dist/esm/loaders/agent-base.js +2 -7
  64. package/dist/esm/loaders/agent.js +4 -4
  65. package/dist/esm/loaders/api/api.js +1 -1
  66. package/dist/esm/loaders/configure/configure.js +1 -1
  67. package/dist/esm/loaders/configure/nonce.cdn.js +11 -0
  68. package/dist/esm/loaders/configure/nonce.js +1 -11
  69. package/dist/esm/loaders/configure/public-path.cdn.js +9 -0
  70. package/dist/esm/loaders/configure/public-path.js +2 -8
  71. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  72. package/dist/types/common/drain/drain.d.ts +6 -0
  73. package/dist/types/common/drain/drain.d.ts.map +1 -1
  74. package/dist/types/common/event-emitter/contextual-ee.d.ts +4 -3
  75. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  76. package/dist/types/common/event-emitter/event-context.d.ts.map +1 -0
  77. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  78. package/dist/types/common/timing/time-keeper.d.ts +31 -0
  79. package/dist/types/common/timing/time-keeper.d.ts.map +1 -0
  80. package/dist/types/common/util/feature-flags.d.ts +11 -2
  81. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  82. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  83. package/dist/types/common/wrap/wrap-function.d.ts +1 -0
  84. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  85. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  86. package/dist/types/features/ajax/aggregate/index.d.ts +5 -5
  87. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  89. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  90. package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
  91. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  92. package/dist/types/features/page_view_timing/aggregate/index.d.ts +0 -2
  93. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  94. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  95. package/dist/types/features/soft_navigations/aggregate/index.d.ts +0 -2
  96. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  97. package/dist/types/features/spa/aggregate/index.d.ts +2 -0
  98. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  99. package/dist/types/features/utils/aggregate-base.d.ts +2 -2
  100. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  101. package/dist/types/features/utils/feature-base.d.ts +1 -0
  102. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  103. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  104. package/dist/types/loaders/agent-base.d.ts +2 -2
  105. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  106. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  107. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  108. package/package.json +1 -1
  109. package/src/cdn/polyfills.js +1 -0
  110. package/src/common/config/state/runtime.js +4 -1
  111. package/src/common/drain/drain.js +41 -28
  112. package/src/common/event-emitter/contextual-ee.js +29 -31
  113. package/src/common/harvest/harvest.js +4 -1
  114. package/src/common/timing/time-keeper.js +96 -0
  115. package/src/common/util/feature-flags.js +13 -31
  116. package/src/common/wrap/wrap-events.js +3 -3
  117. package/src/common/wrap/wrap-fetch.js +2 -3
  118. package/src/common/wrap/wrap-function.js +6 -4
  119. package/src/common/wrap/wrap-promise.js +2 -3
  120. package/src/features/ajax/aggregate/index.js +8 -16
  121. package/src/features/jserrors/aggregate/index.js +12 -14
  122. package/src/features/metrics/aggregate/index.js +18 -17
  123. package/src/features/page_action/aggregate/index.js +6 -5
  124. package/src/features/page_view_event/aggregate/index.js +18 -2
  125. package/src/features/page_view_timing/aggregate/index.js +15 -15
  126. package/src/features/session_replay/aggregate/index.js +10 -4
  127. package/src/features/session_trace/aggregate/index.js +2 -2
  128. package/src/features/soft_navigations/aggregate/index.js +14 -12
  129. package/src/features/spa/aggregate/index.js +15 -13
  130. package/src/features/utils/aggregate-base.js +16 -8
  131. package/src/features/utils/feature-base.js +3 -0
  132. package/src/features/utils/instrument-base.js +1 -0
  133. package/src/loaders/agent-base.js +2 -7
  134. package/src/loaders/agent.js +2 -2
  135. package/src/loaders/api/api.js +1 -1
  136. package/src/loaders/configure/nonce.cdn.js +12 -0
  137. package/src/loaders/configure/nonce.js +1 -12
  138. package/src/loaders/configure/public-path.cdn.js +9 -0
  139. package/src/loaders/configure/public-path.js +2 -8
  140. package/dist/cjs/common/context/observation-context-manager.js +0 -56
  141. package/dist/cjs/loaders/configure/nonce.npm.js +0 -2
  142. package/dist/cjs/loaders/configure/public-path.npm.js +0 -10
  143. package/dist/esm/common/context/observation-context-manager.js +0 -49
  144. package/dist/esm/loaders/configure/nonce.npm.js +0 -1
  145. package/dist/esm/loaders/configure/public-path.npm.js +0 -3
  146. package/dist/types/common/context/event-context.d.ts.map +0 -1
  147. package/dist/types/common/context/observation-context-manager.d.ts +0 -28
  148. package/dist/types/common/context/observation-context-manager.d.ts.map +0 -1
  149. package/dist/types/loaders/configure/nonce.npm.d.ts +0 -1
  150. package/dist/types/loaders/configure/nonce.npm.d.ts.map +0 -1
  151. package/dist/types/loaders/configure/public-path.npm.d.ts +0 -2
  152. package/dist/types/loaders/configure/public-path.npm.d.ts.map +0 -1
  153. package/src/common/context/observation-context-manager.js +0 -55
  154. package/src/loaders/configure/nonce.npm.js +0 -1
  155. package/src/loaders/configure/public-path.npm.js +0 -3
  156. /package/dist/cjs/common/{context → event-emitter}/event-context.js +0 -0
  157. /package/dist/esm/common/{context → event-emitter}/event-context.js +0 -0
  158. /package/dist/types/common/{context → event-emitter}/event-context.d.ts +0 -0
  159. /package/src/common/{context → event-emitter}/event-context.js +0 -0
@@ -6,9 +6,11 @@
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 '../context/event-context'
10
- import { ObservationContextManager } from '../context/observation-context-manager'
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:${bundleId}`
12
14
  // create global emitter instance that can be shared among bundles
13
15
  const globalInstance = ee(undefined, 'globalEE')
14
16
 
@@ -18,7 +20,7 @@ if (!nr.ee) {
18
20
  nr.ee = globalInstance
19
21
  }
20
22
 
21
- export { globalInstance as ee }
23
+ export { globalInstance as ee, contextId }
22
24
 
23
25
  function ee (old, debugId) {
24
26
  var handlers = {}
@@ -47,28 +49,41 @@ function ee (old, debugId) {
47
49
  context,
48
50
  buffer: bufferEventsByGroup,
49
51
  abort,
50
- aborted: false,
51
52
  isBuffering,
52
53
  debugId,
53
54
  backlog: isolatedBacklog ? {} : old && typeof old.backlog === 'object' ? old.backlog : {},
54
- observationContextManager: null
55
+ isolatedBacklog
55
56
  }
56
57
 
58
+ function abort () {
59
+ emitter._aborted = true
60
+ Object.keys(emitter.backlog).forEach(key => {
61
+ delete emitter.backlog[key]
62
+ })
63
+ }
64
+
65
+ Object.defineProperty(emitter, 'aborted', {
66
+ get: () => {
67
+ let aborted = emitter._aborted || false
68
+
69
+ if (aborted) return aborted
70
+ else if (old) {
71
+ aborted = old.aborted
72
+ }
73
+
74
+ return aborted
75
+ }
76
+ })
77
+
57
78
  return emitter
58
79
 
59
80
  function context (contextOrStore) {
60
81
  if (contextOrStore && contextOrStore instanceof EventContext) {
61
82
  return contextOrStore
62
83
  } else if (contextOrStore) {
63
- return getOrSet(contextOrStore, ObservationContextManager.contextId, () =>
64
- emitter.observationContextManager
65
- ? emitter.observationContextManager.getCreateContext(contextOrStore)
66
- : new EventContext(ObservationContextManager.contextId)
67
- )
84
+ return getOrSet(contextOrStore, contextId, () => new EventContext(contextId))
68
85
  } else {
69
- return emitter.observationContextManager
70
- ? emitter.observationContextManager.getCreateContext({})
71
- : new EventContext(ObservationContextManager.contextId)
86
+ return new EventContext(contextId)
72
87
  }
73
88
  }
74
89
 
@@ -116,13 +131,7 @@ function ee (old, debugId) {
116
131
  }
117
132
 
118
133
  function getOrCreate (name) {
119
- const newEventEmitter = (emitters[name] = emitters[name] || ee(emitter, name))
120
-
121
- if (!newEventEmitter.observationContextManager && emitter.observationContextManager) {
122
- newEventEmitter.observationContextManager = emitter.observationContextManager
123
- }
124
-
125
- return newEventEmitter
134
+ return (emitters[name] = emitters[name] || ee(emitter, name))
126
135
  }
127
136
 
128
137
  function bufferEventsByGroup (types, group) {
@@ -151,14 +160,3 @@ function ee (old, debugId) {
151
160
  return emitter.backlog
152
161
  }
153
162
  }
154
-
155
- function abort () {
156
- globalInstance.aborted = true
157
- // The global backlog can be referenced directly by other emitters,
158
- // so we need to delete its contents as opposed to replacing it.
159
- // Otherwise, these references to the old backlog would still exist
160
- // and the keys will not be garbage collected.
161
- Object.keys(globalInstance.backlog).forEach(key => {
162
- delete globalInstance.backlog[key]
163
- })
164
- }
@@ -145,7 +145,7 @@ export class Harvest extends SharedContext {
145
145
  result.addEventListener('loadend', function () {
146
146
  // `this` refers to the XHR object in this scope, do not change this to a fat arrow
147
147
  // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
148
- const cbResult = { sent: this.status !== 0, status: this.status }
148
+ const cbResult = { sent: this.status !== 0, status: this.status, xhr: this, fullUrl }
149
149
  if (this.status === 429) {
150
150
  cbResult.retry = true
151
151
  cbResult.delay = harvestScope.tooManyRequestsDelay
@@ -160,6 +160,9 @@ export class Harvest extends SharedContext {
160
160
  }, eventListenerOpts(false))
161
161
  }
162
162
 
163
+ const runtime = getRuntime(this.sharedContext.agentIdentifier)
164
+ runtime.harvestCount++
165
+
163
166
  return result
164
167
  }
165
168
 
@@ -0,0 +1,96 @@
1
+ import { gosNREUM } from '../window/nreum'
2
+ import { globalScope } from '../constants/runtime'
3
+ import { getRuntime } from '../config/config'
4
+
5
+ /**
6
+ * Class used to adjust the timestamp of harvested data to New Relic server time. This
7
+ * is done by tracking the performance timings of the RUM call and applying a calculation
8
+ * to the harvested data event offset time.
9
+ */
10
+ export class TimeKeeper {
11
+ #agent
12
+
13
+ /**
14
+ * Represents the browser origin time corrected to NR server time.
15
+ * @type {number}
16
+ */
17
+ #correctedOriginTime
18
+
19
+ /**
20
+ * Represents the difference in milliseconds between the calculated NR server time and
21
+ * the local time.
22
+ * @type {number}
23
+ */
24
+ #localTimeDiff
25
+
26
+ constructor (agent) {
27
+ this.#agent = agent
28
+ }
29
+
30
+ static getTimeKeeperByAgentIdentifier (agentIdentifier) {
31
+ const nr = gosNREUM()
32
+ return Object.keys(nr?.initializedAgents || {}).indexOf(agentIdentifier) > -1
33
+ ? nr.initializedAgents[agentIdentifier].timeKeeper
34
+ : undefined
35
+ }
36
+
37
+ get correctedPageOriginTime () {
38
+ return this.#correctedOriginTime
39
+ }
40
+
41
+ /**
42
+ * Process a rum request to calculate NR server time.
43
+ * @param rumRequest {XMLHttpRequest} The xhr for the rum request
44
+ * @param rumRequestUrl {string} The full url of the rum request
45
+ */
46
+ processRumRequest (rumRequest, rumRequestUrl) {
47
+ const responseDateHeader = rumRequest.getResponseHeader('Date')
48
+ if (!responseDateHeader) {
49
+ throw new Error('Missing date header on rum response.')
50
+ }
51
+
52
+ const resourceEntries = globalScope.performance.getEntriesByName(rumRequestUrl, 'resource')
53
+ if (!Array.isArray((resourceEntries)) || resourceEntries.length === 0) {
54
+ throw new Error('Missing rum request performance entry.')
55
+ }
56
+
57
+ let medianRumOffset = 0
58
+ let serverOffset = 0
59
+ if (typeof resourceEntries[0].responseStart === 'number' && resourceEntries[0].responseStart !== 0) {
60
+ // Cors is enabled and we can make a more accurate calculation of NR server time
61
+ medianRumOffset = (resourceEntries[0].responseStart - resourceEntries[0].requestStart) / 2
62
+ serverOffset = Math.floor(resourceEntries[0].requestStart + medianRumOffset)
63
+ } else {
64
+ // Cors is disabled or erred, we need to use a less accurate calculation
65
+ medianRumOffset = (resourceEntries[0].responseEnd - resourceEntries[0].fetchStart) / 2
66
+ serverOffset = Math.floor(resourceEntries[0].fetchStart + medianRumOffset)
67
+ }
68
+
69
+ // Corrected page origin time
70
+ this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset)
71
+ this.#localTimeDiff = getRuntime(this.#agent.agentIdentifier).offset - this.#correctedOriginTime
72
+
73
+ if (Number.isNaN(this.#correctedOriginTime)) {
74
+ throw new Error('Date header invalid format.')
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Converts a page origin relative time to an absolute timestamp
80
+ * corrected to NR server time.
81
+ * @param relativeTime {number} The relative time of the event in milliseconds
82
+ * @returns {number} The correct timestamp as a unix/epoch timestamp value
83
+ */
84
+ convertRelativeTimestamp (relativeTime) {
85
+ return this.#correctedOriginTime + relativeTime
86
+ }
87
+
88
+ /**
89
+ * Corrects an event timestamp to NR server time.
90
+ * @param timestamp {number} The unix/epoch timestamp of the event with milliseconds
91
+ * @return {number} Corrected unix/epoch timestamp
92
+ */
93
+ correctAbsoluteTimestamp (timestamp) {
94
+ return Math.floor(timestamp - this.#localTimeDiff)
95
+ }
96
+ }
@@ -3,49 +3,31 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { ee } from '../event-emitter/contextual-ee'
6
- import { handle } from '../event-emitter/handle'
7
- import { FEATURE_NAMES } from '../../loaders/features/features'
8
6
  import { dispatchGlobalEvent } from '../dispatch/global-event'
9
7
 
10
- const bucketMap = {
11
- stn: [FEATURE_NAMES.sessionTrace],
12
- err: [FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics],
13
- ins: [FEATURE_NAMES.pageAction],
14
- spa: [FEATURE_NAMES.spa, FEATURE_NAMES.softNav],
15
- sr: [FEATURE_NAMES.sessionReplay, FEATURE_NAMES.sessionTrace]
16
- }
17
-
18
8
  const sentIds = new Set()
19
9
 
20
- /** Note that this function only processes each unique flag ONCE, with the first occurrence of each flag and numeric value determining its switch on/off setting. */
10
+ /** A map of feature flags and their values as provided by the rum call -- scoped by agent ID */
11
+ export const activatedFeatures = {}
12
+
13
+ /**
14
+ * Sets the activatedFeatures object, dispatches the global loaded event,
15
+ * and emits the rumresp flag to features
16
+ * @param {{[key:string]:number}} flags key-val pair of flag names and numeric
17
+ * @param {string} agentIdentifier agent instance identifier
18
+ * @returns {void}
19
+ */
21
20
  export function activateFeatures (flags, agentIdentifier) {
22
21
  const sharedEE = ee.get(agentIdentifier)
22
+ activatedFeatures[agentIdentifier] ??= {}
23
23
  if (!(flags && typeof flags === 'object')) return
24
24
  if (sentIds.has(agentIdentifier)) return
25
25
 
26
- Object.entries(flags).forEach(([flag, num]) => {
27
- if (bucketMap[flag]) {
28
- bucketMap[flag].forEach(feat => {
29
- if (!num) handle('block-' + flag, [], undefined, feat, sharedEE)
30
- else handle('feat-' + flag, [], undefined, feat, sharedEE)
31
- handle('rumresp-' + flag, [Boolean(num)], undefined, feat, sharedEE) // this is a duplicate of feat-/block- but makes awaiting for 1 event easier than 2
32
- })
33
- } else if (num) handle('feat-' + flag, [], undefined, undefined, sharedEE) // not sure what other flags are overlooked, but there's a test for ones not in the map --
34
- activatedFeatures[flag] = Boolean(num)
35
- })
26
+ sharedEE.emit('rumresp', [flags])
27
+ activatedFeatures[agentIdentifier] = flags
36
28
 
37
- // Let the features waiting on their respective flags know that RUM response was received and that any missing flags are interpreted as bad entitlement / "off".
38
- // Hence, those features will not be hanging forever if their flags aren't included in the response.
39
- Object.keys(bucketMap).forEach(flag => {
40
- if (activatedFeatures[flag] === undefined) {
41
- bucketMap[flag]?.forEach(feat => handle('rumresp-' + flag, [false], undefined, feat, sharedEE))
42
- activatedFeatures[flag] = false
43
- }
44
- })
45
29
  sentIds.add(agentIdentifier)
46
30
 
47
31
  // let any window level subscribers know that the agent is running
48
32
  dispatchGlobalEvent({ loaded: true })
49
33
  }
50
-
51
- export const activatedFeatures = {}
@@ -7,16 +7,16 @@
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
- import { ObservationContextManager } from '../context/observation-context-manager'
15
14
 
16
15
  const wrapped = {}
17
16
  const XHR = globalScope.XMLHttpRequest
18
17
  const ADD_EVENT_LISTENER = 'addEventListener'
19
18
  const REMOVE_EVENT_LISTENER = 'removeEventListener'
19
+ const flag = `nr@wrapped:${contextId}`
20
20
 
21
21
  /**
22
22
  * Wraps `addEventListener` and `removeEventListener` on: global scope; the prototype of `XMLHttpRequest`, and
@@ -49,7 +49,7 @@ export function wrapEvents (sharedEE) {
49
49
  return
50
50
  }
51
51
 
52
- var wrapped = getOrSet(originalListener, ObservationContextManager.contextWrappedId, function () {
52
+ var wrapped = getOrSet(originalListener, flag, function () {
53
53
  var listener = {
54
54
  object: wrapHandleEvent,
55
55
  function: originalListener
@@ -6,9 +6,8 @@
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 { ObservationContextManager } from '../context/observation-context-manager'
12
11
 
13
12
  var prefix = 'fetch-'
14
13
  var bodyPrefix = prefix + 'body-'
@@ -75,7 +74,7 @@ export function wrapFetch (sharedEE) {
75
74
  // we are wrapping args in an array so we can preserve the reference
76
75
  ee.emit(prefix + 'before-start', [args], ctx)
77
76
  var dtPayload
78
- if (ctx[ObservationContextManager.contextId] && ctx[ObservationContextManager.contextId].dt) dtPayload = ctx[ObservationContextManager.contextId].dt
77
+ if (ctx[contextId] && ctx[contextId].dt) dtPayload = ctx[contextId].dt
79
78
 
80
79
  var origPromiseFromFetch = fn.apply(this, args)
81
80
 
@@ -7,7 +7,9 @@
7
7
  */
8
8
 
9
9
  import { ee } from '../event-emitter/contextual-ee'
10
- import { ObservationContextManager } from '../context/observation-context-manager'
10
+ import { bundleId } from '../ids/bundle-id'
11
+
12
+ export const flag = `nr@original:${bundleId}`
11
13
 
12
14
  /**
13
15
  * A convenience alias of `hasOwnProperty`.
@@ -40,7 +42,7 @@ export function createWrapperWithEmitter (emitter, always) {
40
42
  * As a property on a wrapped function, contains the original function.
41
43
  * @type {string}
42
44
  */
43
- wrapFn.flag = ObservationContextManager.contextOriginalId
45
+ wrapFn.flag = flag
44
46
 
45
47
  return wrapFn
46
48
 
@@ -59,7 +61,7 @@ export function createWrapperWithEmitter (emitter, always) {
59
61
 
60
62
  if (!prefix) prefix = ''
61
63
 
62
- nrWrapper[ObservationContextManager.contextOriginalId] = fn
64
+ nrWrapper[flag] = fn
63
65
  copy(fn, nrWrapper, emitter)
64
66
  return nrWrapper
65
67
 
@@ -213,5 +215,5 @@ function copy (from, to, emitter) {
213
215
  * @returns {boolean} Whether the passed function is ineligible to be wrapped.
214
216
  */
215
217
  function notWrappable (fn) {
216
- return !(fn && typeof fn === 'function' && fn.apply && !fn[ObservationContextManager.contextOriginalId])
218
+ return !(fn && typeof fn === 'function' && fn.apply && !fn[flag])
217
219
  }
@@ -7,10 +7,9 @@
7
7
  * This module is used by: spa.
8
8
  */
9
9
 
10
- import { createWrapperWithEmitter as wrapFn } from './wrap-function'
10
+ import { createWrapperWithEmitter as wrapFn, flag } 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'
14
13
 
15
14
  const wrapped = {}
16
15
 
@@ -123,7 +122,7 @@ export function wrapPromise (sharedEE) {
123
122
 
124
123
  return origFnCallWithThis
125
124
  }
126
- prevPromiseObj.prototype.then[ObservationContextManager.contextOriginalId] = prevPromiseOrigThen
125
+ prevPromiseObj.prototype.then[flag] = prevPromiseOrigThen
127
126
 
128
127
  promiseEE.on('executor-start', function (args) {
129
128
  args[0] = promiseWrapper(args[0], 'resolve-', this, null, false)
@@ -21,13 +21,17 @@ export class Aggregate extends AggregateBase {
21
21
  constructor (agentIdentifier, aggregator) {
22
22
  super(agentIdentifier, aggregator, FEATURE_NAME)
23
23
  const agentInit = getConfiguration(agentIdentifier)
24
- const allAjaxIsEnabled = agentInit.ajax.enabled !== false
25
24
 
26
25
  registerHandler('xhr', storeXhr, this.featureName, this.ee)
27
- if (!allAjaxIsEnabled) {
26
+
27
+ this.waitForFlags(([])).then(() => {
28
+ const scheduler = new HarvestScheduler('events', {
29
+ onFinished: onEventsHarvestFinished,
30
+ getPayload: prepareHarvest
31
+ }, this)
32
+ scheduler.startTimer(harvestTimeSeconds)
28
33
  this.drain()
29
- return // feature will only collect timeslice metrics & ajax trace nodes if it's not fully enabled
30
- }
34
+ })
31
35
 
32
36
  const denyList = getRuntime(agentIdentifier).denyList
33
37
  setDenyList(denyList)
@@ -61,15 +65,6 @@ export class Aggregate extends AggregateBase {
61
65
  registerHandler('returnAjax', event => ajaxEvents.push(event), this.featureName, this.ee)
62
66
  // --- ^
63
67
 
64
- const scheduler = new HarvestScheduler('events', {
65
- onFinished: onEventsHarvestFinished,
66
- getPayload: prepareHarvest
67
- }, this)
68
-
69
- ee.on(`drain-${this.featureName}`, () => { scheduler.startTimer(harvestTimeSeconds) })
70
-
71
- this.drain()
72
-
73
68
  const beacon = getInfo(agentIdentifier).errorBeacon
74
69
  const proxyBeacon = agentInit.proxy.beacon
75
70
 
@@ -92,8 +87,6 @@ export class Aggregate extends AggregateBase {
92
87
  aggregator.store('xhr', hash, params, metrics)
93
88
  }
94
89
 
95
- if (!allAjaxIsEnabled) return
96
-
97
90
  if (!shouldCollect) {
98
91
  if (params.hostname === beacon || (proxyBeacon && params.hostname === proxyBeacon)) {
99
92
  // This doesn't make a distinction if the same-domain request is going to a different port or path...
@@ -143,7 +136,6 @@ export class Aggregate extends AggregateBase {
143
136
  if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, ee)
144
137
 
145
138
  const softNavInUse = Boolean(getNREUMInitializedAgent(agentIdentifier)?.features?.[FEATURE_NAMES.softNav])
146
-
147
139
  if (softNavInUse) { // For newer soft nav (when running), pass the event to it for evaluation -- either part of an interaction or is given back
148
140
  handle('ajax', [event], undefined, FEATURE_NAMES.softNav, ee)
149
141
  } else if (this.spaNode) { // For old spa (when running), if the ajax happened inside an interaction, hold it until the interaction finishes
@@ -21,6 +21,7 @@ import { FEATURE_NAME } from '../constants'
21
21
  import { FEATURE_NAMES } from '../../../loaders/features/features'
22
22
  import { AggregateBase } from '../../utils/aggregate-base'
23
23
  import { getNREUMInitializedAgent } from '../../../common/window/nreum'
24
+ import { deregisterDrain } from '../../../common/drain/drain'
24
25
 
25
26
  /**
26
27
  * @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
@@ -48,21 +49,18 @@ export class Aggregate extends AggregateBase {
48
49
 
49
50
  const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'jserrors.harvestTimeSeconds') || 10
50
51
 
51
- const scheduler = new HarvestScheduler('jserrors', { onFinished: (...args) => this.onHarvestFinished(...args) }, this)
52
- scheduler.harvest.on('jserrors', (...args) => this.onHarvestStarted(...args))
53
-
54
- // Don't start harvesting until "drain" for this feat has been called (which currently requires RUM response).
55
- this.ee.on(`drain-${this.featureName}`, () => {
56
- if (!this.blocked) scheduler.startTimer(harvestTimeSeconds) // and only if ingest will accept jserror payloads
52
+ // 0 == off, 1 == on
53
+ this.waitForFlags(['err']).then(([errFlag]) => {
54
+ if (errFlag) {
55
+ const scheduler = new HarvestScheduler('jserrors', { onFinished: (...args) => this.onHarvestFinished(...args) }, this)
56
+ scheduler.harvest.on('jserrors', (...args) => this.onHarvestStarted(...args))
57
+ scheduler.startTimer(harvestTimeSeconds)
58
+ this.drain()
59
+ } else {
60
+ this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
61
+ deregisterDrain(this.agentIdentifier, this.featureName)
62
+ }
57
63
  })
58
-
59
- // If RUM-call's response determines that customer lacks entitlements for the /jserror ingest endpoint, don't harvest at all.
60
- register('block-err', () => {
61
- this.blocked = true
62
- scheduler.stopTimer(true)
63
- }, this.featureName, this.ee)
64
-
65
- this.drain()
66
64
  }
67
65
 
68
66
  onHarvestStarted (options) {
@@ -9,18 +9,25 @@ import { onDOMContentLoaded } from '../../../common/window/load'
9
9
  import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts'
10
10
  import { isBrowserScope, isWorkerScope } from '../../../common/constants/runtime'
11
11
  import { AggregateBase } from '../../utils/aggregate-base'
12
+ import { deregisterDrain } from '../../../common/drain/drain'
12
13
 
13
14
  export class Aggregate extends AggregateBase {
14
15
  static featureName = FEATURE_NAME
15
16
  constructor (agentIdentifier, aggregator) {
16
17
  super(agentIdentifier, aggregator, FEATURE_NAME)
17
- let scheduler
18
18
 
19
- // If RUM-call's response determines that customer lacks entitlements for the /jserror ingest endpoint, don't harvest at all.
20
- registerHandler('block-err', () => {
21
- this.blocked = true
22
- if (scheduler) scheduler.aborted = true // RUM response may or may not have happened already before scheduler initialization below
23
- }, this.featureName, this.ee)
19
+ this.waitForFlags(['err']).then(([errFlag]) => {
20
+ if (errFlag) {
21
+ // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
22
+ const scheduler = new HarvestScheduler('jserrors', { onUnload: () => this.unload() }, this)
23
+ // this is needed to ensure EoL is "on" and sent
24
+ scheduler.harvest.on('jserrors', () => ({ body: this.aggregator.take(['cm', 'sm']) }))
25
+ this.drain()
26
+ } else {
27
+ this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
28
+ deregisterDrain(this.agentIdentifier, this.featureName)
29
+ }
30
+ })
24
31
 
25
32
  // Allow features external to the metrics feature to capture SMs and CMs through the event emitter
26
33
  registerHandler(SUPPORTABILITY_METRIC_CHANNEL, this.storeSupportabilityMetrics.bind(this), this.featureName, this.ee)
@@ -28,14 +35,6 @@ export class Aggregate extends AggregateBase {
28
35
 
29
36
  this.singleChecks() // checks that are run only one time, at script load
30
37
  this.eachSessionChecks() // the start of every time user engages with page
31
-
32
- this.ee.on(`drain-${this.featureName}`, () => {
33
- // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
34
- scheduler = new HarvestScheduler('jserrors', { onUnload: () => this.unload() }, this)
35
- scheduler.harvest.on('jserrors', () => ({ body: this.aggregator.take(['cm', 'sm']) }))
36
- }) // this is needed to ensure EoL is "on" and sent
37
-
38
- this.drain()
39
38
  }
40
39
 
41
40
  storeSupportabilityMetrics (name, value) {
@@ -55,6 +54,8 @@ export class Aggregate extends AggregateBase {
55
54
  singleChecks () {
56
55
  // report loaderType
57
56
  const { distMethod, loaderType } = getRuntime(this.agentIdentifier)
57
+ const { proxy, privacy, page_view_timing } = getConfiguration(this.agentIdentifier)
58
+
58
59
  if (loaderType) this.storeSupportabilityMetrics(`Generic/LoaderType/${loaderType}/Detected`)
59
60
  if (distMethod) this.storeSupportabilityMetrics(`Generic/DistMethod/${distMethod}/Detected`)
60
61
 
@@ -72,6 +73,9 @@ export class Aggregate extends AggregateBase {
72
73
  this.storeSupportabilityMetrics('Framework/' + framework + '/Detected')
73
74
  })
74
75
  })
76
+
77
+ if (!privacy.cookies_enabled) this.storeSupportabilityMetrics('Config/SessionTracking/Disabled')
78
+ if (page_view_timing.long_task) this.storeSupportabilityMetrics('Config/LongTask/Enabled')
75
79
  } else if (isWorkerScope) {
76
80
  this.storeSupportabilityMetrics('Generic/Runtime/Worker/Detected')
77
81
  } else {
@@ -90,11 +94,8 @@ export class Aggregate extends AggregateBase {
90
94
  if (rules.length > 0 && !validateRules(rules)) this.storeSupportabilityMetrics('Generic/Obfuscate/Invalid')
91
95
 
92
96
  // Check if proxy for either chunks or beacon is being used
93
- const { proxy, privacy } = getConfiguration(this.agentIdentifier)
94
97
  if (proxy.assets) this.storeSupportabilityMetrics('Config/AssetsUrl/Changed')
95
98
  if (proxy.beacon) this.storeSupportabilityMetrics('Config/BeaconUrl/Changed')
96
-
97
- if (!(isBrowserScope && privacy.cookies_enabled)) this.storeSupportabilityMetrics('Config/SessionTracking/Disabled')
98
99
  }
99
100
 
100
101
  eachSessionChecks () {
@@ -12,6 +12,7 @@ import { getConfigurationValue, getInfo, getRuntime } from '../../../common/conf
12
12
  import { FEATURE_NAME } from '../constants'
13
13
  import { isBrowserScope } from '../../../common/constants/runtime'
14
14
  import { AggregateBase } from '../../utils/aggregate-base'
15
+ import { deregisterDrain } from '../../../common/drain/drain'
15
16
 
16
17
  export class Aggregate extends AggregateBase {
17
18
  static featureName = FEATURE_NAME
@@ -31,17 +32,17 @@ export class Aggregate extends AggregateBase {
31
32
 
32
33
  register('api-addPageAction', (...args) => this.addPageAction(...args), this.featureName, this.ee)
33
34
 
34
- this.waitForFlags(['ins']).then(([enabled]) => {
35
- if (enabled) {
35
+ this.waitForFlags(['ins']).then(([insFlag]) => {
36
+ if (insFlag) {
36
37
  const scheduler = new HarvestScheduler('ins', { onFinished: (...args) => this.onHarvestFinished(...args) }, this)
37
38
  scheduler.harvest.on('ins', (...args) => this.onHarvestStarted(...args))
38
39
  scheduler.startTimer(this.harvestTimeSeconds, 0)
40
+ this.drain()
39
41
  } else {
40
- this.blocked = true
42
+ this.blocked = true // if rum response determines that customer lacks entitlements for ins endpoint, this feature shouldn't harvest
43
+ deregisterDrain(this.agentIdentifier, this.featureName)
41
44
  }
42
45
  })
43
-
44
- this.drain()
45
46
  }
46
47
 
47
48
  onHarvestStarted (options) {
@@ -11,6 +11,10 @@ import { AggregateBase } from '../../utils/aggregate-base'
11
11
  import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
12
12
  import { firstPaint } from '../../../common/vitals/first-paint'
13
13
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
14
+ import { drain } from '../../../common/drain/drain'
15
+ import { FEATURE_NAMES } from '../../../loaders/features/features'
16
+ import { handle } from '../../../common/event-emitter/handle'
17
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
14
18
 
15
19
  export class Aggregate extends AggregateBase {
16
20
  static featureName = CONSTANTS.FEATURE_NAME
@@ -100,7 +104,7 @@ export class Aggregate extends AggregateBase {
100
104
  endpoint: 'rum',
101
105
  payload: { qs: queryParameters, body },
102
106
  opts: { needResponse: true, sendEmptyBody: true },
103
- cbFinished: ({ status, responseText }) => {
107
+ cbFinished: ({ status, responseText, xhr, fullUrl }) => {
104
108
  if (status >= 400 || status === 0) {
105
109
  // Adding retry logic for the rum call will be a separate change
106
110
  this.ee.abort()
@@ -108,7 +112,19 @@ export class Aggregate extends AggregateBase {
108
112
  }
109
113
 
110
114
  try {
111
- activateFeatures(JSON.parse(responseText), this.agentIdentifier)
115
+ this.timeKeeper.processRumRequest(xhr, fullUrl)
116
+ } catch (error) {
117
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, FEATURE_NAMES.metrics, this.ee)
118
+ drain(this.agentIdentifier, FEATURE_NAMES.metrics, true)
119
+ this.ee.abort()
120
+ warn('Could not calculate New Relic server time. Agent shutting down.')
121
+ return
122
+ }
123
+
124
+ try {
125
+ const { app, ...flags } = JSON.parse(responseText)
126
+ agentRuntime.appMetadata = app
127
+ activateFeatures(flags, this.agentIdentifier)
112
128
  this.drain()
113
129
  } catch (err) {
114
130
  this.ee.abort()