@newrelic/browser-agent 1.242.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 (111) hide show
  1. package/CHANGELOG.md +1465 -0
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/session/session-entity.js +20 -2
  5. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  6. package/dist/cjs/features/ajax/aggregate/index.js +1 -1
  7. package/dist/cjs/features/session_replay/aggregate/index.js +84 -54
  8. package/dist/cjs/features/utils/feature-base.js +1 -2
  9. package/dist/cjs/loaders/api/api.js +2 -2
  10. package/dist/cjs/loaders/api/apiAsync.js +0 -37
  11. package/dist/cjs/loaders/configure/configure.js +1 -1
  12. package/dist/cjs/loaders/configure/public-path.js +6 -3
  13. package/dist/esm/common/constants/env.cdn.js +1 -1
  14. package/dist/esm/common/constants/env.npm.js +1 -1
  15. package/dist/esm/common/session/session-entity.js +18 -1
  16. package/dist/esm/common/wrap/wrap-function.js +1 -1
  17. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  18. package/dist/esm/features/session_replay/aggregate/index.js +83 -54
  19. package/dist/esm/features/utils/feature-base.js +1 -2
  20. package/dist/esm/loaders/api/api.js +2 -2
  21. package/dist/esm/loaders/api/apiAsync.js +1 -36
  22. package/dist/esm/loaders/configure/configure.js +1 -1
  23. package/dist/esm/loaders/configure/public-path.js +6 -3
  24. package/dist/types/common/session/session-entity.d.ts +5 -0
  25. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  26. package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
  27. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  28. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  29. package/dist/types/loaders/api/api.d.ts.map +1 -1
  30. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  31. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  32. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/src/common/session/session-entity.js +20 -1
  35. package/src/common/wrap/wrap-function.js +1 -1
  36. package/src/features/ajax/aggregate/index.js +2 -2
  37. package/src/features/session_replay/aggregate/index.js +81 -37
  38. package/src/features/utils/feature-base.js +1 -2
  39. package/src/loaders/api/api.js +1 -2
  40. package/src/loaders/api/apiAsync.js +1 -39
  41. package/src/loaders/configure/configure.js +1 -1
  42. package/src/loaders/configure/public-path.js +6 -3
  43. package/src/common/aggregate/aggregator.test.js +0 -107
  44. package/src/common/config/state/configurable.test.js +0 -73
  45. package/src/common/config/state/info.test.js +0 -31
  46. package/src/common/config/state/init.test.js +0 -68
  47. package/src/common/config/state/loader-config.test.js +0 -21
  48. package/src/common/config/state/runtime.test.js +0 -21
  49. package/src/common/constants/env.cdn.test.js +0 -7
  50. package/src/common/constants/env.npm.test.js +0 -7
  51. package/src/common/constants/env.test.js +0 -7
  52. package/src/common/constants/runtime.test.js +0 -176
  53. package/src/common/deny-list/deny-list.test.js +0 -104
  54. package/src/common/dom/query-selector.test.js +0 -24
  55. package/src/common/drain/drain.test.js +0 -74
  56. package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
  57. package/src/common/event-emitter/handle.test.js +0 -56
  58. package/src/common/event-emitter/register-handler.test.js +0 -61
  59. package/src/common/harvest/harvest-scheduler.test.js +0 -492
  60. package/src/common/harvest/harvest.test.js +0 -813
  61. package/src/common/ids/id.test.js +0 -92
  62. package/src/common/ids/unique-id.test.js +0 -58
  63. package/src/common/session/session-entity.component-test.js +0 -346
  64. package/src/common/storage/local-storage.test.js +0 -17
  65. package/src/common/timer/interaction-timer.component-test.js +0 -212
  66. package/src/common/timer/timer.test.js +0 -99
  67. package/src/common/timing/nav-timing.test.js +0 -161
  68. package/src/common/url/canonicalize-url.test.js +0 -45
  69. package/src/common/url/clean-url.test.js +0 -25
  70. package/src/common/url/encode.test.js +0 -81
  71. package/src/common/url/location.test.js +0 -15
  72. package/src/common/url/parse-url.test.js +0 -110
  73. package/src/common/url/protocol.test.js +0 -17
  74. package/src/common/util/console.test.js +0 -34
  75. package/src/common/util/data-size.test.js +0 -56
  76. package/src/common/util/feature-flags.test.js +0 -94
  77. package/src/common/util/get-or-set.test.js +0 -58
  78. package/src/common/util/invoke.test.js +0 -65
  79. package/src/common/util/map-own.test.js +0 -52
  80. package/src/common/util/obfuscate.component-test.js +0 -173
  81. package/src/common/util/stringify.test.js +0 -49
  82. package/src/common/util/submit-data.test.js +0 -183
  83. package/src/common/util/traverse.test.js +0 -50
  84. package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
  85. package/src/common/vitals/first-contentful-paint.test.js +0 -124
  86. package/src/common/vitals/first-input-delay.test.js +0 -88
  87. package/src/common/vitals/first-paint.test.js +0 -127
  88. package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
  89. package/src/common/vitals/largest-contentful-paint.test.js +0 -94
  90. package/src/common/vitals/long-task.test.js +0 -122
  91. package/src/common/vitals/time-to-first-byte.test.js +0 -147
  92. package/src/common/vitals/vital-metric.test.js +0 -171
  93. package/src/common/wrap/wrap-promise.component-test.js +0 -110
  94. package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
  95. package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
  96. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
  97. package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
  98. package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
  99. package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
  100. package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
  101. package/src/features/session_replay/aggregate/index.component-test.js +0 -317
  102. package/src/features/spa/aggregate/interaction-node.test.js +0 -17
  103. package/src/features/utils/agent-session.test.js +0 -194
  104. package/src/features/utils/aggregate-base.test.js +0 -123
  105. package/src/features/utils/feature-base.test.js +0 -45
  106. package/src/features/utils/handler-cache.test.js +0 -72
  107. package/src/features/utils/instrument-base.test.js +0 -216
  108. package/src/features/utils/lazy-feature-loader.test.js +0 -37
  109. package/src/loaders/api/api.component-test.js +0 -45
  110. package/src/loaders/api/api.test.js +0 -85
  111. package/src/loaders/api/apiAsync.test.js +0 -17
