@newrelic/browser-agent 1.241.0 → 1.243.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 (132) hide show
  1. package/CHANGELOG.md +1465 -0
  2. package/dist/cjs/cdn/polyfills/lite.js +13 -1
  3. package/dist/cjs/cdn/polyfills/pro.js +17 -1
  4. package/dist/cjs/cdn/polyfills/spa.js +18 -1
  5. package/dist/cjs/common/config/state/init.js +32 -5
  6. package/dist/cjs/common/constants/env.cdn.js +1 -1
  7. package/dist/cjs/common/constants/env.npm.js +1 -1
  8. package/dist/cjs/common/dom/query-selector.js +16 -0
  9. package/dist/cjs/common/session/session-entity.js +20 -2
  10. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  11. package/dist/cjs/features/ajax/aggregate/index.js +1 -1
  12. package/dist/cjs/features/session_replay/aggregate/index.js +84 -50
  13. package/dist/cjs/features/utils/feature-base.js +1 -2
  14. package/dist/cjs/features/utils/instrument-base.js +1 -0
  15. package/dist/cjs/loaders/api/api.js +2 -2
  16. package/dist/cjs/loaders/api/apiAsync.js +0 -37
  17. package/dist/cjs/loaders/configure/configure.js +1 -1
  18. package/dist/cjs/loaders/configure/public-path.js +6 -3
  19. package/dist/esm/cdn/polyfills/lite.js +8 -1
  20. package/dist/esm/cdn/polyfills/pro.js +13 -2
  21. package/dist/esm/cdn/polyfills/spa.js +13 -1
  22. package/dist/esm/common/config/state/init.js +32 -5
  23. package/dist/esm/common/constants/env.cdn.js +1 -1
  24. package/dist/esm/common/constants/env.npm.js +1 -1
  25. package/dist/esm/common/dom/query-selector.js +9 -0
  26. package/dist/esm/common/session/session-entity.js +18 -1
  27. package/dist/esm/common/wrap/wrap-function.js +1 -1
  28. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  29. package/dist/esm/features/session_replay/aggregate/index.js +83 -50
  30. package/dist/esm/features/utils/feature-base.js +1 -2
  31. package/dist/esm/features/utils/instrument-base.js +1 -0
  32. package/dist/esm/loaders/api/api.js +2 -2
  33. package/dist/esm/loaders/api/apiAsync.js +1 -36
  34. package/dist/esm/loaders/configure/configure.js +1 -1
  35. package/dist/esm/loaders/configure/public-path.js +6 -3
  36. package/dist/types/common/config/state/init.d.ts.map +1 -1
  37. package/dist/types/common/dom/query-selector.d.ts +2 -0
  38. package/dist/types/common/dom/query-selector.d.ts.map +1 -0
  39. package/dist/types/common/session/session-entity.d.ts +5 -0
  40. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  41. package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
  42. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  43. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  44. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  45. package/dist/types/loaders/api/api.d.ts.map +1 -1
  46. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  47. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  48. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  49. package/package.json +2 -2
  50. package/src/cdn/polyfills/lite.js +14 -1
  51. package/src/cdn/polyfills/pro.js +23 -2
  52. package/src/cdn/polyfills/spa.js +24 -1
  53. package/src/common/config/state/init.js +33 -4
  54. package/src/common/dom/query-selector.js +9 -0
  55. package/src/common/session/session-entity.js +20 -1
  56. package/src/common/wrap/wrap-function.js +1 -1
  57. package/src/features/ajax/aggregate/index.js +2 -2
  58. package/src/features/session_replay/aggregate/index.js +82 -34
  59. package/src/features/utils/feature-base.js +1 -2
  60. package/src/features/utils/instrument-base.js +1 -0
  61. package/src/loaders/api/api.js +1 -2
  62. package/src/loaders/api/apiAsync.js +1 -39
  63. package/src/loaders/configure/configure.js +1 -1
  64. package/src/loaders/configure/public-path.js +6 -3
  65. package/src/common/aggregate/aggregator.test.js +0 -107
  66. package/src/common/config/state/configurable.test.js +0 -73
  67. package/src/common/config/state/info.test.js +0 -31
  68. package/src/common/config/state/init.test.js +0 -28
  69. package/src/common/config/state/loader-config.test.js +0 -21
  70. package/src/common/config/state/runtime.test.js +0 -21
  71. package/src/common/constants/env.cdn.test.js +0 -7
  72. package/src/common/constants/env.npm.test.js +0 -7
  73. package/src/common/constants/env.test.js +0 -7
  74. package/src/common/constants/runtime.test.js +0 -176
  75. package/src/common/deny-list/deny-list.test.js +0 -104
  76. package/src/common/drain/drain.test.js +0 -74
  77. package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
  78. package/src/common/event-emitter/handle.test.js +0 -56
  79. package/src/common/event-emitter/register-handler.test.js +0 -61
  80. package/src/common/harvest/harvest-scheduler.test.js +0 -492
  81. package/src/common/harvest/harvest.test.js +0 -813
  82. package/src/common/ids/id.test.js +0 -92
  83. package/src/common/ids/unique-id.test.js +0 -58
  84. package/src/common/session/session-entity.component-test.js +0 -346
  85. package/src/common/storage/local-storage.test.js +0 -17
  86. package/src/common/timer/interaction-timer.component-test.js +0 -212
  87. package/src/common/timer/timer.test.js +0 -99
  88. package/src/common/timing/nav-timing.test.js +0 -161
  89. package/src/common/url/canonicalize-url.test.js +0 -45
  90. package/src/common/url/clean-url.test.js +0 -25
  91. package/src/common/url/encode.test.js +0 -81
  92. package/src/common/url/location.test.js +0 -15
  93. package/src/common/url/parse-url.test.js +0 -110
  94. package/src/common/url/protocol.test.js +0 -17
  95. package/src/common/util/console.test.js +0 -34
  96. package/src/common/util/data-size.test.js +0 -56
  97. package/src/common/util/feature-flags.test.js +0 -94
  98. package/src/common/util/get-or-set.test.js +0 -58
  99. package/src/common/util/invoke.test.js +0 -65
  100. package/src/common/util/map-own.test.js +0 -52
  101. package/src/common/util/obfuscate.component-test.js +0 -173
  102. package/src/common/util/stringify.test.js +0 -49
  103. package/src/common/util/submit-data.test.js +0 -183
  104. package/src/common/util/traverse.test.js +0 -50
  105. package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
  106. package/src/common/vitals/first-contentful-paint.test.js +0 -124
  107. package/src/common/vitals/first-input-delay.test.js +0 -88
  108. package/src/common/vitals/first-paint.test.js +0 -127
  109. package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
  110. package/src/common/vitals/largest-contentful-paint.test.js +0 -94
  111. package/src/common/vitals/long-task.test.js +0 -122
  112. package/src/common/vitals/time-to-first-byte.test.js +0 -147
  113. package/src/common/vitals/vital-metric.test.js +0 -171
  114. package/src/common/wrap/wrap-promise.component-test.js +0 -110
  115. package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
  116. package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
  117. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
  118. package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
  119. package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
  120. package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
  121. package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
  122. package/src/features/session_replay/aggregate/index.component-test.js +0 -317
  123. package/src/features/spa/aggregate/interaction-node.test.js +0 -17
  124. package/src/features/utils/agent-session.test.js +0 -194
  125. package/src/features/utils/aggregate-base.test.js +0 -123
  126. package/src/features/utils/feature-base.test.js +0 -45
  127. package/src/features/utils/handler-cache.test.js +0 -72
  128. package/src/features/utils/instrument-base.test.js +0 -216
  129. package/src/features/utils/lazy-feature-loader.test.js +0 -37
  130. package/src/loaders/api/api.component-test.js +0 -45
  131. package/src/loaders/api/api.test.js +0 -85
  132. package/src/loaders/api/apiAsync.test.js +0 -17
