@newrelic/browser-agent 1.239.1 → 1.241.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 (178) hide show
  1. package/README.md +4 -0
  2. package/dist/cjs/cdn/pro.js +3 -2
  3. package/dist/cjs/cdn/spa.js +4 -3
  4. package/dist/cjs/common/config/state/init.js +25 -17
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/constants/runtime.js +9 -5
  8. package/dist/cjs/common/harvest/harvest.js +5 -3
  9. package/dist/cjs/common/vitals/constants.js +17 -0
  10. package/dist/cjs/common/vitals/cumulative-layout-shift.js +27 -0
  11. package/dist/cjs/common/vitals/first-contentful-paint.js +49 -0
  12. package/dist/cjs/common/vitals/first-input-delay.js +32 -0
  13. package/dist/{esm/features/page_view_timing → cjs/common/vitals}/first-paint.js +19 -17
  14. package/dist/cjs/common/vitals/interaction-to-next-paint.js +29 -0
  15. package/dist/cjs/common/vitals/largest-contentful-paint.js +41 -0
  16. package/dist/cjs/common/vitals/long-task.js +64 -0
  17. package/dist/cjs/common/vitals/time-to-first-byte.js +36 -0
  18. package/dist/cjs/common/vitals/vital-metric.js +71 -0
  19. package/dist/cjs/features/ajax/aggregate/index.js +4 -1
  20. package/dist/cjs/features/metrics/aggregate/index.js +17 -7
  21. package/dist/cjs/features/page_view_event/aggregate/index.js +18 -40
  22. package/dist/cjs/features/page_view_event/constants.js +2 -8
  23. package/dist/cjs/features/page_view_event/instrument/index.js +0 -22
  24. package/dist/cjs/features/page_view_timing/aggregate/index.js +36 -147
  25. package/dist/cjs/features/page_view_timing/instrument/index.js +0 -3
  26. package/dist/cjs/features/session_replay/aggregate/index.js +81 -35
  27. package/dist/cjs/features/session_trace/aggregate/index.js +13 -1
  28. package/dist/cjs/features/spa/aggregate/index.js +4 -3
  29. package/dist/cjs/loaders/agent.js +3 -0
  30. package/dist/cjs/loaders/api/api.js +2 -0
  31. package/dist/cjs/loaders/api/apiAsync.js +4 -2
  32. package/dist/cjs/loaders/browser-agent.js +2 -1
  33. package/dist/cjs/loaders/configure/configure.js +13 -1
  34. package/dist/cjs/loaders/configure/public-path.js +13 -0
  35. package/dist/cjs/loaders/configure/public-path.npm.js +10 -0
  36. package/dist/esm/cdn/pro.js +2 -1
  37. package/dist/esm/cdn/spa.js +2 -1
  38. package/dist/esm/common/config/state/init.js +25 -17
  39. package/dist/esm/common/constants/env.cdn.js +1 -1
  40. package/dist/esm/common/constants/env.npm.js +1 -1
  41. package/dist/esm/common/constants/runtime.js +5 -3
  42. package/dist/esm/common/harvest/harvest.js +6 -4
  43. package/dist/esm/common/vitals/constants.js +10 -0
  44. package/dist/esm/common/vitals/cumulative-layout-shift.js +20 -0
  45. package/dist/esm/common/vitals/first-contentful-paint.js +41 -0
  46. package/dist/esm/common/vitals/first-input-delay.js +25 -0
  47. package/dist/{cjs/features/page_view_timing → esm/common/vitals}/first-paint.js +12 -24
  48. package/dist/esm/common/vitals/interaction-to-next-paint.js +22 -0
  49. package/dist/esm/common/vitals/largest-contentful-paint.js +34 -0
  50. package/dist/esm/common/vitals/long-task.js +57 -0
  51. package/dist/esm/common/vitals/time-to-first-byte.js +29 -0
  52. package/dist/esm/common/vitals/vital-metric.js +64 -0
  53. package/dist/esm/features/ajax/aggregate/index.js +4 -1
  54. package/dist/esm/features/metrics/aggregate/index.js +18 -8
  55. package/dist/esm/features/page_view_event/aggregate/index.js +20 -42
  56. package/dist/esm/features/page_view_event/constants.js +1 -4
  57. package/dist/esm/features/page_view_event/instrument/index.js +0 -22
  58. package/dist/esm/features/page_view_timing/aggregate/index.js +37 -148
  59. package/dist/esm/features/page_view_timing/instrument/index.js +0 -3
  60. package/dist/esm/features/session_replay/aggregate/index.js +81 -35
  61. package/dist/esm/features/session_trace/aggregate/index.js +13 -1
  62. package/dist/esm/features/spa/aggregate/index.js +4 -3
  63. package/dist/esm/loaders/agent.js +2 -0
  64. package/dist/esm/loaders/api/api.js +2 -0
  65. package/dist/esm/loaders/api/apiAsync.js +5 -3
  66. package/dist/esm/loaders/browser-agent.js +2 -1
  67. package/dist/esm/loaders/configure/configure.js +13 -1
  68. package/dist/esm/loaders/configure/public-path.js +6 -0
  69. package/dist/esm/loaders/configure/public-path.npm.js +3 -0
  70. package/dist/types/common/config/state/init.d.ts.map +1 -1
  71. package/dist/types/common/constants/runtime.d.ts +3 -1
  72. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  73. package/dist/types/common/harvest/harvest.d.ts +0 -1
  74. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  75. package/dist/types/common/vitals/constants.d.ts +11 -0
  76. package/dist/types/common/vitals/constants.d.ts.map +1 -0
  77. package/dist/types/common/vitals/cumulative-layout-shift.d.ts +3 -0
  78. package/dist/types/common/vitals/cumulative-layout-shift.d.ts.map +1 -0
  79. package/dist/types/common/vitals/first-contentful-paint.d.ts +3 -0
  80. package/dist/types/common/vitals/first-contentful-paint.d.ts.map +1 -0
  81. package/dist/types/common/vitals/first-input-delay.d.ts +3 -0
  82. package/dist/types/common/vitals/first-input-delay.d.ts.map +1 -0
  83. package/dist/types/common/vitals/first-paint.d.ts +3 -0
  84. package/dist/types/common/vitals/first-paint.d.ts.map +1 -0
  85. package/dist/types/common/vitals/interaction-to-next-paint.d.ts +3 -0
  86. package/dist/types/common/vitals/interaction-to-next-paint.d.ts.map +1 -0
  87. package/dist/types/common/vitals/largest-contentful-paint.d.ts +3 -0
  88. package/dist/types/common/vitals/largest-contentful-paint.d.ts.map +1 -0
  89. package/dist/types/common/vitals/long-task.d.ts +3 -0
  90. package/dist/types/common/vitals/long-task.d.ts.map +1 -0
  91. package/dist/types/common/vitals/time-to-first-byte.d.ts +3 -0
  92. package/dist/types/common/vitals/time-to-first-byte.d.ts.map +1 -0
  93. package/dist/types/common/vitals/vital-metric.d.ts +18 -0
  94. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -0
  95. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  96. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  97. package/dist/types/features/page_view_event/aggregate/index.d.ts +3 -2
  98. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  99. package/dist/types/features/page_view_event/constants.d.ts +0 -3
  100. package/dist/types/features/page_view_event/constants.d.ts.map +1 -1
  101. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  102. package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -3
  103. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  104. package/dist/types/features/page_view_timing/instrument/index.d.ts.map +1 -1
  105. package/dist/types/features/session_replay/aggregate/index.d.ts +16 -4
  106. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  107. package/dist/types/features/session_trace/aggregate/index.d.ts +9 -1
  108. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  109. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  110. package/dist/types/loaders/agent.d.ts.map +1 -1
  111. package/dist/types/loaders/api/api.d.ts.map +1 -1
  112. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  113. package/dist/types/loaders/browser-agent.d.ts.map +1 -1
  114. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  115. package/dist/types/loaders/configure/public-path.d.ts +2 -0
  116. package/dist/types/loaders/configure/public-path.d.ts.map +1 -0
  117. package/dist/types/loaders/configure/public-path.npm.d.ts +2 -0
  118. package/dist/types/loaders/configure/public-path.npm.d.ts.map +1 -0
  119. package/package.json +2 -3
  120. package/src/cdn/pro.js +2 -0
  121. package/src/cdn/spa.js +2 -0
  122. package/src/common/config/state/init.js +21 -17
  123. package/src/common/constants/runtime.js +7 -3
  124. package/src/common/constants/runtime.test.js +8 -0
  125. package/src/common/harvest/harvest-scheduler.test.js +2 -2
  126. package/src/common/harvest/harvest.js +6 -4
  127. package/src/common/harvest/harvest.test.js +17 -0
  128. package/src/common/vitals/__mocks__/web-vitals.js +19 -0
  129. package/src/common/vitals/constants.js +10 -0
  130. package/src/common/vitals/cumulative-layout-shift.js +13 -0
  131. package/src/common/vitals/cumulative-layout-shift.test.js +71 -0
  132. package/src/common/vitals/first-contentful-paint.js +31 -0
  133. package/src/common/vitals/first-contentful-paint.test.js +124 -0
  134. package/src/common/vitals/first-input-delay.js +20 -0
  135. package/src/common/vitals/first-input-delay.test.js +88 -0
  136. package/src/{features/page_view_timing → common/vitals}/first-paint.js +11 -17
  137. package/src/common/vitals/first-paint.test.js +127 -0
  138. package/src/common/vitals/interaction-to-next-paint.js +13 -0
  139. package/src/common/vitals/interaction-to-next-paint.test.js +74 -0
  140. package/src/common/vitals/largest-contentful-paint.js +29 -0
  141. package/src/common/vitals/largest-contentful-paint.test.js +94 -0
  142. package/src/common/vitals/long-task.js +52 -0
  143. package/src/common/vitals/long-task.test.js +122 -0
  144. package/src/common/vitals/time-to-first-byte.js +21 -0
  145. package/src/common/vitals/time-to-first-byte.test.js +147 -0
  146. package/src/common/vitals/vital-metric.js +60 -0
  147. package/src/common/vitals/vital-metric.test.js +171 -0
  148. package/src/features/ajax/aggregate/index.js +5 -1
  149. package/src/features/metrics/aggregate/index.js +11 -4
  150. package/src/features/page_view_event/aggregate/index.js +20 -43
  151. package/src/features/page_view_event/constants.js +0 -3
  152. package/src/features/page_view_event/instrument/index.js +0 -21
  153. package/src/features/page_view_timing/aggregate/index.component-test.js +86 -0
  154. package/src/features/page_view_timing/aggregate/index.js +31 -108
  155. package/src/features/page_view_timing/instrument/index.js +0 -3
  156. package/src/features/session_replay/aggregate/index.component-test.js +10 -10
  157. package/src/features/session_replay/aggregate/index.js +62 -29
  158. package/src/features/session_trace/aggregate/index.js +15 -1
  159. package/src/features/spa/aggregate/index.js +4 -3
  160. package/src/loaders/agent.js +2 -0
  161. package/src/loaders/api/api.js +2 -0
  162. package/src/loaders/api/apiAsync.js +5 -4
  163. package/src/loaders/browser-agent.js +3 -1
  164. package/src/loaders/configure/configure.js +15 -7
  165. package/src/loaders/configure/public-path.js +6 -0
  166. package/src/loaders/configure/public-path.npm.js +4 -0
  167. package/dist/cjs/common/metrics/paint-metrics.js +0 -13
  168. package/dist/cjs/features/page_view_timing/long-tasks.js +0 -75
  169. package/dist/esm/common/metrics/paint-metrics.js +0 -6
  170. package/dist/esm/features/page_view_timing/long-tasks.js +0 -69
  171. package/dist/types/common/metrics/paint-metrics.d.ts +0 -2
  172. package/dist/types/common/metrics/paint-metrics.d.ts.map +0 -1
  173. package/dist/types/features/page_view_timing/first-paint.d.ts +0 -2
  174. package/dist/types/features/page_view_timing/first-paint.d.ts.map +0 -1
  175. package/dist/types/features/page_view_timing/long-tasks.d.ts +0 -2
  176. package/dist/types/features/page_view_timing/long-tasks.d.ts.map +0 -1
  177. package/src/common/metrics/paint-metrics.js +0 -6
  178. package/src/features/page_view_timing/long-tasks.js +0 -60
