@newrelic/browser-agent 1.255.0 → 1.256.1

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 (66) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cjs/common/constants/env.cdn.js +2 -2
  3. package/dist/cjs/common/constants/env.npm.js +2 -2
  4. package/dist/cjs/common/constants/runtime.js +1 -1
  5. package/dist/cjs/common/session/session-entity.js +2 -1
  6. package/dist/cjs/common/timer/interaction-timer.js +16 -2
  7. package/dist/cjs/common/timing/time-keeper.js +1 -2
  8. package/dist/cjs/features/jserrors/aggregate/index.js +16 -6
  9. package/dist/cjs/features/jserrors/instrument/index.js +8 -3
  10. package/dist/cjs/features/session_replay/aggregate/index.js +48 -29
  11. package/dist/cjs/features/session_replay/constants.js +2 -1
  12. package/dist/cjs/features/session_replay/instrument/index.js +9 -2
  13. package/dist/cjs/features/session_replay/shared/recorder-events.js +1 -9
  14. package/dist/cjs/features/session_replay/shared/recorder.js +22 -50
  15. package/dist/cjs/features/session_replay/shared/utils.js +12 -0
  16. package/dist/cjs/loaders/api/api.js +7 -1
  17. package/dist/esm/common/constants/env.cdn.js +2 -2
  18. package/dist/esm/common/constants/env.npm.js +2 -2
  19. package/dist/esm/common/constants/runtime.js +1 -1
  20. package/dist/esm/common/session/session-entity.js +2 -1
  21. package/dist/esm/common/timer/interaction-timer.js +16 -2
  22. package/dist/esm/common/timing/time-keeper.js +1 -3
  23. package/dist/esm/features/jserrors/aggregate/index.js +16 -6
  24. package/dist/esm/features/jserrors/instrument/index.js +8 -3
  25. package/dist/esm/features/session_replay/aggregate/index.js +48 -29
  26. package/dist/esm/features/session_replay/constants.js +2 -1
  27. package/dist/esm/features/session_replay/instrument/index.js +9 -2
  28. package/dist/esm/features/session_replay/shared/recorder-events.js +1 -9
  29. package/dist/esm/features/session_replay/shared/recorder.js +23 -51
  30. package/dist/esm/features/session_replay/shared/utils.js +11 -0
  31. package/dist/esm/loaders/api/api.js +7 -1
  32. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  33. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  34. package/dist/types/common/timer/interaction-timer.d.ts +2 -0
  35. package/dist/types/common/timer/interaction-timer.d.ts.map +1 -1
  36. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  37. package/dist/types/features/jserrors/aggregate/index.d.ts +2 -1
  38. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  39. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  40. package/dist/types/features/session_replay/aggregate/index.d.ts +5 -2
  41. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  42. package/dist/types/features/session_replay/constants.d.ts +1 -0
  43. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  44. package/dist/types/features/session_replay/instrument/index.d.ts +1 -0
  45. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  46. package/dist/types/features/session_replay/shared/recorder-events.d.ts +0 -8
  47. package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
  48. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -17
  49. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  50. package/dist/types/features/session_replay/shared/utils.d.ts +8 -0
  51. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
  52. package/dist/types/loaders/api/api.d.ts.map +1 -1
  53. package/package.json +2 -2
  54. package/src/common/constants/runtime.js +1 -1
  55. package/src/common/session/session-entity.js +2 -1
  56. package/src/common/timer/interaction-timer.js +17 -2
  57. package/src/common/timing/time-keeper.js +1 -3
  58. package/src/features/jserrors/aggregate/index.js +15 -6
  59. package/src/features/jserrors/instrument/index.js +9 -4
  60. package/src/features/session_replay/aggregate/index.js +43 -25
  61. package/src/features/session_replay/constants.js +2 -1
  62. package/src/features/session_replay/instrument/index.js +7 -2
  63. package/src/features/session_replay/shared/recorder-events.js +1 -6
  64. package/src/features/session_replay/shared/recorder.js +21 -27
  65. package/src/features/session_replay/shared/utils.js +12 -0
  66. package/src/loaders/api/api.js +10 -1
@@ -1,6 +1,6 @@
1
1
  import { record as recorder } from 'rrweb'
2
2
  import { stringify } from '../../../common/util/stringify'
3
- import { AVG_COMPRESSION, CHECKOUT_MS, IDEAL_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES } from '../constants'
3
+ import { AVG_COMPRESSION, CHECKOUT_MS, IDEAL_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES } from '../constants'
4
4
  import { getConfigurationValue } from '../../../common/config/config'
5
5
  import { RecorderEvents } from './recorder-events'