@@ -15,18 +15,47 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
15
15
  import { FEATURE_NAME } from '../constants'
16
16
  import { stringify } from '../../../common/util/stringify'
17
17
  import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
18
- import { SESSION_EVENTS, MODE } from '../../../common/session/session-entity'
18
+ import { SESSION_EVENTS, MODE, SESSION_EVENT_TYPES } from '../../../common/session/session-entity'
19
19
  import { AggregateBase } from '../../utils/aggregate-base'
20
20
  import { sharedChannel } from '../../../common/constants/shared-channel'
21
21
  import { obj as encodeObj } from '../../../common/url/encode'
22
22
  import { warn } from '../../../common/util/console'
23
23
  import { globalScope } from '../../../common/constants/runtime'
24
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
24
25
 
25
26
  // would be better to get this dynamically in some way
26
27
  export const RRWEB_VERSION = '2.0.0-alpha.8'
27
28
 
28
29
  export const AVG_COMPRESSION = 0.12
29
30
 
31
+ export const RRWEB_EVENT_TYPES = {
32
+ DomContentLoaded: 0,
33
+ Load: 1,
34
+ FullSnapshot: 2,
35
+ IncrementalSnapshot: 3,
36
+ Meta: 4,
37
+ Custom: 5
38
+ }
39
+
40
+ const ABORT_REASONS = {
41
+ RESET: {
42
+ message: 'Session was reset',
43
+ sm: 'Reset'
44
+ },
45
+ IMPORT: {
46
+ message: 'Recorder failed to import',
47
+ sm: 'Import'
48
+ },
49
+ TOO_MANY: {
50
+ message: '429: Too Many Requests',
51
+ sm: 'Too-Many'
52
+ },
53
+ TOO_BIG: {
54
+ message: 'Payload was too large',
55
+ sm: 'Too-Big'
56
+ }
57
+ }
58
+
30
59
  let recorder, gzipper, u8