@@ -20,6 +20,7 @@ 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
+ import { globalScope } from '../../../common/constants/runtime'
23
24
 
24
25
  // would be better to get this dynamically in some way
25
26
  export const RRWEB_VERSION = '2.0.0-alpha.8'
@@ -63,15 +64,22 @@ export class Aggregate extends AggregateBase {
63
64
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
64
65
  */
65
66
  this.hasSnapshot = false
67
+ /** Payload metadata -- Should indicate that the payload being sent has a meta node. The meta node should always precede a snapshot node. */
68
+ this.hasMeta = false
66
69
  /** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
67
70
  this.hasError = false
68
71
 
69
- /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs. */
70
- this.timestamp = { first: undefined, last: undefined }
72
+ /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
73
+ * cycle timestamps are used as fallbacks if event timestamps cannot be used
74
+ */
75
+ this.timestamp = { event: { first: undefined, last: undefined }, cycle: { first: undefined, last: undefined } }
71
76
 
72
77
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
73
78
  this.payloadBytesEstimation = 0
74
79
 
80
+ /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
81
+ this.lastMeta = undefined
82
+
75
83
  const shouldSetup = (
76
84
  getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true &&
77
85
  getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true
@@ -123,8 +131,8 @@ export class Aggregate extends AggregateBase {
123
131
 
124
132
  this.waitForFlags(['sr']).then(([flagOn]) => this.initializeRecording(
125
133
  flagOn,
126
- Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.errorSampleRate'),
127
- Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.sampleRate')
134
+ (Math.random() * 100) < getConfigurationValue(this.agentIdentifier, 'session_replay.error_sampling_rate'),
135
+ (Math.random() * 100) < getConfigurationValue(this.agentIdentifier, 'session_replay.sampling_rate')
128
136
  )).then(() => sharedChannel.onReplayReady(this.mode)) // notify watchers that replay started with the mode
129
137
 
130
138
  this.drain()
@@ -164,6 +172,13 @@ export class Aggregate extends AggregateBase {
164
172
  this.mode = MODE.FULL
165
173
  }
166
174
 
175
+ try {
176
+ // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
177
+ recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
178
+ } catch (err) {
179
+ return this.abort()
180
+ }
181
+
167
182
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
168
183
  // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
169
184
  // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
@@ -172,13 +187,6 @@ export class Aggregate extends AggregateBase {
172
187
  this.scheduler.startTimer(this.harvestTimeSeconds)
173
188
  }
174
189
 
175
- try {
176
- // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
177
- recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
178
- } catch (err) {
179
- return this.abort()
180
- }
181
-
182
190
  try {
183
191
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
184
192
  const { gzipSync, strToU8 } = await import(/* webpackChunkName: "compressor" */'fflate')
@@ -213,6 +221,8 @@ export class Aggregate extends AggregateBase {
213
221
  getHarvestContents () {
214
222
  const agentRuntime = getRuntime(this.agentIdentifier)
215
223
  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
216
226
  return {
217
227
  qs: {
218
228
  browser_monitoring_key: info.licenseKey,
@@ -221,11 +231,12 @@ export class Aggregate extends AggregateBase {
221
231
  protocol_version: '0',
222
232
  attributes: encodeObj({
223
233
  ...(this.shouldCompress && { content_encoding: 'gzip' }),
224
- 'replay.firstTimestamp': this.timestamp.first,
225
- 'replay.lastTimestamp': this.timestamp.last,
226
- 'replay.durationMs': this.timestamp.last - this.timestamp.first,
234
+ 'replay.firstTimestamp': firstTimestamp,
235
+ 'replay.lastTimestamp': lastTimestamp,
236
+ 'replay.durationMs': lastTimestamp - firstTimestamp,
227
237
  agentVersion: agentRuntime.version,
228
238
  session: agentRuntime.session.state.value,
239
+ hasMeta: this.hasMeta,
229
240
  hasSnapshot: this.hasSnapshot,
230
241
  hasError: this.hasError,
231
242
  isFirstChunk: this.isFirstChunk,
@@ -251,6 +262,7 @@ export class Aggregate extends AggregateBase {
251
262
  this.events = []
252
263
  this.isFirstChunk = false
253
264
  this.hasSnapshot = false
265
+ this.hasMeta = false
254
266
  this.hasError = false
255
267
  this.payloadBytesEstimation = 0
256
268
  this.clearTimestamps()
@@ -262,19 +274,22 @@ export class Aggregate extends AggregateBase {
262
274
  warn('Recording library was never imported')
263
275
  return this.abort()
264
276
  }
277
+ this.clearTimestamps()
278
+ // set the fallbacks as early as possible
279
+ this.setTimestamps()
265
280
  this.recording = true
266
- const { blockClass, ignoreClass, maskTextClass, blockSelector, maskInputOptions, maskTextSelector, maskAllInputs } = getConfigurationValue(this.agentIdentifier, 'session_replay')
281
+ const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs } = getConfigurationValue(this.agentIdentifier, 'session_replay')
267
282
  // set up rrweb configurations for maximum privacy --
268
283
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
269
284
  const stop = recorder({
270
285
  emit: this.store.bind(this),
271
- blockClass,
272
- ignoreClass,
273
- maskTextClass,
274
- blockSelector,
275
- maskInputOptions,
276
- maskTextSelector,
277
- maskAllInputs,
286
+ blockClass: block_class,
287
+ ignoreClass: ignore_class,
288
+ maskTextClass: mask_text_class,
289
+ blockSelector: block_selector,
290
+ maskInputOptions: mask_input_options,
291
+ maskTextSelector: mask_text_selector,
292
+ maskAllInputs: mask_all_inputs,
278
293
  checkoutEveryNms: CHECKOUT_MS[this.mode]
279
294
  })
280
295
 
@@ -286,6 +301,7 @@ export class Aggregate extends AggregateBase {
286
301
 
287
302
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
288
303
  store (event, isCheckout) {
304
+ this.setTimestamps(event)
289
305
  if (this.blocked) return
290
306
  const eventBytes = stringify(event).length
291
307
  /** The estimated size of the payload after compression */
@@ -303,8 +319,21 @@ export class Aggregate extends AggregateBase {
303
319
  this.clearBuffer()
304
320
  }
305
321
 
306
- this.setTimestamps(event)
307
- if (event.type === 2) this.hasSnapshot = true
322
+ // meta event
323
+ if (event.type === 4) {
324
+ this.hasMeta = true
325
+ this.lastMeta = event
326
+ }
327
+ // snapshot event
328
+ if (event.type === 2) {
329
+ 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
+ }
308
337
 
309
338
  this.events.push(event)
310
339
  this.payloadBytesEstimation += eventBytes
@@ -323,14 +352,18 @@ export class Aggregate extends AggregateBase {
323
352
  recorder.takeFullSnapshot()
324
353
  }
325
354
 
326
- setTimestamps (rrwebEvent) {
327
- if (!rrwebEvent) return
328
- if (!this.timestamp.first) this.timestamp.first = rrwebEvent.timestamp
329
- this.timestamp.last = rrwebEvent.timestamp
355
+ setTimestamps (event) {
356
+ // 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
330
363
  }
331
364
 
332
365
  clearTimestamps () {
333
- this.timestamp = { first: undefined, last: undefined }
366
+ this.timestamp = { event: { first: undefined, last: undefined }, cycle: { first: undefined, last: undefined } }
334
367
  }
335
368
 
336
369
  /** Estimate the payload size */
@@ -443,7 +443,11 @@ export class Aggregate extends AggregateBase {
443
443
  this.storeResources(window.performance.getEntriesByType('resource'))
444
444
  }
445
445
 
446
+ let earliestTimeStamp = Infinity
446
447
  const stns = Object.entries(this.trace).flatMap(([name, listOfSTNodes]) => { // basically take the "this.trace" map-obj and concat all the list-type values
448
+ const oldestNodeTS = listOfSTNodes.reduce((acc, next) => (!acc || next.s < acc) ? next.s : acc, undefined)
449
+ if (oldestNodeTS < earliestTimeStamp) earliestTimeStamp = oldestNodeTS
450
+
447
451
  if (!(name in toAggregate)) return listOfSTNodes
448
452
  // Special processing for event nodes dealing with user inputs:
449
453
  const reindexByOriginFn = this.smearEvtsByOrigin(name)
@@ -459,7 +463,17 @@ export class Aggregate extends AggregateBase {
459
463
  this.nodeCount = 0
460
464
 
461
465
  return {
462
- qs: { st: String(getRuntime(this.agentIdentifier).offset) },
466
+ qs: {
467
+ st: this.agentRuntime.offset,
468
+ /** hr === "hasReplay" in NR1, standalone is always checked and processed before harvesting
469
+ * so a race condition between ST and SR states should not be a concern if implemented here */
470
+ hr: Number(!this.isStandalone),
471
+ /** fts === "firstTimestamp" in NR1, indicates what the earliest NODE timestamp was
472
+ * so that blob parsing doesn't need to happen to support UI/API functions */
473
+ fts: this.agentRuntime.offset + earliestTimeStamp,
474
+ /** n === "nodeCount" in NR1, a count of nodes in the ST payload, so that blob parsing doesn't need to happen to support UI/API functions */
475
+ n: stns.length // node count
476
+ },
463
477
  body: { res: stns }
464
478
  }
465
479
  }
@@ -9,7 +9,6 @@ import { shouldCollectEvent } from '../../../common/deny-list/deny-list'
9
9
  import { mapOwn } from '../../../common/util/map-own'
10
10
  import { navTimingValues as navTiming } from '../../../common/timing/nav-timing'
11
11
  import { generateUuid } from '../../../common/ids/unique-id'
12
- import { paintMetrics } from '../../../common/metrics/paint-metrics'
13
12
  import { Interaction } from './interaction'
14
13
  import { getConfigurationValue, getRuntime } from '../../../common/config/config'
15
14
  import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts'
@@ -19,6 +18,8 @@ import { ee } from '../../../common/event-emitter/contextual-ee'
19
18
  import * as CONSTANTS from '../constants'
20
19
  import { FEATURE_NAMES } from '../../../loaders/features/features'
21
20
  import { AggregateBase } from '../../utils/aggregate-base'
21
+ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
22
+ import { firstPaint } from '../../../common/vitals/first-paint'
22
23
 
23
24
  const {
24
25
  FEATURE_NAME, INTERACTION_EVENTS, MAX_TIMER_BUDGET, FN_START, FN_END, CB_START, INTERACTION_API, REMAINING,
@@ -714,8 +715,8 @@ export class Aggregate extends AggregateBase {
714
715
  interaction.root.attrs.id = generateUuid()
715
716
 
716
717
  if (interaction.root.attrs.trigger === 'initialPageLoad') {
717
- interaction.root.attrs.firstPaint = paintMetrics['first-paint']
718
- interaction.root.attrs.firstContentfulPaint = paintMetrics['first-contentful-paint']
718
+ interaction.root.attrs.firstPaint = firstPaint.current.value
719
+ interaction.root.attrs.firstContentfulPaint = firstContentfulPaint.current.value
719
720
  }
720
721
  baseEE.emit('interactionSaved', [interaction])
721
722
  state.interactionsToHarvest.push(interaction)
@@ -1,3 +1,5 @@
1
+ // important side effects
2
+ import './configure/public-path'
1
3
  // loader files
2
4
  import { AgentBase } from './agent-base'
3
5
  import { getEnabledFeatures } from './features/enabled-features'
@@ -124,6 +124,8 @@ export function setAPI (agentIdentifier, forceDrain) {
124
124
 
125
125
  apiInterface.start = (features) => {
126
126
  try {
127
+ const smTag = !features ? 'undefined' : 'defined'
128
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [`API/start/${smTag}/called`], undefined, FEATURE_NAMES.metrics, instanceEE)
127
129
  const featNames = Object.values(FEATURE_NAMES)
128
130
  if (features === undefined) features = featNames
129
131
  else {
@@ -1,5 +1,5 @@
1
1
  import { FEATURE_NAMES } from '../features/features'
2
- import { getConfigurationValue, getInfo, getRuntime } from '../../common/config/config'
2
+ import { getConfiguration, getInfo, 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'
@@ -12,8 +12,6 @@ export function setAPI (agentIdentifier) {
12
12
  var instanceEE = ee.get(agentIdentifier)
13
13
  var cycle = 0
14
14
 
15
- var scheme = (getConfigurationValue(agentIdentifier, 'ssl') === false) ? 'http' : 'https'
16
-
17
15
  var api = {
18
16
  finished: single(finished),
19
17
  setErrorHandler,
@@ -66,7 +64,10 @@ export function setAPI (agentIdentifier) {
66
64
  const agentInfo = getInfo(agentIdentifier)
67
65
  if (!agentInfo.beacon) return
68
66
 
69
- var url = scheme + '://' + agentInfo.beacon + '/1/' + agentInfo.licenseKey
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}`
70
71
 
71
72
  url += '?a=' + agentInfo.applicationID + '&'
72
73
  url += 't=' + requestName + '&'
@@ -8,6 +8,7 @@ import { Instrument as InstrumentXhr } from '../features/ajax/instrument'
8
8
  import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument'
9
9
  import { Instrument as InstrumentSpa } from '../features/spa/instrument'
10
10
  import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
11
+ import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument'
11
12
 
12
13
  /**
13
14
  * An agent class with all feature modules available. Features may be disabled and enabled via runtime configuration.
@@ -25,7 +26,8 @@ export class BrowserAgent extends Agent {
25
26
  InstrumentMetrics,
26
27
  InstrumentPageAction,
27
28
  InstrumentErrors,
28
- InstrumentSpa
29
+ InstrumentSpa,
30
+ InstrumentSessionReplay
29
31
  ],
30
32
  loaderType: 'browser-agent'
31
33
  })
@@ -3,6 +3,9 @@ import { addToNREUM, gosCDN, gosNREUMInitializedAgents } from '../../common/wind
3
3
  import { getConfiguration, setConfiguration, setInfo, setLoaderConfig, setRuntime } from '../../common/config/config'
4
4
  import { activatedFeatures } from '../../common/util/feature-flags'
5
5
  import { isWorkerScope } from '../../common/constants/runtime'
6
+ import { redefinePublicPath } from './public-path'
7
+
8
+ let alreadySetOnce = false // the configure() function can run multiple times in agent lifecycle
6
9
 
7
10
  export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
8
11
  // eslint-disable-next-line camelcase
@@ -26,14 +29,19 @@ export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
26
29
  setInfo(agentIdentifier, info)
27
30
 
28
31
  const updatedInit = getConfiguration(agentIdentifier)
32
+ const internalTrafficList = [info.beacon, info.errorBeacon]
33
+ if (!alreadySetOnce) {
34
+ alreadySetOnce = true
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
37
+ internalTrafficList.push(updatedInit.proxy.assets)
38
+ }
39
+ if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon)
40
+ }
41
+
29
42
  runtime.denyList = [
30
- ...(updatedInit.ajax?.deny_list || []),
31
- ...(updatedInit.ajax?.block_internal
32
- ? [
33
- info.beacon,
34
- info.errorBeacon
35
- ]
36
- : [])
43
+ ...(updatedInit.ajax.deny_list || []),
44
+ ...(updatedInit.ajax.block_internal ? internalTrafficList : [])
37
45
  ]
38
46
  setRuntime(agentIdentifier, runtime)
39
47
 
@@ -0,0 +1,6 @@
1
+ // Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
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
6
+ }
@@ -0,0 +1,4 @@
1
+
2
+ export const redefinePublicPath = () => {
3
+ // We don't support setting public path in webpack via NPM build.
4
+ }
@@ -1,13 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.paintMetrics = void 0;
7
- /*
8
- * Copyright 2020 New Relic Corporation. All rights reserved.
9
- * SPDX-License-Identifier: Apache-2.0
10
- */
11
-
12
- const paintMetrics = {};
13
- exports.paintMetrics = paintMetrics;
@@ -1,75 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.onLongTask = void 0;
7
- var _eol = require("../../common/unload/eol");
8
- /**
9
- * Calls the `onReport` function for every entry reported by the PerformanceLongTaskTiming API.
10
- * The reported value is a `DOMHighResTimeStamp`.
11
- *
12
- * The callback is always called when the page's visibility state changes to hidden.
13
- * As a result, the `onReport` function might be called multiple times during the same page load.
14
- *
15
- * @param {Function} onReport - callback that accepts a `metric` object as the single parameter
16
- */
17
- const onLongTask = onReport => {
18
- const handleEntries = entries => {
19
- entries.forEach(entry => {
20
- const metric = {
21
- name: 'LT',
22
- value: entry.duration,
23
- info: {
24
- // this property deviates from CWV std interface but will hold the custom context to send to NRDB
25
- ltFrame: entry.name,
26
- // MDN: the browsing context or frame that can be attributed to the long task
27
- ltStart: entry.startTime,
28
- // MDN: a double representing the time (millisec) when the task started
29
- ltCtr: entry.attribution[0].containerType // MDN: type of frame container: 'iframe', 'embed', or 'object' ... but this can also be 'window'
30
- }
31
- };
32
-
33
- if (metric.info.ltCtr !== 'window') {
34
- // the following properties are only of relevance & appended for html elements
35
- Object.assign(metric.info, {
36
- ltCtrSrc: entry.attribution[0].containerSrc,
37
- // MDN: container's 'src' attribute
38
- ltCtrId: entry.attribution[0].containerId,
39
- // MDN: container's 'id' attribute
40
- ltCtrName: entry.attribution[0].containerName // MDN: container's 'name' attribute
41
- });
42
- }
43
-
44
- onReport(metric); // report every long task observed unconditionally
45
- });
46
- };
47
-
48
- let observer;
49
- try {
50
- if (PerformanceObserver.supportedEntryTypes.includes('longtask')) {
51
- observer = new PerformanceObserver(list => {
52
- // Delay by a microtask to workaround a bug in Safari where the
53
- // callback is invoked immediately, rather than in a separate task.
54
- // See: https://github.com/GoogleChrome/web-vitals/issues/277
55
- Promise.resolve().then(() => {
56
- handleEntries(list.getEntries());
57
- });
58
- });
59
- observer.observe({
60
- type: 'longtask',
61
- buffered: true
62
- });
63
- }
64
- } catch (e) {
65
- // Do nothing.
66
- }
67
- if (observer) {
68
- (0, _eol.subscribeToEOL)(() => {
69
- handleEntries(observer.takeRecords());
70
- }, true); // this bool is a temp arg under staged BFCache work that runs the func under the new page session logic -- tb removed w/ the feature flag later
71
-
72
- /* No work needed on BFCache restore for long task. */
73
- }
74
- };
75
- exports.onLongTask = onLongTask;
@@ -1,6 +0,0 @@
1
- /*
2
- * Copyright 2020 New Relic Corporation. All rights reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- export const paintMetrics = {};
@@ -1,69 +0,0 @@
1
- import { subscribeToEOL } from '../../common/unload/eol';
2
-
3
- /**
4
- * Calls the `onReport` function for every entry reported by the PerformanceLongTaskTiming API.
5
- * The reported value is a `DOMHighResTimeStamp`.
6
- *
7
- * The callback is always called when the page's visibility state changes to hidden.
8
- * As a result, the `onReport` function might be called multiple times during the same page load.
9
- *
10
- * @param {Function} onReport - callback that accepts a `metric` object as the single parameter
11
- */
12
- export const onLongTask = onReport => {
13
- const handleEntries = entries => {
14
- entries.forEach(entry => {
15
- const metric = {
16
- name: 'LT',
17
- value: entry.duration,
18
- info: {
19
- // this property deviates from CWV std interface but will hold the custom context to send to NRDB
20
- ltFrame: entry.name,
21
- // MDN: the browsing context or frame that can be attributed to the long task
22
- ltStart: entry.startTime,
23
- // MDN: a double representing the time (millisec) when the task started
24
- ltCtr: entry.attribution[0].containerType // MDN: type of frame container: 'iframe', 'embed', or 'object' ... but this can also be 'window'
25
- }
26
- };
27
-
28
- if (metric.info.ltCtr !== 'window') {
29
- // the following properties are only of relevance & appended for html elements
30
- Object.assign(metric.info, {
31
- ltCtrSrc: entry.attribution[0].containerSrc,
32
- // MDN: container's 'src' attribute
33
- ltCtrId: entry.attribution[0].containerId,
34
- // MDN: container's 'id' attribute
35
- ltCtrName: entry.attribution[0].containerName // MDN: container's 'name' attribute
36
- });
37
- }
38
-
39
- onReport(metric); // report every long task observed unconditionally
40
- });
41
- };
42
-
43
- let observer;
44
- try {
45
- if (PerformanceObserver.supportedEntryTypes.includes('longtask')) {
46
- observer = new PerformanceObserver(list => {
47
- // Delay by a microtask to workaround a bug in Safari where the
48
- // callback is invoked immediately, rather than in a separate task.
49
- // See: https://github.com/GoogleChrome/web-vitals/issues/277
50
- Promise.resolve().then(() => {
51
- handleEntries(list.getEntries());
52
- });
53
- });
54
- observer.observe({
55
- type: 'longtask',
56
- buffered: true
57
- });
58
- }
59
- } catch (e) {
60
- // Do nothing.
61
- }
62
- if (observer) {
63
- subscribeToEOL(() => {
64
- handleEntries(observer.takeRecords());
65
- }, true); // this bool is a temp arg under staged BFCache work that runs the func under the new page session logic -- tb removed w/ the feature flag later
66
-
67
- /* No work needed on BFCache restore for long task. */
68
- }
69
- };
@@ -1,2 +0,0 @@
1
- export const paintMetrics: {};
2
- //# sourceMappingURL=paint-metrics.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"paint-metrics.d.ts","sourceRoot":"","sources":["../../../../src/common/metrics/paint-metrics.js"],"names":[],"mappings":"AAKA,8BAA8B"}
@@ -1,2 +0,0 @@
1
- export function onFirstPaint(onReport: Function): void;
2
- //# sourceMappingURL=first-paint.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"first-paint.d.ts","sourceRoot":"","sources":["../../../../src/features/page_view_timing/first-paint.js"],"names":[],"mappings":"AAMO,uDAkCN"}
@@ -1,2 +0,0 @@
1
- export function onLongTask(onReport: Function): void;
2
- //# sourceMappingURL=long-tasks.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"long-tasks.d.ts","sourceRoot":"","sources":["../../../../src/features/page_view_timing/long-tasks.js"],"names":[],"mappings":"AAWO,qDAgDN"}
@@ -1,6 +0,0 @@
1
- /*
2
- * Copyright 2020 New Relic Corporation. All rights reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- export const paintMetrics = {}
@@ -1,60 +0,0 @@
1
- import { subscribeToEOL } from '../../common/unload/eol'
2
-
3
- /**
4
- * Calls the `onReport` function for every entry reported by the PerformanceLongTaskTiming API.
5
- * The reported value is a `DOMHighResTimeStamp`.
6
- *
7
- * The callback is always called when the page's visibility state changes to hidden.
8
- * As a result, the `onReport` function might be called multiple times during the same page load.
9
- *
10
- * @param {Function} onReport - callback that accepts a `metric` object as the single parameter
11
- */
12
- export const onLongTask = (onReport) => {
13
- const handleEntries = (entries) => {
14
- entries.forEach(entry => {
15
- const metric = {
16
- name: 'LT',
17
- value: entry.duration,
18
- info: { // this property deviates from CWV std interface but will hold the custom context to send to NRDB
19
- ltFrame: entry.name, // MDN: the browsing context or frame that can be attributed to the long task
20
- ltStart: entry.startTime, // MDN: a double representing the time (millisec) when the task started
21
- ltCtr: entry.attribution[0].containerType // MDN: type of frame container: 'iframe', 'embed', or 'object' ... but this can also be 'window'
22
- }
23
- }
24
- if (metric.info.ltCtr !== 'window') { // the following properties are only of relevance & appended for html elements
25
- Object.assign(metric.info, {
26
- ltCtrSrc: entry.attribution[0].containerSrc, // MDN: container's 'src' attribute
27
- ltCtrId: entry.attribution[0].containerId, // MDN: container's 'id' attribute
28
- ltCtrName: entry.attribution[0].containerName // MDN: container's 'name' attribute
29
- })
30
- }
31
-
32
- onReport(metric) // report every long task observed unconditionally
33
- })
34
- }
35
-
36
- let observer
37
- try {
38
- if (PerformanceObserver.supportedEntryTypes.includes('longtask')) {
39
- observer = new PerformanceObserver((list) => {
40
- // Delay by a microtask to workaround a bug in Safari where the
41
- // callback is invoked immediately, rather than in a separate task.
42
- // See: https://github.com/GoogleChrome/web-vitals/issues/277
43
- Promise.resolve().then(() => {
44
- handleEntries(list.getEntries())
45
- })
46
- })
47
- observer.observe({ type: 'longtask', buffered: true })
48
- }
49
- } catch (e) {
50
- // Do nothing.
51
- }
52
-
53
- if (observer) {
54
- subscribeToEOL(() => {
55
- handleEntries(observer.takeRecords())
56
- }, true) // this bool is a temp arg under staged BFCache work that runs the func under the new page session logic -- tb removed w/ the feature flag later
57
-
58
- /* No work needed on BFCache restore for long task. */
59
- }
60
- }