6
6
  import { MODE } from '../../../common/session/constants'
@@ -8,6 +8,7 @@ import { stylesheetEvaluator } from './stylesheet-evaluator'
8
8
  import { handle } from '../../../common/event-emitter/handle'
9
9
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
10
10
  import { FEATURE_NAMES } from '../../../loaders/features/features'
11
+ import { buildNRMetaNode } from './utils'
11
12
 
12
13
  export class Recorder {
13
14
  /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
@@ -20,9 +21,9 @@ export class Recorder {
20
21
  #fixing = false
21
22
 
22
23
  constructor (parent) {
23
- this.#events = new RecorderEvents({ canCorrectTimestamps: !!parent.timeKeeper?.ready })
24
- this.#backloggedEvents = new RecorderEvents({ canCorrectTimestamps: !!parent.timeKeeper?.ready })
25
- this.#preloaded = [new RecorderEvents({ canCorrectTimestamps: !!parent.timeKeeper?.ready })]
24
+ this.#events = new RecorderEvents()
25
+ this.#backloggedEvents = new RecorderEvents()
26
+ this.#preloaded = [new RecorderEvents()]
26
27
  /** True when actively recording, false when paused or stopped */
27
28
  this.recording = false
28
29
  /** The pointer to the current bucket holding rrweb events */
@@ -40,14 +41,9 @@ export class Recorder {
40
41
  }
41
42
 
42
43
  getEvents () {
43
- if (this.#preloaded[0]?.events.length) {
44
- const preloadedEvents = this.returnCorrectTimestamps(this.#preloaded[0])
45
- return { ...this.#preloaded[0], events: preloadedEvents, type: 'preloaded' }
46
- }
47
- const backloggedEvents = this.returnCorrectTimestamps(this.#backloggedEvents)
48
- const events = this.returnCorrectTimestamps(this.#events)
44
+ if (this.#preloaded[0]?.events.length) return { ...this.#preloaded[0], type: 'preloaded' }
49
45
  return {
50
- events: [...backloggedEvents, ...events].filter(x => x),
46
+ events: [...this.#backloggedEvents.events, ...this.#events.events].filter(x => x),
51
47
  type: 'standard',
52
48
  cycleTimestamp: Math.min(this.#backloggedEvents.cycleTimestamp, this.#events.cycleTimestamp),
53
49
  payloadBytesEstimation: this.#backloggedEvents.payloadBytesEstimation + this.#events.payloadBytesEstimation,
@@ -58,30 +54,22 @@ export class Recorder {
58
54
  }
59
55
  }
60
56
 
61
- /**
62
- * Returns time-corrected events. If the events were correctable from the beginning, this correction will have already been applied.
63
- * @param {SessionReplayEvent[]} events The array of buffered SR nodes
64
- * @returns {CorrectedSessionReplayEvent[]}
65
- */
66
- returnCorrectTimestamps (events) {
67
- if (!this.parent.timeKeeper?.ready) return events.events
68
- return events.canCorrectTimestamps
69
- ? events.events
70
- : events.events.map(({ __serialized, timestamp, ...e }) => ({ timestamp: this.parent.timeKeeper.correctAbsoluteTimestamp(timestamp), ...e }))
71
- }
72
-
73
57
  /** Clears the buffer (this.#events), and resets all payload metadata properties */
74
58
  clearBuffer () {
75
59
  if (this.#preloaded[0]?.events.length) this.#preloaded.shift()
76
60
  else if (this.parent.mode === MODE.ERROR) this.#backloggedEvents = this.#events
77
- else this.#backloggedEvents = new RecorderEvents({ canCorrectTimestamps: !!this.parent.timeKeeper?.ready })
78
- this.#events = new RecorderEvents({ canCorrectTimestamps: !!this.parent.timeKeeper?.ready })
61
+ else this.#backloggedEvents = new RecorderEvents()
62
+ this.#events = new RecorderEvents()
79
63
  }
80
64
 
81
65
  /** Begin recording using configured recording lib */
82
66
  startRecording () {
83
67
  this.recording = true
84
68
  const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.parent.agentIdentifier, 'session_replay')
69
+ const customMasker = (text, element) => {
70
+ if (element?.type?.toLowerCase() !== 'password' && (element?.dataset.nrUnmask !== undefined || element?.classList.contains('nr-unmask'))) return text
71
+ return '*'.repeat(text.length)
72
+ }
85
73
  // set up rrweb configurations for maximum privacy --
86
74
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
87
75
  const stop = recorder({
@@ -92,15 +80,20 @@ export class Recorder {
92
80
  blockSelector: block_selector,
93
81
  maskInputOptions: mask_input_options,
94
82
  maskTextSelector: mask_text_selector,
83
+ maskTextFn: customMasker,
95
84
  maskAllInputs: mask_all_inputs,
85
+ maskInputFn: customMasker,
96
86
  inlineStylesheet: inline_stylesheet,
97
87
  inlineImages: inline_images,
98
88
  collectFonts: collect_fonts,
99
89
  checkoutEveryNms: CHECKOUT_MS[this.parent.mode]
100
90
  })
101
91
 
92
+ this.parent.ee.emit(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [true, this.parent.mode])
93
+
102
94
  this.stopRecording = () => {
103
95
  this.recording = false
96
+ this.parent.ee.emit(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode])
104
97
  stop()
105
98
  }
106
99
  }
@@ -148,7 +141,8 @@ export class Recorder {
148
141
 
149
142
  if (this.parent.blocked) return
150
143
 
151
- if (this.currentBufferTarget.canCorrectTimestamps) {
144
+ if (this.parent.timeKeeper?.ready && !event.__newrelic) {
145
+ event.__newrelic = buildNRMetaNode(event.timestamp, this.parent.timeKeeper)
152
146
  event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp)
153
147
  }
154
148
  event.__serialized = stringify(event)
@@ -184,7 +178,7 @@ export class Recorder {
184
178
  this.parent.scheduler.runHarvest()
185
179
  } else {
186
180
  // we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
187
- this.#preloaded.push(new RecorderEvents({ canCorrectTimestamps: !!this.parent.timeKeeper?.ready }))
181
+ this.#preloaded.push(new RecorderEvents())
188
182
  }
189
183
  }
190
184
  }
@@ -17,3 +17,15 @@ export function canImportReplayAgg (agentId, sessionMgr) {
17
17
  if (!hasReplayPrerequisite(agentId)) return false
18
18
  return !!sessionMgr?.isNew || !!sessionMgr?.state.sessionReplayMode // Session Replay should only try to run if already running from a previous page, or at the beginning of a session
19
19
  }
20
+
21
+ export function buildNRMetaNode (timestamp, timeKeeper) {
22
+ const correctedTimestamp = timeKeeper.correctAbsoluteTimestamp(timestamp)
23
+ return {
24
+ originalTimestamp: timestamp,
25
+ correctedTimestamp,
26
+ timestampDiff: timestamp - correctedTimestamp,
27
+ timeKeeperOriginTime: timeKeeper.originTime,
28
+ timeKeeperCorrectedOriginTime: timeKeeper.correctedOriginTime,
29
+ timeKeeperDiff: Math.floor(timeKeeper.originTime - timeKeeper.correctedOriginTime)
30
+ }
31
+ }
@@ -15,6 +15,7 @@ import { gosCDN } from '../../common/window/nreum'
15
15
  import { apiMethods, asyncApiMethods } from './api-methods'
16
16
  import { SR_EVENT_EMITTER_TYPES } from '../../features/session_replay/constants'
17
17
  import { now } from '../../common/timing/now'
18
+ import { MODE } from '../../common/session/constants'
18
19
 
19
20
  export function setTopLevelCallers () {
20
21
  const nr = gosCDN()
@@ -33,12 +34,20 @@ export function setTopLevelCallers () {
33
34
  }
34
35
  }
35
36
 
37
+ const replayRunning = {}
38
+
36
39
  export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false) {
37
40
  if (!forceDrain) registerDrain(agentIdentifier, 'api')
38
41
  const apiInterface = {}
39
42
  var instanceEE = ee.get(agentIdentifier)
40
43
  var tracerEE = instanceEE.get('tracer')
41
44
 
45
+ replayRunning[agentIdentifier] = MODE.OFF
46
+
47
+ instanceEE.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, (isRunning) => {
48
+ replayRunning[agentIdentifier] = isRunning
49
+ })
50
+
42
51
  var prefix = 'api-'
43
52
  var spaPrefix = prefix + 'ixn-'
44
53
 
@@ -184,7 +193,7 @@ export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false)
184
193
  apiInterface.noticeError = function (err, customAttributes) {
185
194
  if (typeof err === 'string') err = new Error(err)
186
195
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/noticeError/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
187
- handle('err', [err, now(), false, customAttributes], undefined, FEATURE_NAMES.jserrors, instanceEE)
196
+ handle('err', [err, now(), false, customAttributes, !!replayRunning[agentIdentifier]], undefined, FEATURE_NAMES.jserrors, instanceEE)
188
197
  }
189
198
 
190
199
  // theres no window.load event on non-browser scopes, lazy load immediately