31
60
 
32
61
  /** Vortex caps payload sizes at 1MB */
@@ -57,8 +86,6 @@ export class Aggregate extends AggregateBase {
57
86
  /** can shut off efforts to compress the data */
58
87
  this.shouldCompress = true
59
88
 
60
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
61
- this.isFirstChunk = false
62
89
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
63
90
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
64
91
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -72,7 +99,7 @@ export class Aggregate extends AggregateBase {
72
99
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
73
100
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
74
101
  */
75
- this.timestamp = { event: { first: undefined, last: undefined }, cycle: { first: undefined, last: undefined } }
102
+ this.cycleTimestamp = undefined
76
103
 
77
104
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
78
105
  this.payloadBytesEstimation = 0
@@ -91,17 +118,26 @@ export class Aggregate extends AggregateBase {
91
118
  if (shouldSetup) {
92
119
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
93
120
  this.ee.on(SESSION_EVENTS.RESET, () => {
94
- this.abort()
121
+ this.abort(ABORT_REASONS.RESET)
95
122
  })
96
123
 
97
124
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
98
125
  this.ee.on(SESSION_EVENTS.PAUSE, () => { this.stopRecording() })
99
126
  // The SessionEntity class can emit a message indicating the session was resumed (visibility change). This feature must start running again (if already running) if that occurs.
100
127
  this.ee.on(SESSION_EVENTS.RESUME, () => {
128
+ // if the mode changed on a different tab, it needs to update this instance to match
129
+ const { session } = getRuntime(this.agentIdentifier)
130
+ this.mode = session.state.sessionReplay
101
131
  if (!this.initialized || this.mode === MODE.OFF) return
102
132
  this.startRecording()
103
133
  })
104
134
 
135
+ this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
136
+ if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return
137
+ if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort('Session Entity was set to OFF on another tab')
138
+ this.mode = data.sessionReplay
139
+ })
140
+
105
141
  // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
106
142
  this.scheduler = new HarvestScheduler('browser/blobs', {
107
143
  onFinished: this.onHarvestFinished.bind(this),
@@ -116,12 +152,13 @@ export class Aggregate extends AggregateBase {
116
152
  this.hasError = true
117
153
  this.errorNoticed = true
118
154
  // run once
119
- if (this.mode === MODE.ERROR) {
155
+ if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
120
156
  this.mode = MODE.FULL
121
157
  // if the error was noticed AFTER the recorder was already imported....
122
158
  if (recorder && this.initialized) {
123
159
  this.stopRecording()
124
160
  this.startRecording()
161
+
125
162
  this.scheduler.startTimer(this.harvestTimeSeconds)
126
163
 
127
164
  this.syncWithSessionManager({ sessionReplay: this.mode })
@@ -176,7 +213,7 @@ export class Aggregate extends AggregateBase {
176
213
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
177
214
  recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
178
215
  } catch (err) {
179
- return this.abort()
216
+ return this.abort(ABORT_REASONS.IMPORT)
180
217
  }
181
218
 
182
219
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -198,8 +235,6 @@ export class Aggregate extends AggregateBase {
198
235
  }
199
236
  this.startRecording()
200
237
 
201
- this.isFirstChunk = !!session.isNew
202
-
203
238
  this.syncWithSessionManager({ sessionReplay: this.mode })
204
239
  }
205
240
 
@@ -214,6 +249,8 @@ export class Aggregate extends AggregateBase {
214
249
  this.scheduler.opts.gzip = false
215
250
  }
216
251
  // TODO -- Gracefully handle the buffer for retries.
252
+ const { session } = getRuntime(this.agentIdentifier)
253
+ if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
217
254
  this.clearBuffer()
218
255
  return [payload]
219
256
  }
@@ -221,8 +258,28 @@ export class Aggregate extends AggregateBase {
221
258
  getHarvestContents () {
222
259
  const agentRuntime = getRuntime(this.agentIdentifier)
223
260
  const info = getInfo(this.agentIdentifier)
224
- const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first
225
- const lastTimestamp = this.timestamp.event.last || this.timestamp.cycle.last
261
+
262
+ // do not let the last node be a meta node, since this NEEDS to precede a snapshot
263
+ // we will manually inject it later if we find a payload that is missing a meta node
264
+ const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta
265
+ if (payloadEndsWithMeta) {
266
+ this.lastMeta = this.events[this.events.length - 1]
267
+ this.events = this.events.slice(0, this.events.length - 1)
268
+ this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta)
269
+ }
270
+
271
+ // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
272
+ // we will manually inject it if this happens
273
+ const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot
274
+ if (payloadStartsWithFullSnapshot) {
275
+ this.hasMeta = true
276
+ this.events.unshift(this.lastMeta)
277
+ }
278
+
279
+ const firstEventTimestamp = this.events[0]?.timestamp
280
+ const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp
281
+ const firstTimestamp = firstEventTimestamp || this.cycleTimestamp
282
+ const lastTimestamp = lastEventTimestamp || getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
226
283
  return {
227
284
  qs: {
228
285
  browser_monitoring_key: info.licenseKey,
@@ -234,12 +291,13 @@ export class Aggregate extends AggregateBase {
234
291
  'replay.firstTimestamp': firstTimestamp,
235
292
  'replay.lastTimestamp': lastTimestamp,
236
293
  'replay.durationMs': lastTimestamp - firstTimestamp,
294
+ 'replay.nodes': this.events.length,
237
295
  agentVersion: agentRuntime.version,
238
296
  session: agentRuntime.session.state.value,
239
297
  hasMeta: this.hasMeta,
240
298
  hasSnapshot: this.hasSnapshot,
241
299
  hasError: this.hasError,
242
- isFirstChunk: this.isFirstChunk,
300
+ isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
243
301
  decompressedBytes: this.payloadBytesEstimation,
244
302
  'nr.rrweb.version': RRWEB_VERSION
245
303
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
@@ -251,7 +309,7 @@ export class Aggregate extends AggregateBase {
251
309
  onHarvestFinished (result) {
252
310
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
253
311
  if (result.status === 429) {
254
- this.abort()
312
+ this.abort(ABORT_REASONS.TOO_MANY)
255
313
  }
256
314
 
257
315
  if (this.blocked) this.scheduler.stopTimer(true)
@@ -260,7 +318,6 @@ export class Aggregate extends AggregateBase {
260
318
  /** Clears the buffer (this.events), and resets all payload metadata properties */
261
319
  clearBuffer () {
262
320
  this.events = []
263
- this.isFirstChunk = false
264
321
  this.hasSnapshot = false
265
322
  this.hasMeta = false
266
323
  this.hasError = false
@@ -272,7 +329,7 @@ export class Aggregate extends AggregateBase {
272
329
  startRecording () {
273
330
  if (!recorder) {
274
331
  warn('Recording library was never imported')
275
- return this.abort()
332
+ return this.abort(ABORT_REASONS.IMPORT)
276
333
  }
277
334
  this.clearTimestamps()
278
335
  // set the fallbacks as early as possible
@@ -301,7 +358,7 @@ export class Aggregate extends AggregateBase {
301
358
 
302
359
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
303
360
  store (event, isCheckout) {
304
- this.setTimestamps(event)
361
+ this.setTimestamps()
305
362
  if (this.blocked) return
306
363
  const eventBytes = stringify(event).length
307
364
  /** The estimated size of the payload after compression */
@@ -309,7 +366,7 @@ export class Aggregate extends AggregateBase {
309
366
  // Vortex will block payloads at a certain size, we might as well not send.
310
367
  if (payloadSize > MAX_PAYLOAD_SIZE) {
311
368
  this.clearBuffer()
312
- return this.abort()
369
+ return this.abort(ABORT_REASONS.TOO_BIG)
313
370
  }
314
371
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
315
372
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -320,19 +377,13 @@ export class Aggregate extends AggregateBase {
320
377
  }
321
378
 
322
379
  // meta event
323
- if (event.type === 4) {
380
+ if (event.type === RRWEB_EVENT_TYPES.Meta) {
324
381
  this.hasMeta = true
325
382
  this.lastMeta = event
326
383
  }
327
384
  // snapshot event
328
- if (event.type === 2) {
385
+ if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
329
386
  this.hasSnapshot = true
330
- // small chance that the meta event got separated from its matching snapshot across payload harvests
331
- // it needs to precede the snapshot, so shove it in first.
332
- if (!this.hasMeta) {
333
- this.events.push(this.lastMeta)
334
- this.hasMeta = true
335
- }
336
387
  }
337
388
 
338
389
  this.events.push(event)
@@ -352,18 +403,13 @@ export class Aggregate extends AggregateBase {
352
403
  recorder.takeFullSnapshot()
353
404
  }
354
405
 
355
- setTimestamps (event) {
406
+ setTimestamps () {
356
407
  // fallbacks if timestamps cannot be derived from rrweb events
357
- this.timestamp.cycle.last = getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
358
- if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last
359
- // timestamps based on rrweb events
360
- if (!event || !event.timestamp) return
361
- if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp
362
- this.timestamp.event.last = event.timestamp
408
+ if (!this.cycleTimestamp) this.cycleTimestamp = getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
363
409
  }
364
410
 
365
411
  clearTimestamps () {
366
- this.timestamp = { event: { first: undefined, last: undefined }, cycle: { first: undefined, last: undefined } }
412
+ this.cycleTimestamp = undefined
367
413
  }
368
414
 
369
415
  /** Estimate the payload size */
@@ -373,7 +419,9 @@ export class Aggregate extends AggregateBase {
373
419
  }
374
420
 
375
421
  /** Abort the feature, once aborted it will not resume */
376
- abort () {
422
+ abort (reason = {}) {
423
+ warn(`SR aborted -- ${reason.message}`)
424
+ this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, [`SessionReplay/Abort/${ABORT_REASONS[reason.sm]}`])
377
425
  this.blocked = true
378
426
  this.mode = MODE.OFF
379
427
  this.stopRecording()
@@ -1,4 +1,3 @@
1
- import { getRuntime } from '../../common/config/config'
2
1
  import { ee } from '../../common/event-emitter/contextual-ee'
3
2
 
4
3
  export class FeatureBase {
@@ -8,7 +7,7 @@ export class FeatureBase {
8
7
  /** @type {Aggregator} */
9
8
  this.aggregator = aggregator
10
9
  /** @type {ContextualEE} */
11
- this.ee = ee.get(agentIdentifier, getRuntime(this.agentIdentifier).isolatedBacklog)
10
+ this.ee = ee.get(agentIdentifier)
12
11
  /** @type {string} */
13
12
  this.featureName = featureName
14
13
  /**
@@ -108,6 +108,7 @@ export class InstrumentBase extends FeatureBase {
108
108
  warn(`Downloading and initializing ${this.featureName} failed...`, e)
109
109
  this.abortHandler?.() // undo any important alterations made to the page
110
110
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
111
+ drain(this.agentIdentifier, this.featureName)
111
112
  loadedSuccessfully(false)
112
113
  }
113
114
  }
@@ -19,7 +19,7 @@ export const CUSTOM_ATTR_GROUP = 'CUSTOM/' // the subgroup items should be store
19
19
  export function setTopLevelCallers () {
20
20
  const nr = gosCDN()
21
21
  const funcs = [
22
- 'setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease',
22
+ 'setErrorHandler', 'finished', 'addToTrace', 'addRelease',
23
23
  'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute',
24
24
  'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'
25
25
  ]
@@ -48,7 +48,6 @@ export function setAPI (agentIdentifier, forceDrain) {
48
48
  'setErrorHandler',
49
49
  'finished',
50
50
  'addToTrace',
51
- 'inlineHit',
52
51
  'addRelease'
53
52
  ]
54
53
 
@@ -1,22 +1,18 @@
1
1
  import { FEATURE_NAMES } from '../features/features'
2
- import { getConfiguration, getInfo, getRuntime } from '../../common/config/config'
2
+ import { getRuntime } from '../../common/config/config'
3
3
  import { ee } from '../../common/event-emitter/contextual-ee'
4
4
  import { handle } from '../../common/event-emitter/handle'
5
5
  import { registerHandler } from '../../common/event-emitter/register-handler'
6
6
  import { single } from '../../common/util/invoke'
7
- import * as submitData from '../../common/util/submit-data'
8
- import { isBrowserScope } from '../../common/constants/runtime'
9
7
  import { CUSTOM_METRIC_CHANNEL } from '../../features/metrics/constants'
10
8
 
11
9
  export function setAPI (agentIdentifier) {
12
10
  var instanceEE = ee.get(agentIdentifier)
13
- var cycle = 0
14
11
 
15
12
  var api = {
16
13
  finished: single(finished),
17
14
  setErrorHandler,
18
15
  addToTrace,
19
- inlineHit,
20
16
  addRelease
21
17
  }
22
18
 
@@ -47,40 +43,6 @@ export function setAPI (agentIdentifier) {
47
43
  handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, instanceEE)
48
44
  }
49
45
 
50
- // NREUM.inlineHit(requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime)
51
- //
52
- // requestName - the 'web page' name or service name
53
- // queueTime - the amount of time spent in the app tier queue
54
- // appTime - the amount of time spent in the application code
55
- // totalBackEndTime - the total roundtrip time of the remote service call
56
- // domTime - the time spent processing the result of the service call (or user defined)
57
- // frontEndTime - the time spent rendering the result of the service call (or user defined)
58
- function inlineHit (t, requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime) {
59
- if (!isBrowserScope) return
60
-
61
- requestName = window.encodeURIComponent(requestName)
62
- cycle += 1
63
-
64
- const agentInfo = getInfo(agentIdentifier)
65
- if (!agentInfo.beacon) return
66
-
67
- const agentInit = getConfiguration(agentIdentifier)
68
- const scheme = agentInit.ssl === false ? 'http' : 'https'
69
- const beacon = agentInit.proxy.beacon || agentInfo.beacon
70
- let url = `${scheme}://${beacon}/1/${agentInfo.licenseKey}`
71
-
72
- url += '?a=' + agentInfo.applicationID + '&'
73
- url += 't=' + requestName + '&'
74
- url += 'qt=' + ~~queueTime + '&'
75
- url += 'ap=' + ~~appTime + '&'
76
- url += 'be=' + ~~totalBackEndTime + '&'
77
- url += 'dc=' + ~~domTime + '&'
78
- url += 'fe=' + ~~frontEndTime + '&'
79
- url += 'c=' + cycle
80
-
81
- submitData.xhr({ url })
82
- }
83
-
84
46
  function setErrorHandler (t, handler) {
85
47
  getRuntime(agentIdentifier).onerror = handler
86
48
  }
@@ -33,7 +33,7 @@ export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
33
33
  if (!alreadySetOnce) {
34
34
  alreadySetOnce = true
35
35
  if (updatedInit.proxy.assets) {
36
- redefinePublicPath(updatedInit.proxy.assets + '/') // much like the info.beacon & init.proxy.beacon, this input should not end in a slash, but one is needed for webpack concat
36
+ redefinePublicPath(updatedInit.proxy.assets)
37
37
  internalTrafficList.push(updatedInit.proxy.assets)
38
38
  }
39
39
  if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon)
@@ -1,6 +1,9 @@
1
1
  // Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
2
2
 
3
- export const redefinePublicPath = (url) => {
4
- // There's no URL validation here, so caller should check arg if need be.
5
- __webpack_public_path__ = url // eslint-disable-line
3
+ export const redefinePublicPath = (urlString) => {
4
+ const isOrigin = urlString.startsWith('http')
5
+ // Input is not expected to end in a slash, but webpack concats as-is, so one is inserted.
6
+ urlString += '/'
7
+ // If there's no existing HTTP scheme, the secure protocol is prepended by default.
8
+ __webpack_public_path__ = isOrigin ? urlString : 'https://' + urlString // eslint-disable-line
6
9
  }
@@ -1,107 +0,0 @@
1
- import { Aggregator } from './aggregator.js'
2
-
3
- let aggregator
4
- beforeEach(() => {
5
- aggregator = new Aggregator({ agentIdentifier: 'blah' })
6
- })
7
-
8
- test('storing and getting buckets', () => {
9
- let bucket = aggregator.store('abc', '123')
10
- const expectedBucketPattern = { params: {}, metrics: { count: 1 } }
11
- expect(bucket).toEqual(expectedBucketPattern)
12
- expect(aggregator.get('abc', '123')).toEqual(expectedBucketPattern)
13
-
14
- aggregator.store('abc', '456')
15
- // check we can get all buckets under the same type
16
- expect(aggregator.get('abc')).toEqual({ 123: expectedBucketPattern, 456: expectedBucketPattern })
17
- })
18
-
19
- describe('storing the same bucket again', () => {
20
- test('increments the count', () => {
21
- aggregator.store('abc', '123')
22
- aggregator.store('abc', '123')
23
- expect(aggregator.get('abc', '123')).toEqual({ params: {}, metrics: { count: 2 } })
24
- })
25
-
26
- test('does not overwrite params', () => {
27
- aggregator.store('def', '123', { someParam: true })
28
- aggregator.store('def', '123', { anotherParam: true })
29
- expect(aggregator.get('def', '123')).toEqual({ params: { someParam: true }, metrics: { count: 2 } })
30
- })
31
-
32
- test('does not overwrite custom params either', () => {
33
- aggregator.store('ghi', '123', undefined, undefined, { someCustomParam: true })
34
- aggregator.store('ghi', '123', undefined, undefined, { someCustomParam: false })
35
- expect(aggregator.get('ghi', '123')).toEqual({ params: {}, custom: { someCustomParam: true }, metrics: { count: 2 } })
36
- })
37
- })
38
-
39
- describe('metrics are properly updated', () => {
40
- test('when using store fn', () => {
41
- const expectedBucketPattern = {
42
- params: {},
43
- metrics: {
44
- count: 2,
45
- met1: { t: 3, min: 1, max: 2, sos: 5, c: 2 },
46
- met2: { t: 7, min: 3, max: 4, sos: 25, c: 2 }
47
- }
48
- }
49
- aggregator.store('abc', '123', undefined, { met1: 1, met2: 3 })
50
- aggregator.store('abc', '123', undefined, { met1: 2, met2: 4 })
51
- expect(aggregator.get('abc', '123')).toEqual(expectedBucketPattern)
52
- })
53
-
54
- test('when using storeMetric fn', () => {
55
- const expectedBucketPattern = {
56
- params: {},
57
- stats: { t: 6, min: 1, max: 3, sos: 14, c: 3 }
58
- }
59
- aggregator.storeMetric('abc', 'metric', undefined, 2)
60
- aggregator.storeMetric('abc', 'metric', undefined, 1)
61
- aggregator.storeMetric('abc', 'metric', undefined, 3)
62
- expect(aggregator.get('abc', 'metric')).toEqual(expectedBucketPattern)
63
- })
64
- })
65
-
66
- test('take fn gets and deletes correctly', () => {
67
- aggregator.store('type1', 'a')
68
- aggregator.store('type1', 'b')
69
- aggregator.store('type2', 'a')
70
- aggregator.store('type3', 'a')
71
-
72
- expect(aggregator.take(['type0'])).toBeNull() // when type dne in aggregator
73
- let obj = aggregator.take(['type1', 'type2'])
74
- expect(obj.type1.length).toEqual(2)
75
- expect(obj.type2.length).toEqual(1)
76
- expect(aggregator.get('type1', 'a')).toBeUndefined() // should be gone now
77
- expect(aggregator.get('type3', 'a')).toEqual(expect.any(Object))
78
- })
79
-
80
- test('merge fn combines metrics correctly', () => {
81
- aggregator.store('abc', '123', undefined, { met1: 1, met2: 3 })
82
- let bucket = aggregator.store('abc', '123', undefined, { met1: 2 })
83
- const expectedMetrics = {
84
- count: 2,
85
- met1: { t: 3, min: 1, max: 2, sos: 5, c: 2 },
86
- met2: { t: 3 }
87
- }
88
- expect(bucket.metrics).toEqual(expectedMetrics)
89
-
90
- aggregator.merge('abc', '456', bucket.metrics)
91
- expect(aggregator.get('abc', '456').metrics).toEqual(expectedMetrics)
92
-
93
- aggregator.merge('abc', '123', {
94
- count: 4,
95
- met1: { t: 4, min: 0, max: 4, sos: 16, c: 2 },
96
- met2: { t: 5 },
97
- met3: { t: 6 },
98
- met4: { t: 7, min: 3, max: 4, sos: 25, c: 2 }
99
- })
100
- expect(aggregator.get('abc', '123').metrics).toEqual({
101
- count: 6,
102
- met1: { t: 7, min: 0, max: 4, sos: 21, c: 4 },
103
- met2: { t: 8, min: 3, max: 5, sos: 34, c: 2 },
104
- met3: { t: 6 },
105
- met4: { t: 7, min: 3, max: 4, sos: 25, c: 2 }
106
- })
107
- })
@@ -1,73 +0,0 @@
1
- import { getModeledObject } from './configurable'
2
-
3
- test('if either params are not objects, function fails', () => {
4
- let result = getModeledObject(123, {})
5
- expect(result).toEqual(undefined)
6
-
7
- result = getModeledObject({}, () => 'whatever')
8
- expect(result).toEqual(undefined)
9
- })
10
-
11
- test('models are not mutated by function', () => {
12
- const template = { dontchangethis: true }
13
- getModeledObject({ dontchangethis: false, addthis: true }, template)
14
- expect(template).toEqual({ dontchangethis: true })
15
- })
16
-
17
- const model = {
18
- eat: true,
19
- sleep: true,
20
- rave: true,
21
- repeat: {
22
- eat: true,
23
- sleep: true,
24
- rave: true,
25
- repeat: false
26
- }
27
- }
28
-
29
- test('empty object does not change model output', () => {
30
- expect(getModeledObject({}, model)).toEqual(model)
31
- })
32
-
33
- test('existing keys get their values changed', () => {
34
- let obj = {
35
- sleep: false,
36
- repeat: {
37
- eat: false
38
- }
39
- }
40
- expect(getModeledObject(obj, model)).toEqual({
41
- eat: true,
42
- sleep: false,
43
- rave: true,
44
- repeat: {
45
- eat: false, // should recurse
46
- sleep: true,
47
- rave: true,
48
- repeat: false
49
- }
50
- })
51
- })
52
-
53
- test('existing keys can change types too, to/from object', () => {
54
- // in other words, if replacement is not an object, it has no effect
55
- let obj = {
56
- rave: {},
57
- repeat: 'just a sad lonely string now'
58
- }
59
- expect(getModeledObject(obj, model)).toEqual({
60
- eat: true,
61
- sleep: true,
62
- rave: {},
63
- repeat: 'just a sad lonely string now'
64
- })
65
- })
66
-
67
- test('keys not in model are ignored', () => {
68
- expect(getModeledObject({ game: 'allnight' }, model)).toEqual(model)
69
- })
70
-
71
- test('but if model is empty, then keys are not ignored', () => {
72
- expect(getModeledObject({ game: 'allnight' }, {})).toEqual({ game: 'allnight' })
73
- })
@@ -1,31 +0,0 @@
1
- let isValid, getInfo, setInfo
2
- beforeEach(async () => {
3
- jest.resetModules()
4
- ;({ isValid, getInfo, setInfo } = await import('./info.js'))
5
- })
6
-
7
- test('set/getInfo should throw on an invalid agent id', () => {
8
- // currently only checks if it's a truthy value
9
- expect(() => setInfo(undefined, {})).toThrow('require an agent id')
10
- expect(() => getInfo(undefined)).toThrow('require an agent id')
11
- expect(() => setInfo('', {})).toThrow('require an agent id')
12
- expect(() => getInfo('')).toThrow('require an agent id')
13
- })
14
-
15
- test('set/getInfo works correctly', () => {
16
- expect(() => setInfo(123, { beacon: 'im here' })).not.toThrow() // notice setInfo accepts numbers
17
- let cachedObj = getInfo('123')
18
- expect(Object.keys(cachedObj).length).toBeGreaterThan(1)
19
- expect(cachedObj.beacon).toEqual('im here')
20
- expect(cachedObj.licenseKey).toEqual(undefined) // this should mirror default in info.js
21
- })
22
-
23
- test('isValid is correct', () => {
24
- setInfo('ab', { errorBeacon: 'some value' })
25
- setInfo('cd', { errorBeacon: 'some value', licenseKey: 'cereal box' })
26
- expect(isValid('ab')).toBeFalsy()
27
- expect(isValid('cd')).toBeFalsy()
28
-
29
- setInfo('ef', { errorBeacon: 'some value', licenseKey: 'cereal box', applicationID: '25' })
30
- expect(isValid('ef')).toBeTruthy()
31
- })
@@ -1,28 +0,0 @@
1
- let getConfiguration, setConfiguration, getConfigurationValue
2
- beforeEach(async () => {
3
- jest.resetModules()
4
- ;({ getConfiguration, setConfiguration, getConfigurationValue } = await import('./init.js'))
5
- })
6
-
7
- test('set/getConfiguration should throw on an invalid agent id', () => {
8
- // currently only checks if it's a truthy value
9
- expect(() => setConfiguration(undefined, {})).toThrow('require an agent id')
10
- expect(() => getConfiguration(undefined)).toThrow('require an agent id')
11
- expect(() => setConfiguration('', {})).toThrow('require an agent id')
12
- expect(() => getConfiguration('')).toThrow('require an agent id')
13
- })
14
-
15
- test('set/getConfiguration works correctly', () => {
16
- expect(() => setConfiguration(123, { jserrors: { enabled: false } })).not.toThrow() // notice setConfiguration accepts numbers
17
- let cachedObj = getConfiguration('123')
18
- expect(Object.keys(cachedObj).length).toBeGreaterThan(1)
19
- expect(cachedObj.jserrors.enabled).toEqual(false)
20
- expect(cachedObj.page_action.enabled).toEqual(true) // this should mirror default in init.js
21
- })
22
-
23
- test('getConfigurationValue parses path correctly', () => {
24
- setConfiguration('ab', { page_action: { harvestTimeSeconds: 1000 } })
25
- expect(getConfigurationValue('ab', '')).toBeUndefined()
26
- expect(getConfigurationValue('ab', 'page_action')).toEqual({ enabled: true, harvestTimeSeconds: 1000, autoStart: true })
27
- expect(getConfigurationValue('ab', 'page_action.harvestTimeSeconds')).toEqual(1000)
28
- })
@@ -1,21 +0,0 @@
1
- let getLoaderConfig, setLoaderConfig
2
- beforeEach(async () => {
3
- jest.resetModules()
4
- ;({ getLoaderConfig, setLoaderConfig } = await import('./loader-config.js'))
5
- })
6
-
7
- test('set/getLoaderConfig should throw on an invalid agent id', () => {
8
- // currently only checks if it's a truthy value
9
- expect(() => setLoaderConfig(undefined, {})).toThrow('require an agent id')
10
- expect(() => getLoaderConfig(undefined)).toThrow('require an agent id')
11
- expect(() => setLoaderConfig('', {})).toThrow('require an agent id')
12
- expect(() => getLoaderConfig('')).toThrow('require an agent id')
13
- })
14
-
15
- test('set/getLoaderConfig works correctly', () => {
16
- expect(() => setLoaderConfig(123, { trustKey: 1 })).not.toThrow() // notice setLoaderConfig accepts numbers
17
- let cachedObj = getLoaderConfig('123')
18
- expect(Object.keys(cachedObj).length).toBeGreaterThan(1)
19
- expect(cachedObj.trustKey).toEqual(1)
20
- expect(cachedObj.xpid).toBeUndefined() // this should mirror default in loader-config.js
21
- })