@newrelic/browser-agent 1.258.2 → 1.259.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 (117) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cjs/cdn/polyfills.js +3 -1
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/drain/drain.js +1 -1
  6. package/dist/cjs/common/harvest/harvest-scheduler.js +4 -2
  7. package/dist/cjs/features/ajax/constants.js +3 -2
  8. package/dist/cjs/features/jserrors/aggregate/index.js +2 -1
  9. package/dist/cjs/features/metrics/aggregate/index.js +0 -8
  10. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  11. package/dist/cjs/features/session_replay/aggregate/index.js +31 -36
  12. package/dist/cjs/features/session_replay/constants.js +5 -2
  13. package/dist/cjs/features/session_replay/instrument/index.js +53 -13
  14. package/dist/cjs/features/session_replay/shared/utils.js +3 -5
  15. package/dist/cjs/features/session_trace/aggregate/index.js +181 -527
  16. package/dist/cjs/features/session_trace/aggregate/trace/node.js +19 -0
  17. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +289 -0
  18. package/dist/cjs/features/session_trace/constants.js +3 -2
  19. package/dist/cjs/features/session_trace/instrument/index.js +7 -3
  20. package/dist/cjs/features/utils/aggregate-base.js +1 -0
  21. package/dist/cjs/features/utils/feature-gates.js +17 -0
  22. package/dist/cjs/features/utils/instrument-base.js +2 -1
  23. package/dist/cjs/loaders/agent-base.js +4 -0
  24. package/dist/cjs/loaders/api/api-methods.js +1 -1
  25. package/dist/cjs/loaders/api/api.js +2 -2
  26. package/dist/cjs/loaders/configure/configure.js +1 -0
  27. package/dist/esm/cdn/polyfills.js +3 -1
  28. package/dist/esm/common/constants/env.cdn.js +1 -1
  29. package/dist/esm/common/constants/env.npm.js +1 -1
  30. package/dist/esm/common/drain/drain.js +1 -1
  31. package/dist/esm/common/harvest/harvest-scheduler.js +4 -2
  32. package/dist/esm/features/ajax/constants.js +2 -1
  33. package/dist/esm/features/jserrors/aggregate/index.js +2 -1
  34. package/dist/esm/features/metrics/aggregate/index.js +0 -8
  35. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  36. package/dist/esm/features/session_replay/aggregate/index.js +32 -37
  37. package/dist/esm/features/session_replay/constants.js +4 -1
  38. package/dist/esm/features/session_replay/instrument/index.js +54 -14
  39. package/dist/esm/features/session_replay/shared/utils.js +4 -6
  40. package/dist/esm/features/session_trace/aggregate/index.js +182 -527
  41. package/dist/esm/features/session_trace/aggregate/trace/node.js +12 -0
  42. package/dist/esm/features/session_trace/aggregate/trace/storage.js +282 -0
  43. package/dist/esm/features/session_trace/constants.js +2 -1
  44. package/dist/esm/features/session_trace/instrument/index.js +7 -3
  45. package/dist/esm/features/utils/aggregate-base.js +1 -0
  46. package/dist/esm/features/utils/feature-gates.js +11 -0
  47. package/dist/esm/features/utils/instrument-base.js +3 -2
  48. package/dist/esm/loaders/agent-base.js +4 -0
  49. package/dist/esm/loaders/api/api-methods.js +1 -1
  50. package/dist/esm/loaders/api/api.js +2 -2
  51. package/dist/esm/loaders/configure/configure.js +1 -0
  52. package/dist/types/common/harvest/harvest-scheduler.d.ts +1 -0
  53. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  54. package/dist/types/features/ajax/constants.d.ts +1 -0
  55. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  56. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  57. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  58. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  59. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  60. package/dist/types/features/session_replay/constants.d.ts +3 -0
  61. package/dist/types/features/session_replay/instrument/index.d.ts +0 -1
  62. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  63. package/dist/types/features/session_replay/shared/utils.d.ts +1 -1
  64. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
  65. package/dist/types/features/session_trace/aggregate/index.d.ts +39 -52
  66. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/session_trace/aggregate/trace/node.d.ts +12 -0
  68. package/dist/types/features/session_trace/aggregate/trace/node.d.ts.map +1 -0
  69. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +43 -0
  70. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -0
  71. package/dist/types/features/session_trace/constants.d.ts +1 -0
  72. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  73. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  74. package/dist/types/features/utils/aggregate-base.d.ts +1 -0
  75. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  76. package/dist/types/features/utils/feature-gates.d.ts +2 -0
  77. package/dist/types/features/utils/feature-gates.d.ts.map +1 -0
  78. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  79. package/dist/types/loaders/agent-base.d.ts +1 -0
  80. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  81. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/cdn/polyfills.js +2 -0
  84. package/src/common/drain/drain.js +1 -1
  85. package/src/common/harvest/harvest-scheduler.js +4 -2
  86. package/src/features/ajax/constants.js +2 -0
  87. package/src/features/jserrors/aggregate/index.js +2 -1
  88. package/src/features/metrics/aggregate/index.js +0 -8
  89. package/src/features/page_view_event/aggregate/index.js +1 -1
  90. package/src/features/session_replay/aggregate/index.js +30 -39
  91. package/src/features/session_replay/constants.js +4 -0
  92. package/src/features/session_replay/instrument/index.js +48 -8
  93. package/src/features/session_replay/shared/__mocks__/utils.js +0 -1
  94. package/src/features/session_replay/shared/utils.js +4 -7
  95. package/src/features/session_trace/aggregate/index.js +157 -493
  96. package/src/features/session_trace/aggregate/trace/node.js +12 -0
  97. package/src/features/session_trace/aggregate/trace/storage.js +287 -0
  98. package/src/features/session_trace/constants.js +1 -0
  99. package/src/features/session_trace/instrument/index.js +7 -2
  100. package/src/features/utils/__mocks__/feature-gates.js +1 -0
  101. package/src/features/utils/aggregate-base.js +1 -0
  102. package/src/features/utils/feature-gates.js +11 -0
  103. package/src/features/utils/instrument-base.js +3 -2
  104. package/src/loaders/agent-base.js +4 -0
  105. package/src/loaders/api/api-methods.js +1 -1
  106. package/src/loaders/api/api.js +2 -2
  107. package/src/loaders/configure/configure.js +1 -0
  108. package/dist/cjs/features/session_replay/shared/replay-mode.js +0 -28
  109. package/dist/cjs/features/utils/handler-cache.js +0 -70
  110. package/dist/esm/features/session_replay/shared/replay-mode.js +0 -23
  111. package/dist/esm/features/utils/handler-cache.js +0 -63
  112. package/dist/types/features/session_replay/shared/replay-mode.d.ts +0 -9
  113. package/dist/types/features/session_replay/shared/replay-mode.d.ts.map +0 -1
  114. package/dist/types/features/utils/handler-cache.d.ts +0 -23
  115. package/dist/types/features/utils/handler-cache.d.ts.map +0 -1
  116. package/src/features/session_replay/shared/replay-mode.js +0 -23
  117. package/src/features/utils/handler-cache.js +0 -65