@@ -15,20 +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
24
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
25
- import { FEATURE_NAMES } from '../../../loaders/features/features'
26
25
 
27
26
  // would be better to get this dynamically in some way
28
27
  export const RRWEB_VERSION = '2.0.0-alpha.8'
29
28
 
30
29
  export const AVG_COMPRESSION = 0.12
31
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
+
32
59
  let recorder, gzipper, u8
33
60
 
34
61
  /** Vortex caps payload sizes at 1MB */
@@ -59,8 +86,6 @@ export class Aggregate extends AggregateBase {
59
86
  /** can shut off efforts to compress the data */
60
87
  this.shouldCompress = true
61
88
 
62
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
63
- this.isFirstChunk = false
64
89
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
65
90
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
66
91
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -74,7 +99,7 @@ export class Aggregate extends AggregateBase {
74
99
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
75
100
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
76
101
  */
77
- this.timestamp = { event: { first: undefined, last: undefined }, cycle: { first: undefined, last: undefined } }
102
+ this.cycleTimestamp = undefined
78
103
 
79
104
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
80
105
  this.payloadBytesEstimation = 0
@@ -93,17 +118,26 @@ export class Aggregate extends AggregateBase {
93
118
  if (shouldSetup) {
94
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.
95
120
  this.ee.on(SESSION_EVENTS.RESET, () => {
96
- this.abort('Session Reset')
121
+ this.abort(ABORT_REASONS.RESET)
97
122
  })
98
123
 
99
124
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
100
125
  this.ee.on(SESSION_EVENTS.PAUSE, () => { this.stopRecording() })
101
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.
102
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
103
131
  if (!this.initialized || this.mode === MODE.OFF) return
104
132
  this.startRecording()
105
133
  })
106
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
+
107
141
  // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
108
142
  this.scheduler = new HarvestScheduler('browser/blobs', {
109
143
  onFinished: this.onHarvestFinished.bind(this),
@@ -118,12 +152,13 @@ export class Aggregate extends AggregateBase {
118
152
  this.hasError = true
119
153
  this.errorNoticed = true
120
154
  // run once
121
- if (this.mode === MODE.ERROR) {
155
+ if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
122
156
  this.mode = MODE.FULL
123
157
  // if the error was noticed AFTER the recorder was already imported....
124
158
  if (recorder && this.initialized) {
125
159
  this.stopRecording()
126
160
  this.startRecording()
161
+
127
162
  this.scheduler.startTimer(this.harvestTimeSeconds)
128
163
 
129
164
  this.syncWithSessionManager({ sessionReplay: this.mode })
@@ -178,7 +213,7 @@ export class Aggregate extends AggregateBase {
178
213
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
179
214
  recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
180
215
  } catch (err) {
181
- return this.abort('Recorder failed to import')
216
+ return this.abort(ABORT_REASONS.IMPORT)
182
217
  }
183
218
 
184
219
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -200,8 +235,6 @@ export class Aggregate extends AggregateBase {
200
235
  }
201
236
  this.startRecording()
202
237
 
203
- this.isFirstChunk = !!session.isNew
204
-
205
238
  this.syncWithSessionManager({ sessionReplay: this.mode })
206
239
  }
207
240
 
@@ -216,6 +249,8 @@ export class Aggregate extends AggregateBase {
216
249
  this.scheduler.opts.gzip = false
217
250
  }
218
251
  // TODO -- Gracefully handle the buffer for retries.
252
+ const { session } = getRuntime(this.agentIdentifier)
253
+ if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
219
254
  this.clearBuffer()
220
255
  return [payload]
221
256
  }
@@ -223,8 +258,28 @@ export class Aggregate extends AggregateBase {
223
258
  getHarvestContents () {
224
259
  const agentRuntime = getRuntime(this.agentIdentifier)
225
260
  const info = getInfo(this.agentIdentifier)
226
- const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first
227
- 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()
228
283
  return {
229
284
  qs: {
230
285
  browser_monitoring_key: info.licenseKey,
@@ -236,12 +291,13 @@ export class Aggregate extends AggregateBase {
236
291
  'replay.firstTimestamp': firstTimestamp,
237
292
  'replay.lastTimestamp': lastTimestamp,
238
293
  'replay.durationMs': lastTimestamp - firstTimestamp,
294
+ 'replay.nodes': this.events.length,
239
295
  agentVersion: agentRuntime.version,
240
296
  session: agentRuntime.session.state.value,
241
297
  hasMeta: this.hasMeta,
242
298
  hasSnapshot: this.hasSnapshot,
243
299
  hasError: this.hasError,
244
- isFirstChunk: this.isFirstChunk,
300
+ isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
245
301
  decompressedBytes: this.payloadBytesEstimation,
246
302
  'nr.rrweb.version': RRWEB_VERSION
247
303
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
@@ -253,7 +309,7 @@ export class Aggregate extends AggregateBase {
253
309
  onHarvestFinished (result) {
254
310
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
255
311
  if (result.status === 429) {
256
- this.abort('429: Too many requests')
312
+ this.abort(ABORT_REASONS.TOO_MANY)
257
313
  }
258
314
 
259
315
  if (this.blocked) this.scheduler.stopTimer(true)
@@ -262,7 +318,6 @@ export class Aggregate extends AggregateBase {
262
318
  /** Clears the buffer (this.events), and resets all payload metadata properties */
263
319
  clearBuffer () {
264
320
  this.events = []
265
- this.isFirstChunk = false
266
321
  this.hasSnapshot = false
267
322
  this.hasMeta = false
268
323
  this.hasError = false
@@ -274,7 +329,7 @@ export class Aggregate extends AggregateBase {
274
329
  startRecording () {
275
330
  if (!recorder) {
276
331
  warn('Recording library was never imported')
277
- return this.abort('Recorder was never imported')
332
+ return this.abort(ABORT_REASONS.IMPORT)
278
333
  }
279
334
  this.clearTimestamps()
280
335
  // set the fallbacks as early as possible
@@ -303,7 +358,7 @@ export class Aggregate extends AggregateBase {
303
358
 
304
359
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
305
360
  store (event, isCheckout) {
306
- this.setTimestamps(event)
361
+ this.setTimestamps()
307
362
  if (this.blocked) return
308
363
  const eventBytes = stringify(event).length
309
364
  /** The estimated size of the payload after compression */
@@ -311,8 +366,7 @@ export class Aggregate extends AggregateBase {
311
366
  // Vortex will block payloads at a certain size, we might as well not send.
312
367
  if (payloadSize > MAX_PAYLOAD_SIZE) {
313
368
  this.clearBuffer()
314
- this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Too-Big/Seen'], undefined, FEATURE_NAMES.metrics, this.ee)
315
- return this.abort('Payload too big')
369
+ return this.abort(ABORT_REASONS.TOO_BIG)
316
370
  }
317
371
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
318
372
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -323,19 +377,13 @@ export class Aggregate extends AggregateBase {
323
377
  }
324
378
 
325
379
  // meta event
326
- if (event.type === 4) {
380
+ if (event.type === RRWEB_EVENT_TYPES.Meta) {
327
381
  this.hasMeta = true
328
382
  this.lastMeta = event
329
383
  }
330
384
  // snapshot event
331
- if (event.type === 2) {
385
+ if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
332
386
  this.hasSnapshot = true
333
- // small chance that the meta event got separated from its matching snapshot across payload harvests
334
- // it needs to precede the snapshot, so shove it in first.
335
- if (!this.hasMeta) {
336
- this.events.push(this.lastMeta)
337
- this.hasMeta = true
338
- }
339
387
  }
340
388
 
341
389
  this.events.push(event)
@@ -355,18 +403,13 @@ export class Aggregate extends AggregateBase {
355
403
  recorder.takeFullSnapshot()
356
404
  }
357
405
 
358
- setTimestamps (event) {
406
+ setTimestamps () {
359
407
  // fallbacks if timestamps cannot be derived from rrweb events
360
- this.timestamp.cycle.last = getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
361
- if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last
362
- // timestamps based on rrweb events
363
- if (!event || !event.timestamp) return
364
- if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp
365
- this.timestamp.event.last = event.timestamp
408
+ if (!this.cycleTimestamp) this.cycleTimestamp = getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
366
409
  }
367
410
 
368
411
  clearTimestamps () {
369
- this.timestamp = { event: { first: undefined, last: undefined }, cycle: { first: undefined, last: undefined } }
412
+ this.cycleTimestamp = undefined
370
413
  }
371
414
 
372
415
  /** Estimate the payload size */
@@ -376,8 +419,9 @@ export class Aggregate extends AggregateBase {
376
419
  }
377
420
 
378
421
  /** Abort the feature, once aborted it will not resume */
379
- abort (reason) {
380
- warn(`SR aborted -- ${reason}`)
422
+ abort (reason = {}) {
423
+ warn(`SR aborted -- ${reason.message}`)
424
+ this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, [`SessionReplay/Abort/${ABORT_REASONS[reason.sm]}`])
381
425
  this.blocked = true
382
426
  this.mode = MODE.OFF
383
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
  /**
@@ -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
- })