@@ -0,0 +1,12 @@
1
+ /**
2
+ * All nodes reported to the consumer must take this shape
3
+ */
4
+ export class TraceNode {
5
+ constructor (name, start, end, origin, type) {
6
+ this.n = name
7
+ this.s = start
8
+ this.e = end
9
+ this.o = origin
10
+ this.t = type
11
+ }
12
+ }
@@ -0,0 +1,287 @@
1
+ import { globalScope } from '../../../../common/constants/runtime'
2
+ import { MODE } from '../../../../common/session/constants'
3
+ import { now } from '../../../../common/timing/now'
4
+ import { parseUrl } from '../../../../common/url/parse-url'
5
+ import { MAX_NODES_PER_HARVEST } from '../../constants'
6
+ import { TraceNode } from './node'
7
+
8
+ const ERROR_MODE_SECONDS_WINDOW = 30 * 1000 // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
9
+ const SUPPORTS_PERFORMANCE_OBSERVER = typeof globalScope.PerformanceObserver === 'function'
10
+
11
+ const ignoredEvents = {
12
+ // we find that certain events make the data too noisy to be useful
13
+ global: { mouseup: true, mousedown: true },
14
+ // certain events are present both in the window and in PVT metrics. PVT metrics are prefered so the window events should be ignored
15
+ window: { load: true, pagehide: true },
16
+ // when ajax instrumentation is disabled, all XMLHttpRequest events will return with origin = xhrOriginMissing and should be ignored
17
+ xhrOriginMissing: { ignoreAll: true }
18
+ }
19
+ const toAggregate = {
20
+ typing: [1000, 2000],
21
+ scrolling: [100, 1000],
22
+ mousing: [1000, 2000],
23
+ touching: [1000, 2000]
24
+ }
25
+
26
+ /** The purpose of this class is to manage, normalize, and retrieve ST nodes as needed without polluting the main ST modules */
27
+ export class TraceStorage {
28
+ nodeCount = 0
29
+ trace = {}
30
+ earliestTimeStamp = Infinity
31
+ latestTimeStamp = 0
32
+ tempStorage = []
33
+ prevStoredEvents = new Set()
34
+
35
+ constructor (parent) {
36
+ this.parent = parent
37
+ }
38
+
39
+ /** Central function called by all the other store__ & addToTrace API to append a trace node. */
40
+ storeSTN (stn) {
41
+ if (this.parent.blocked) return
42
+ if (this.nodeCount >= MAX_NODES_PER_HARVEST) { // limit the amount of pending data awaiting next harvest
43
+ if (this.parent.agentRuntime.session.state.sessionTraceMode !== MODE.ERROR) return
44
+ 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
45
+ if (openedSpace === 0) return
46
+ }
47
+ while (this.tempStorage.length) {
48
+ this.storeSTN(this.tempStorage.shift())
49
+ }
50
+
51
+ if (this.trace[stn.n]) this.trace[stn.n].push(stn)
52
+ else this.trace[stn.n] = [stn]
53
+
54
+ if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s
55
+ if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s
56
+ this.nodeCount++
57
+ }
58
+
59
+ /**
60
+ * Trim the collection of nodes awaiting harvest such that those seen outside a certain span of time are discarded.
61
+ * @param {number} lookbackDuration Past length of time until now for which we care about nodes, in milliseconds
62
+ * @returns {number} However many nodes were discarded after trimming.
63
+ */
64
+ trimSTNs (lookbackDuration) {
65
+ let prunedNodes = 0
66
+ const cutoffHighResTime = Math.max(now() - lookbackDuration, 0)
67
+ Object.keys(this.trace).forEach(nameCategory => {
68
+ const nodeList = this.trace[nameCategory]
69
+ /* Notice nodes are appending under their name's list as they end and are stored. This means each list is already (roughly) sorted in chronological order by end time.
70
+ * This isn't exact since nodes go through some processing & EE handlers chain, but it's close enough as we still capture nodes whose duration overlaps the lookback window.
71
+ * ASSUMPTION: all 'end' timings stored are relative to timeOrigin (DOMHighResTimeStamp) and not Unix epoch based. */
72
+ let cutoffIdx = nodeList.findIndex(node => cutoffHighResTime <= node.e)
73
+
74
+ if (cutoffIdx === 0) return
75
+ else if (cutoffIdx < 0) { // whole list falls outside lookback window and is irrelevant
76
+ cutoffIdx = nodeList.length
77
+ delete this.trace[nameCategory]
78
+ } else nodeList.splice(0, cutoffIdx) // chop off everything outside our window i.e. before the last <lookbackDuration> timeframe
79
+
80
+ this.nodeCount -= cutoffIdx
81
+ prunedNodes += cutoffIdx
82
+ })
83
+ return prunedNodes
84
+ }
85
+
86
+ /** Used by session trace's harvester to create the payload body. */
87
+ takeSTNs () {
88
+ if (!SUPPORTS_PERFORMANCE_OBSERVER) { // if PO isn't supported, this checks resourcetiming buffer every harvest.
89
+ this.storeResources(globalScope.performance?.getEntriesByType?.('resource'))
90
+ }
91
+
92
+ const stns = Object.entries(this.trace).flatMap(([name, listOfSTNodes]) => { // basically take the "this.trace" map-obj and concat all the list-type values
93
+ if (!(name in toAggregate)) return listOfSTNodes
94
+ // Special processing for event nodes dealing with user inputs:
95
+ const reindexByOriginFn = this.smearEvtsByOrigin(name)
96
+ const partitionListByOriginMap = listOfSTNodes.sort((a, b) => a.s - b.s).reduce(reindexByOriginFn, {})
97
+ return Object.values(partitionListByOriginMap).flat() // join the partitions back into 1-D, now ordered by origin then start time
98
+ }, this)
99
+ if (stns.length === 0) return {}
100
+
101
+ this.trace = {}
102
+ this.nodeCount = 0
103
+ const earliestTimeStamp = this.earliestTimeStamp
104
+ this.earliestTimeStamp = Infinity
105
+ const latestTimeStamp = this.latestTimeStamp
106
+ this.latestTimeStamp = 0
107
+
108
+ return { stns, earliestTimeStamp, latestTimeStamp }
109
+ }
110
+
111
+ smearEvtsByOrigin (name) {
112
+ const maxGap = toAggregate[name][0]
113
+ const maxLen = toAggregate[name][1]
114
+ const lastO = {}
115
+
116
+ return (byOrigin, evtNode) => {
117
+ let lastArr = byOrigin[evtNode.o]
118
+ if (!lastArr) lastArr = byOrigin[evtNode.o] = []
119
+
120
+ const last = lastO[evtNode.o]
121
+
122
+ if (name === 'scrolling' && !trivial(evtNode)) {
123
+ lastO[evtNode.o] = null
124
+ evtNode.n = 'scroll'
125
+ lastArr.push(evtNode)
126
+ } else if (last && (evtNode.s - last.s) < maxLen && last.e > (evtNode.s - maxGap)) {
127
+ last.e = evtNode.e
128
+ } else {
129
+ lastO[evtNode.o] = evtNode
130
+ lastArr.push(evtNode)
131
+ }
132
+
133
+ return byOrigin
134
+ }
135
+
136
+ function trivial (node) {
137
+ const limit = 4
138
+ return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && (node.e - node.s) < limit)
139
+ }
140
+ }
141
+
142
+ processPVT (name, value, attrs) {
143
+ this.storeTiming({ [name]: value })
144
+ if (hasFID(name, attrs)) this.storeEvent({ type: 'fid', target: 'document' }, 'document', value, value + attrs.fid)
145
+
146
+ function hasFID (name, attrs) {
147
+ return name === 'fi' && !!attrs && typeof attrs.fid === 'number'
148
+ }
149
+ }
150
+
151
+ storeTiming (timingEntry) {
152
+ if (!timingEntry) return
153
+
154
+ // loop iterates through prototype also (for FF)
155
+ for (let key in timingEntry) {
156
+ let val = timingEntry[key]
157
+
158
+ // ignore size and status type nodes that do not map to timestamp metrics
159
+ const lck = key.toLowerCase()
160
+ if (lck.indexOf('size') >= 0 || lck.indexOf('status') >= 0) continue
161
+
162
+ // ignore inherited methods, meaningless 0 values, and bogus timestamps
163
+ // that are in the future (Microsoft Edge seems to sometimes produce these)
164
+ if (!(typeof val === 'number' && val >= 0)) continue
165
+
166
+ val = Math.round(val)
167
+ this.storeSTN(new TraceNode(key, val, val, 'document', 'timing'))
168
+ }
169
+ }
170
+
171
+ // Tracks the events and their listener's duration on objects wrapped by wrap-events.
172
+ storeEvent (currentEvent, target, start, end) {
173
+ if (this.shouldIgnoreEvent(currentEvent, target)) return
174
+ if (this.prevStoredEvents.has(currentEvent)) return // prevent multiple listeners of an event from creating duplicate trace nodes per occurrence. Cleared every harvest. near-zero chance for re-duplication after clearing per harvest since the timestamps of the event are considered for uniqueness.
175
+ this.prevStoredEvents.add(currentEvent)
176
+
177
+ const evt = new TraceNode(this.evtName(currentEvent.type), start, end, undefined, 'event')
178
+ try {
179
+ // webcomponents-lite.js can trigger an exception on currentEvent.target getter because
180
+ // it does not check currentEvent.currentTarget before calling getRootNode() on it
181
+ evt.o = this.evtOrigin(currentEvent.target, target)
182
+ } catch (e) {
183
+ evt.o = this.evtOrigin(null, target)
184
+ }
185
+ this.storeSTN(evt)
186
+ }
187
+
188
+ shouldIgnoreEvent (event, target) {
189
+ const origin = this.evtOrigin(event.target, target)
190
+ if (event.type in ignoredEvents.global) return true
191
+ if (!!ignoredEvents[origin] && ignoredEvents[origin].ignoreAll) return true
192
+ return !!(!!ignoredEvents[origin] && event.type in ignoredEvents[origin])
193
+ }
194
+
195
+ evtName (type) {
196
+ switch (type) {
197
+ case 'keydown':
198
+ case 'keyup':
199
+ case 'keypress':
200
+ return 'typing'
201
+ case 'mousemove':
202
+ case 'mouseenter':
203
+ case 'mouseleave':
204
+ case 'mouseover':
205
+ case 'mouseout':
206
+ return 'mousing'
207
+ case 'scroll':
208
+ return 'scrolling'
209
+ case 'touchstart':
210
+ case 'touchmove':
211
+ case 'touchend':
212
+ case 'touchcancel':
213
+ case 'touchenter':
214
+ case 'touchleave':
215
+ return 'touching'
216
+ default:
217
+ return type
218
+ }
219
+ }
220
+
221
+ evtOrigin (t, target) {
222
+ let origin = 'unknown'
223
+
224
+ if (t && t instanceof XMLHttpRequest) {
225
+ const params = this.parent.ee.context(t).params
226
+ if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing'
227
+ origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname
228
+ } else if (t && typeof (t.tagName) === 'string') {
229
+ origin = t.tagName.toLowerCase()
230
+ if (t.id) origin += '#' + t.id
231
+ if (t.className) {
232
+ for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i]
233
+ }
234
+ }
235
+
236
+ if (origin === 'unknown') {
237
+ if (typeof target === 'string') origin = target
238
+ else if (target === document) origin = 'document'
239
+ else if (target === window) origin = 'window'
240
+ else if (target instanceof FileReader) origin = 'FileReader'
241
+ }
242
+
243
+ return origin
244
+ }
245
+
246
+ // Tracks when the window history API specified by wrap-history is used.
247
+ storeHist (path, old, time) {
248
+ this.storeSTN(new TraceNode('history.pushState', time, time, path, old))
249
+ }
250
+
251
+ #laststart = 0
252
+ // Processes all the PerformanceResourceTiming entries captured (by observer).
253
+ storeResources (resources) {
254
+ if (!resources || resources.length === 0) return
255
+
256
+ resources.forEach((currentResource) => {
257
+ if ((currentResource.fetchStart | 0) <= this.#laststart) return // don't recollect already-seen resources
258
+
259
+ const { initiatorType, fetchStart, responseEnd, entryType } = currentResource
260
+ const { protocol, hostname, port, pathname } = parseUrl(currentResource.name)
261
+ const res = new TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, `${protocol}://${hostname}:${port}${pathname}`, entryType)
262
+ this.storeSTN(res)
263
+ })
264
+
265
+ this.#laststart = resources[resources.length - 1].fetchStart | 0
266
+ }
267
+
268
+ // JavascriptError (FEATURE) events pipes into ST here.
269
+ storeErrorAgg (type, name, params, metrics) {
270
+ if (type !== 'err') return // internal errors are purposefully ignored
271
+ this.storeSTN(new TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash))
272
+ }
273
+
274
+ // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
275
+ storeXhrAgg (type, name, params, metrics) {
276
+ if (type !== 'xhr') return
277
+ this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname
278
+ }`))
279
+ }
280
+
281
+ restoreNode (name, listOfSTNodes) {
282
+ if (this.nodeCount >= MAX_NODES_PER_HARVEST) return
283
+
284
+ this.nodeCount += listOfSTNodes.length
285
+ this.trace[name] = this.trace[name] ? listOfSTNodes.concat(this.trace[name]) : listOfSTNodes
286
+ }
287
+ }
@@ -8,3 +8,4 @@ export const END = '-end'
8
8
  export const FN_START = 'fn' + START
9
9
  export const FN_END = 'fn' + END
10
10
  export const PUSH_STATE = 'pushState'
11
+ export const MAX_NODES_PER_HARVEST = 1000
@@ -7,7 +7,8 @@ import { wrapHistory, wrapEvents } from '../../../common/wrap'
7
7
  import { InstrumentBase } from '../../utils/instrument-base'
8
8
  import * as CONSTANTS from '../constants'
9
9
  import { FEATURE_NAMES } from '../../../loaders/features/features'
10
- import { isBrowserScope } from '../../../common/constants/runtime'
10
+ import { deregisterDrain } from '../../../common/drain/drain'
11
+ import { canEnableSessionTracking } from '../../utils/feature-gates'
11
12
  import { now } from '../../../common/timing/now'
12
13
 
13
14
  const {
@@ -18,7 +19,11 @@ export class Instrument extends InstrumentBase {
18
19
  static featureName = FEATURE_NAME
19
20
  constructor (agentIdentifier, aggregator, auto = true) {
20
21
  super(agentIdentifier, aggregator, FEATURE_NAME, auto)
21
- if (!isBrowserScope) return // session traces not supported outside web env
22
+ const canTrackSession = canEnableSessionTracking(this.agentIdentifier)
23
+ if (!canTrackSession) {
24
+ deregisterDrain(this.agentIdentifier, this.featureName)
25
+ return
26
+ }
22
27
 
23
28
  const thisInstrumentEE = this.ee
24
29
  wrapHistory(thisInstrumentEE)
@@ -0,0 +1 @@
1
+ export const canEnableSessionTracking = jest.fn()
@@ -36,6 +36,7 @@ export class AggregateBase extends FeatureBase {
36
36
 
37
37
  drain () {
38
38
  drain(this.agentIdentifier, this.featureName)
39
+ this.drained = true
39
40
  }
40
41
 
41
42
  /**
@@ -0,0 +1,11 @@
1
+ import { getConfigurationValue } from '../../common/config/config'
2
+ import { isBrowserScope } from '../../common/constants/runtime'
3
+
4
+ /**
5
+ * Checks if session can be tracked, affects session entity and dependent features
6
+ * @param {string} agentId
7
+ * @returns {boolean}
8
+ */
9
+ export const canEnableSessionTracking = (agentId) => {
10
+ return isBrowserScope && getConfigurationValue(agentId, 'privacy.cookies_enabled') === true
11
+ }
@@ -11,7 +11,8 @@ import { isBrowserScope } from '../../common/constants/runtime'
11
11
  import { warn } from '../../common/util/console'
12
12
  import { FEATURE_NAMES } from '../../loaders/features/features'
13
13
  import { getConfigurationValue } from '../../common/config/config'
14
- import { canImportReplayAgg, enableSessionTracking } from '../session_replay/shared/utils'
14
+ import { canImportReplayAgg } from '../session_replay/shared/utils'
15
+ import { canEnableSessionTracking } from './feature-gates'
15
16
  import { single } from '../../common/util/invoke'
16
17
 
17
18
  /**
@@ -79,7 +80,7 @@ export class InstrumentBase extends FeatureBase {
79
80
  const importLater = async () => {
80
81
  let session
81
82
  try {
82
- if (enableSessionTracking(this.agentIdentifier)) { // would require some setup before certain features start
83
+ if (canEnableSessionTracking(this.agentIdentifier)) { // would require some setup before certain features start
83
84
  const { setupAgentSession } = await import(/* webpackChunkName: "session-manager" */ './agent-session')
84
85
  session = setupAgentSession(this.agentIdentifier)
85
86
  }
@@ -3,6 +3,7 @@
3
3
  import { warn } from '../common/util/console'
4
4
  import { SR_EVENT_EMITTER_TYPES } from '../features/session_replay/constants'
5
5
  import { generateRandomHexString } from '../common/ids/unique-id'
6
+ import { ee } from '../common/event-emitter/contextual-ee'
6
7
 
7
8
  /**
8
9
  * @typedef {import('./api/interaction-types').InteractionInstance} InteractionInstance
@@ -13,6 +14,9 @@ export class AgentBase {
13
14
 
14
15
  constructor (agentIdentifier = generateRandomHexString(16)) {
15
16
  this.agentIdentifier = agentIdentifier
17
+
18
+ // Assign the observation context to the event emitter, so it knows how to create observation contexts
19
+ this.ee = ee.get(agentIdentifier)
16
20
  }
17
21
 
18
22
  /**
@@ -3,7 +3,7 @@ import { SR_EVENT_EMITTER_TYPES } from '../../features/session_replay/constants'
3
3
  export const apiMethods = [
4
4
  'setErrorHandler', 'finished', 'addToTrace', 'addRelease',
5
5
  'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute',
6
- 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start', 'recordReplay', 'pauseReplay',
6
+ 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start',
7
7
  SR_EVENT_EMITTER_TYPES.RECORD, SR_EVENT_EMITTER_TYPES.PAUSE
8
8
  ]
9
9
 
@@ -196,8 +196,8 @@ export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false)
196
196
  import(/* webpackChunkName: "async-api" */'./apiAsync').then(({ setAPI }) => {
197
197
  setAPI(agentIdentifier)
198
198
  drain(agentIdentifier, 'api')
199
- }).catch(() => {
200
- warn('Downloading runtime APIs failed...')
199
+ }).catch((err) => {
200
+ warn('Downloading runtime APIs failed...', err)
201
201
  instanceEE.abort()
202
202
  })
203
203
  }
@@ -52,6 +52,7 @@ export function configure (agent, opts = {}, loaderType, forceDrain) {
52
52
  ...(updatedInit.ajax.deny_list || []),
53
53
  ...(updatedInit.ajax.block_internal ? internalTrafficList : [])
54
54
  ]
55
+ runtime.ptid = agent.agentIdentifier
55
56
  setRuntime(agent.agentIdentifier, runtime)
56
57
 
57
58
  if (agent.api === undefined) agent.api = setAPI(agent.agentIdentifier, forceDrain, agent.runSoftNavOverSpa)
@@ -1,28 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.getSessionReplayMode = getSessionReplayMode;
7
- var _config = require("../../../common/config/config");
8
- var _nreum = require("../../../common/window/nreum");
9
- var _sharedChannel = require("../../../common/constants/shared-channel");
10
- var _constants = require("../../../common/session/constants");
11
- /**
12
- * Figure out if the Replay feature is running (what mode it's in).
13
- * IMPORTANT: Session tracking is assumed to be ON; if applicable, check init's privacy.cookies_enabled setting before using this fn!
14
- * CRITICAL: This fn must be called prior to ALL features aggregate draining. If not, it will never resolve.
15
- * @param {String} agentId
16
- * @returns Promise that resolves to one of the values in MODE enum
17
- */
18
- async function getSessionReplayMode(agentId) {
19
- try {
20
- const newrelic = (0, _nreum.gosNREUM)();
21
- // Should be enabled by configuration and using an agent build that includes it (via checking that the instrument class was initialized).
22
- if ((0, _config.getConfigurationValue)(agentId, 'session_replay.enabled') && typeof newrelic.initializedAgents[agentId].features.session_replay === 'object') {
23
- const srInitialized = await newrelic.initializedAgents[agentId].features.session_replay.onAggregateImported;
24
- if (srInitialized) return await _sharedChannel.sharedChannel.sessionReplayInitialized; // wait for replay to determine which mode it's after running its sampling logic
25
- }
26
- } catch (e) {/* exception ==> off */}
27
- return _constants.MODE.OFF; // at any step of the way s.t. SR cannot be on by implication or is explicitly off
28
- }
@@ -1,70 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.HandlerCache = void 0;
7
- /**
8
- * A class to defer callback execution until a decision can be reached
9
- */
10
- class HandlerCache {
11
- /** @private @type {boolean | undefined} */
12
- #decision = undefined;
13
- /** @private @type {Function[]} */
14
- #cache = [];
15
- /** @private @type {Timeout} */
16
- #settleTimer = setTimeout(() => this.#close(), 5000);
17
- #noMoreChanges = false;
18
-
19
- /**
20
- * tell the handlerCache that its ok to immediately execute the callbacks that are triggered by the ee from this moment on
21
- * and execute all the storage callbacks saved up in the handlerCache
22
- * @private
23
- */
24
- #drain() {
25
- this.#cache.forEach(h => h());
26
- this.#close();
27
- }
28
-
29
- /**
30
- * tell the handlerCache not to execute any of the storage callbacks
31
- * and wipe out all the storage callbacks saved up in the handlerCache
32
- * @private
33
- */
34
- #close() {
35
- this.#cache = [];
36
- clearTimeout(this.#settleTimer);
37
- }
38
-
39
- /**
40
- * Wrap callback functions with this method to defer their execution until a decision has been reached
41
- * @param {Function} handler
42
- * @returns {void}
43
- */
44
- settle(handler) {
45
- if (this.#decision === false) {
46
- // Do nothing
47
- } else if (this.#decision === undefined) this.#cache.push(handler);else handler();
48
- }
49
-
50
- /**
51
- * Make a decision about what to do with the cache of callbacks.
52
- * --- if true: tell the handlerCache that its ok to immediately execute the callbacks that are triggered by the ee from this moment on
53
- * and execute all the storage callbacks saved up in the handlerCache ---
54
- * --- if false: tell the handlerCache not to execute any of the storage callbacks
55
- * and wipe out all the storage callbacks saved up in the handlerCache
56
- * @param {boolean} decision
57
- */
58
- decide(decision) {
59
- if (this.#noMoreChanges) return;
60
- this.#decision = decision;
61
- if (decision === false) this.#close();
62
- if (decision === true) this.#drain();
63
- }
64
- permanentlyDecide(decision) {
65
- if (this.#noMoreChanges) return;
66
- this.decide(decision);
67
- this.#noMoreChanges = true;
68
- }
69
- }
70
- exports.HandlerCache = HandlerCache;
@@ -1,23 +0,0 @@
1
- import { getConfigurationValue } from '../../../common/config/config';
2
- import { gosNREUM } from '../../../common/window/nreum';
3
- import { sharedChannel } from '../../../common/constants/shared-channel';
4
- import { MODE } from '../../../common/session/constants';
5
-
6
- /**
7
- * Figure out if the Replay feature is running (what mode it's in).
8
- * IMPORTANT: Session tracking is assumed to be ON; if applicable, check init's privacy.cookies_enabled setting before using this fn!
9
- * CRITICAL: This fn must be called prior to ALL features aggregate draining. If not, it will never resolve.
10
- * @param {String} agentId
11
- * @returns Promise that resolves to one of the values in MODE enum
12
- */
13
- export async function getSessionReplayMode(agentId) {
14
- try {
15
- const newrelic = gosNREUM();
16
- // Should be enabled by configuration and using an agent build that includes it (via checking that the instrument class was initialized).
17
- if (getConfigurationValue(agentId, 'session_replay.enabled') && typeof newrelic.initializedAgents[agentId].features.session_replay === 'object') {
18
- const srInitialized = await newrelic.initializedAgents[agentId].features.session_replay.onAggregateImported;
19
- if (srInitialized) return await sharedChannel.sessionReplayInitialized; // wait for replay to determine which mode it's after running its sampling logic
20
- }
21
- } catch (e) {/* exception ==> off */}
22
- return MODE.OFF; // at any step of the way s.t. SR cannot be on by implication or is explicitly off
23
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * A class to defer callback execution until a decision can be reached
3
- */
4
- export class HandlerCache {
5
- /** @private @type {boolean | undefined} */
6
- #decision = undefined;
7
- /** @private @type {Function[]} */
8
- #cache = [];
9
- /** @private @type {Timeout} */
10
- #settleTimer = setTimeout(() => this.#close(), 5000);
11
- #noMoreChanges = false;
12
-
13
- /**
14
- * tell the handlerCache that its ok to immediately execute the callbacks that are triggered by the ee from this moment on
15
- * and execute all the storage callbacks saved up in the handlerCache
16
- * @private
17
- */
18
- #drain() {
19
- this.#cache.forEach(h => h());
20
- this.#close();
21
- }
22
-
23
- /**
24
- * tell the handlerCache not to execute any of the storage callbacks
25
- * and wipe out all the storage callbacks saved up in the handlerCache
26
- * @private
27
- */
28
- #close() {
29
- this.#cache = [];
30
- clearTimeout(this.#settleTimer);
31
- }
32
-
33
- /**
34
- * Wrap callback functions with this method to defer their execution until a decision has been reached
35
- * @param {Function} handler
36
- * @returns {void}
37
- */
38
- settle(handler) {
39
- if (this.#decision === false) {
40
- // Do nothing
41
- } else if (this.#decision === undefined) this.#cache.push(handler);else handler();
42
- }
43
-
44
- /**
45
- * Make a decision about what to do with the cache of callbacks.
46
- * --- if true: tell the handlerCache that its ok to immediately execute the callbacks that are triggered by the ee from this moment on
47
- * and execute all the storage callbacks saved up in the handlerCache ---
48
- * --- if false: tell the handlerCache not to execute any of the storage callbacks
49
- * and wipe out all the storage callbacks saved up in the handlerCache
50
- * @param {boolean} decision
51
- */
52
- decide(decision) {
53
- if (this.#noMoreChanges) return;
54
- this.#decision = decision;
55
- if (decision === false) this.#close();
56
- if (decision === true) this.#drain();
57
- }
58
- permanentlyDecide(decision) {
59
- if (this.#noMoreChanges) return;
60
- this.decide(decision);
61
- this.#noMoreChanges = true;
62
- }
63
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Figure out if the Replay feature is running (what mode it's in).
3
- * IMPORTANT: Session tracking is assumed to be ON; if applicable, check init's privacy.cookies_enabled setting before using this fn!
4
- * CRITICAL: This fn must be called prior to ALL features aggregate draining. If not, it will never resolve.
5
- * @param {String} agentId
6
- * @returns Promise that resolves to one of the values in MODE enum
7
- */
8
- export function getSessionReplayMode(agentId: string): Promise<any>;
9
- //# sourceMappingURL=replay-mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"replay-mode.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/replay-mode.js"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,oEAUC"}
@@ -1,23 +0,0 @@
1
- /**
2
- * A class to defer callback execution until a decision can be reached
3
- */
4
- export class HandlerCache {
5
- /**
6
- * Wrap callback functions with this method to defer their execution until a decision has been reached
7
- * @param {Function} handler
8
- * @returns {void}
9
- */
10
- settle(handler: Function): void;
11
- /**
12
- * Make a decision about what to do with the cache of callbacks.
13
- * --- if true: tell the handlerCache that its ok to immediately execute the callbacks that are triggered by the ee from this moment on
14
- * and execute all the storage callbacks saved up in the handlerCache ---
15
- * --- if false: tell the handlerCache not to execute any of the storage callbacks
16
- * and wipe out all the storage callbacks saved up in the handlerCache
17
- * @param {boolean} decision
18
- */
19
- decide(decision: boolean): void;
20
- permanentlyDecide(decision: any): void;
21
- #private;
22
- }
23
- //# sourceMappingURL=handler-cache.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-cache.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/handler-cache.js"],"names":[],"mappings":"AAAA;;GAEG;AACH;IA6BE;;;;OAIG;IACH,2BAFa,IAAI,CAOhB;IAED;;;;;;;OAOG;IACH,iBAFW,OAAO,QAOjB;IAED,uCAIC;;CACF"}