@newrelic/browser-agent 1.252.1 → 1.253.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 (203) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/cjs/cdn/experimental.js +6 -2
  4. package/dist/cjs/cdn/spa.js +5 -3
  5. package/dist/cjs/common/aggregate/aggregator.js +1 -8
  6. package/dist/cjs/common/config/state/init.js +7 -0
  7. package/dist/cjs/common/constants/env.cdn.js +1 -1
  8. package/dist/cjs/common/constants/env.npm.js +1 -1
  9. package/dist/cjs/common/context/observation-context-manager.js +56 -0
  10. package/dist/cjs/common/event-emitter/contextual-ee.js +12 -9
  11. package/dist/cjs/common/session/constants.js +2 -1
  12. package/dist/cjs/common/timing/nav-timing.js +8 -3
  13. package/dist/cjs/common/timing/now.js +1 -1
  14. package/dist/cjs/common/util/feature-flags.js +1 -1
  15. package/dist/cjs/common/wrap/index.js +0 -7
  16. package/dist/cjs/common/wrap/wrap-events.js +2 -2
  17. package/dist/cjs/common/wrap/wrap-fetch.js +2 -1
  18. package/dist/cjs/common/wrap/wrap-function.js +5 -7
  19. package/dist/cjs/common/wrap/wrap-promise.js +2 -1
  20. package/dist/cjs/features/ajax/aggregate/index.js +34 -16
  21. package/dist/cjs/features/jserrors/aggregate/index.js +77 -66
  22. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  23. package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +1 -0
  24. package/dist/cjs/features/session_replay/aggregate/index.js +96 -94
  25. package/dist/cjs/features/session_replay/constants.js +5 -1
  26. package/dist/cjs/features/session_replay/instrument/index.js +24 -8
  27. package/dist/cjs/features/session_replay/shared/utils.js +26 -0
  28. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +50 -0
  29. package/dist/cjs/features/soft_navigations/aggregate/bel-node.js +29 -0
  30. package/dist/cjs/features/soft_navigations/aggregate/index.js +263 -0
  31. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +62 -0
  32. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +146 -0
  33. package/dist/cjs/features/soft_navigations/constants.js +31 -0
  34. package/dist/cjs/features/soft_navigations/index.js +12 -0
  35. package/dist/cjs/features/soft_navigations/instrument/index.js +79 -0
  36. package/dist/cjs/features/spa/aggregate/index.js +4 -4
  37. package/dist/cjs/features/utils/agent-session.js +2 -1
  38. package/dist/cjs/features/utils/instrument-base.js +6 -9
  39. package/dist/cjs/features/utils/lazy-feature-loader.js +2 -0
  40. package/dist/cjs/loaders/agent-base.js +18 -3
  41. package/dist/cjs/loaders/agent.js +15 -18
  42. package/dist/cjs/loaders/api/api-methods.js +2 -1
  43. package/dist/cjs/loaders/api/api.js +14 -11
  44. package/dist/cjs/loaders/configure/configure.js +4 -1
  45. package/dist/cjs/loaders/features/enabled-features.js +1 -1
  46. package/dist/cjs/loaders/features/features.js +3 -1
  47. package/dist/esm/cdn/experimental.js +5 -2
  48. package/dist/esm/cdn/spa.js +3 -1
  49. package/dist/esm/common/aggregate/aggregator.js +1 -8
  50. package/dist/esm/common/config/state/init.js +7 -0
  51. package/dist/esm/common/constants/env.cdn.js +1 -1
  52. package/dist/esm/common/constants/env.npm.js +1 -1
  53. package/dist/esm/common/context/observation-context-manager.js +49 -0
  54. package/dist/esm/common/event-emitter/contextual-ee.js +12 -9
  55. package/dist/esm/common/session/constants.js +1 -0
  56. package/dist/esm/common/timing/nav-timing.js +8 -3
  57. package/dist/esm/common/timing/now.js +1 -1
  58. package/dist/esm/common/util/feature-flags.js +1 -1
  59. package/dist/esm/common/wrap/index.js +1 -2
  60. package/dist/esm/common/wrap/wrap-events.js +3 -3
  61. package/dist/esm/common/wrap/wrap-fetch.js +3 -2
  62. package/dist/esm/common/wrap/wrap-function.js +4 -5
  63. package/dist/esm/common/wrap/wrap-promise.js +3 -2
  64. package/dist/esm/features/ajax/aggregate/index.js +36 -18
  65. package/dist/esm/features/jserrors/aggregate/index.js +77 -66
  66. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  67. package/dist/esm/features/page_view_event/aggregate/initialized-features.js +1 -0
  68. package/dist/esm/features/session_replay/aggregate/index.js +97 -95
  69. package/dist/esm/features/session_replay/constants.js +4 -0
  70. package/dist/esm/features/session_replay/instrument/index.js +25 -9
  71. package/dist/esm/features/session_replay/shared/utils.js +17 -0
  72. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +43 -0
  73. package/dist/esm/features/soft_navigations/aggregate/bel-node.js +22 -0
  74. package/dist/esm/features/soft_navigations/aggregate/index.js +256 -0
  75. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +55 -0
  76. package/dist/esm/features/soft_navigations/aggregate/interaction.js +140 -0
  77. package/dist/esm/features/soft_navigations/constants.js +25 -0
  78. package/dist/esm/features/soft_navigations/index.js +1 -0
  79. package/dist/esm/features/soft_navigations/instrument/index.js +73 -0
  80. package/dist/esm/features/spa/aggregate/index.js +4 -4
  81. package/dist/esm/features/utils/agent-session.js +2 -1
  82. package/dist/esm/features/utils/instrument-base.js +7 -10
  83. package/dist/esm/features/utils/lazy-feature-loader.js +2 -0
  84. package/dist/esm/loaders/agent-base.js +18 -3
  85. package/dist/esm/loaders/agent.js +15 -18
  86. package/dist/esm/loaders/api/api-methods.js +2 -1
  87. package/dist/esm/loaders/api/api.js +14 -11
  88. package/dist/esm/loaders/configure/configure.js +4 -1
  89. package/dist/esm/loaders/features/enabled-features.js +1 -1
  90. package/dist/esm/loaders/features/features.js +3 -1
  91. package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
  92. package/dist/types/common/config/state/init.d.ts.map +1 -1
  93. package/dist/types/common/context/event-context.d.ts.map +1 -0
  94. package/dist/types/common/context/observation-context-manager.d.ts +28 -0
  95. package/dist/types/common/context/observation-context-manager.d.ts.map +1 -0
  96. package/dist/types/common/event-emitter/contextual-ee.d.ts +2 -2
  97. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  98. package/dist/types/common/session/constants.d.ts +1 -0
  99. package/dist/types/common/session/constants.d.ts.map +1 -1
  100. package/dist/types/common/timing/nav-timing.d.ts.map +1 -1
  101. package/dist/types/common/wrap/index.d.ts +1 -2
  102. package/dist/types/common/wrap/index.d.ts.map +1 -1
  103. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  104. package/dist/types/common/wrap/wrap-function.d.ts +0 -1
  105. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  106. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  107. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  108. package/dist/types/features/jserrors/aggregate/index.d.ts +4 -3
  109. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  110. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
  111. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  112. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  113. package/dist/types/features/session_replay/constants.d.ts +4 -0
  114. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  115. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  116. package/dist/types/features/session_replay/shared/utils.d.ts +4 -0
  117. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -0
  118. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +19 -0
  119. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -0
  120. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts +16 -0
  121. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts.map +1 -0
  122. package/dist/types/features/soft_navigations/aggregate/index.d.ts +36 -0
  123. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -0
  124. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts +12 -0
  125. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -0
  126. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +50 -0
  127. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -0
  128. package/dist/types/features/soft_navigations/constants.d.ts +20 -0
  129. package/dist/types/features/soft_navigations/constants.d.ts.map +1 -0
  130. package/dist/types/features/soft_navigations/index.d.ts +2 -0
  131. package/dist/types/features/soft_navigations/index.d.ts.map +1 -0
  132. package/dist/types/features/soft_navigations/instrument/index.d.ts +7 -0
  133. package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -0
  134. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  135. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  136. package/dist/types/features/utils/instrument-base.d.ts +1 -7
  137. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  138. package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -1
  139. package/dist/types/loaders/agent-base.d.ts +5 -1
  140. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  141. package/dist/types/loaders/agent.d.ts +2 -2
  142. package/dist/types/loaders/agent.d.ts.map +1 -1
  143. package/dist/types/loaders/api/api-methods.d.ts.map +1 -1
  144. package/dist/types/loaders/api/api.d.ts +3 -5
  145. package/dist/types/loaders/api/api.d.ts.map +1 -1
  146. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  147. package/dist/types/loaders/features/features.d.ts +1 -0
  148. package/dist/types/loaders/features/features.d.ts.map +1 -1
  149. package/dist/types/loaders/micro-agent.d.ts +0 -1
  150. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  151. package/package.json +1 -1
  152. package/src/cdn/experimental.js +4 -2
  153. package/src/cdn/spa.js +3 -1
  154. package/src/common/aggregate/aggregator.js +2 -11
  155. package/src/common/config/state/init.js +3 -1
  156. package/src/common/context/observation-context-manager.js +55 -0
  157. package/src/common/event-emitter/contextual-ee.js +20 -10
  158. package/src/common/session/constants.js +1 -0
  159. package/src/common/timing/nav-timing.js +7 -3
  160. package/src/common/timing/now.js +1 -1
  161. package/src/common/util/feature-flags.js +1 -1
  162. package/src/common/wrap/index.js +1 -2
  163. package/src/common/wrap/wrap-events.js +3 -3
  164. package/src/common/wrap/wrap-fetch.js +3 -2
  165. package/src/common/wrap/wrap-function.js +4 -6
  166. package/src/common/wrap/wrap-promise.js +3 -2
  167. package/src/features/ajax/aggregate/index.js +36 -18
  168. package/src/features/jserrors/aggregate/index.js +70 -73
  169. package/src/features/page_view_event/aggregate/index.js +1 -1
  170. package/src/features/page_view_event/aggregate/initialized-features.js +1 -0
  171. package/src/features/session_replay/aggregate/index.js +92 -95
  172. package/src/features/session_replay/constants.js +5 -0
  173. package/src/features/session_replay/instrument/index.js +24 -9
  174. package/src/features/session_replay/shared/utils.js +19 -0
  175. package/src/features/soft_navigations/aggregate/ajax-node.js +57 -0
  176. package/src/features/soft_navigations/aggregate/bel-node.js +26 -0
  177. package/src/features/soft_navigations/aggregate/index.js +254 -0
  178. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +53 -0
  179. package/src/features/soft_navigations/aggregate/interaction.js +159 -0
  180. package/src/features/soft_navigations/constants.js +29 -0
  181. package/src/features/soft_navigations/index.js +1 -0
  182. package/src/features/soft_navigations/instrument/index.js +67 -0
  183. package/src/features/spa/aggregate/index.js +5 -4
  184. package/src/features/utils/agent-session.js +2 -1
  185. package/src/features/utils/instrument-base.js +7 -10
  186. package/src/features/utils/lazy-feature-loader.js +2 -0
  187. package/src/loaders/agent-base.js +18 -3
  188. package/src/loaders/agent.js +18 -17
  189. package/src/loaders/api/api-methods.js +4 -1
  190. package/src/loaders/api/api.js +14 -12
  191. package/src/loaders/configure/configure.js +4 -1
  192. package/src/loaders/features/enabled-features.js +1 -1
  193. package/src/loaders/features/features.js +3 -1
  194. package/dist/cjs/common/wrap/wrap-raf.js +0 -55
  195. package/dist/esm/common/wrap/wrap-raf.js +0 -48
  196. package/dist/types/common/event-emitter/event-context.d.ts.map +0 -1
  197. package/dist/types/common/wrap/wrap-raf.d.ts +0 -16
  198. package/dist/types/common/wrap/wrap-raf.d.ts.map +0 -1
  199. package/src/common/wrap/wrap-raf.js +0 -52
  200. /package/dist/cjs/common/{event-emitter → context}/event-context.js +0 -0
  201. /package/dist/esm/common/{event-emitter → context}/event-context.js +0 -0
  202. /package/dist/types/common/{event-emitter → context}/event-context.d.ts +0 -0
  203. /package/src/common/{event-emitter → context}/event-context.js +0 -0
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { registerHandler } from '../../../common/event-emitter/register-handler'
14
14
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
15
- import { ABORT_REASONS, FEATURE_NAME, MAX_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES } from '../constants'
15
+ import { ABORT_REASONS, FEATURE_NAME, MAX_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES } from '../constants'
16
16
  import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
17
17
  import { AggregateBase } from '../../utils/aggregate-base'
18
18
  import { sharedChannel } from '../../../common/constants/shared-channel'
@@ -55,102 +55,93 @@ export class Aggregate extends AggregateBase {
55
55
 
56
56
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee)
57
57
 
58
- const shouldSetup = (
59
- getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true &&
60
- getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true
61
- )
62
-
63
- if (shouldSetup) {
64
- // 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.
65
- this.ee.on(SESSION_EVENTS.RESET, () => {
66
- this.scheduler.runHarvest()
67
- this.abort(ABORT_REASONS.RESET)
68
- })
69
-
70
- // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
71
- this.ee.on(SESSION_EVENTS.PAUSE, () => { this.recorder?.stopRecording() })
72
- // 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.
73
- this.ee.on(SESSION_EVENTS.RESUME, () => {
74
- if (!this.recorder) return
75
- // if the mode changed on a different tab, it needs to update this instance to match
76
- const { session } = getRuntime(this.agentIdentifier)
77
- this.mode = session.state.sessionReplayMode
78
- if (!this.initialized || this.mode === MODE.OFF) return
79
- this.recorder?.startRecording()
80
- })
58
+ // 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.
59
+ this.ee.on(SESSION_EVENTS.RESET, () => {
60
+ this.scheduler.runHarvest()
61
+ this.abort(ABORT_REASONS.RESET)
62
+ })
63
+
64
+ // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
65
+ this.ee.on(SESSION_EVENTS.PAUSE, () => { this.recorder?.stopRecording() })
66
+ // 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.
67
+ this.ee.on(SESSION_EVENTS.RESUME, () => {
68
+ if (!this.recorder) return
69
+ // if the mode changed on a different tab, it needs to update this instance to match
70
+ const { session } = getRuntime(this.agentIdentifier)
71
+ this.mode = session.state.sessionReplayMode
72
+ if (!this.initialized || this.mode === MODE.OFF) return
73
+ this.recorder?.startRecording()
74
+ })
75
+
76
+ this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
77
+ if (!this.recorder || !this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return
78
+ if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB)
79
+ this.mode = data.sessionReplay
80
+ })
81
+
82
+ // Bespoke logic for blobs endpoint.
83
+ this.scheduler = new HarvestScheduler('browser/blobs', {
84
+ onFinished: this.onHarvestFinished.bind(this),
85
+ retryDelay: this.harvestTimeSeconds,
86
+ getPayload: this.prepareHarvest.bind(this),
87
+ raw: true
88
+ }, this)
89
+
90
+ registerHandler(SR_EVENT_EMITTER_TYPES.RECORD, () => {
91
+ // if it has aborted or BCS returned bad entitlements, do not allow
92
+ if (this.blocked || !this.entitled) return
93
+ // if it isnt already (fully) initialized... initialize it
94
+ if (!this.recorder) this.initializeRecording(false, true, true)
95
+ // its been initialized and imported the recorder but its not recording (mode === off || error)
96
+ else if (this.mode !== MODE.FULL) this.switchToFull()
97
+ // if it gets all the way to here, that means a full session is already recording... do nothing
98
+ }, this.featureName, this.ee)
99
+
100
+ registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
101
+ this.forceStop(this.mode !== MODE.ERROR)
102
+ }, this.featureName, this.ee)
103
+
104
+ // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
105
+ // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
106
+ registerHandler('errorAgg', (e) => {
107
+ this.errorNoticed = true
108
+ if (this.recorder) this.recorder.currentBufferTarget.hasError = true
109
+ // run once
110
+ if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
111
+ this.switchToFull()
112
+ }
113
+ }, this.featureName, this.ee)
81
114
 
82
- this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
83
- if (!this.recorder || !this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return
84
- if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB)
85
- this.mode = data.sessionReplay
86
- })
115
+ const { error_sampling_rate, sampling_rate, autoStart, block_selector, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')
87
116
 
88
- // Bespoke logic for blobs endpoint.
89
- this.scheduler = new HarvestScheduler('browser/blobs', {
90
- onFinished: this.onHarvestFinished.bind(this),
91
- retryDelay: this.harvestTimeSeconds,
92
- getPayload: this.prepareHarvest.bind(this),
93
- raw: true
94
- }, this)
95
-
96
- if (this.recorder?.getEvents().type === 'preloaded') {
97
- this.prepUtils().then(() => {
98
- this.scheduler.runHarvest()
99
- })
117
+ this.waitForFlags(['sr']).then(([flagOn]) => {
118
+ this.entitled = flagOn
119
+ if (!this.entitled && this.recorder?.recording) {
120
+ this.abort(ABORT_REASONS.ENTITLEMENTS)
121
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee)
122
+ return
100
123
  }
101
-
102
- registerHandler('recordReplay', () => {
103
- // if it has aborted or BCS returned bad entitlements, do not allow
104
- if (this.blocked || !this.entitled) return
105
- // if it isnt already (fully) initialized... initialize it
106
- if (!this.recorder) this.initializeRecording(false, true, true)
107
- // its been initialized and imported the recorder but its not recording (mode === off || error)
108
- else if (this.mode !== MODE.FULL) this.switchToFull()
109
- // if it gets all the way to here, that means a full session is already recording... do nothing
110
- }, this.featureName, this.ee)
111
-
112
- registerHandler('pauseReplay', () => {
113
- this.forceStop(this.mode !== MODE.ERROR)
114
- }, this.featureName, this.ee)
115
-
116
- // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
117
- // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
118
- registerHandler('errorAgg', (e) => {
119
- this.errorNoticed = true
120
- if (this.recorder) this.recorder.currentBufferTarget.hasError = true
121
- // run once
122
- if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
123
- this.switchToFull()
124
- }
125
- }, this.featureName, this.ee)
126
-
127
- const { error_sampling_rate, sampling_rate, autoStart, block_selector, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')
128
-
129
- this.waitForFlags(['sr']).then(([flagOn]) => {
130
- this.entitled = flagOn
131
- if (!this.entitled && this.recorder?.recording) {
132
- this.recorder.abort(ABORT_REASONS.ENTITLEMENTS)
133
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee)
134
- }
135
- this.initializeRecording(
136
- (Math.random() * 100) < error_sampling_rate,
137
- (Math.random() * 100) < sampling_rate
138
- )
139
- }).then(() => sharedChannel.onReplayReady(this.mode)) // notify watchers that replay started with the mode
140
-
141
- /** Detect if the default configs have been altered and report a SM. This is useful to evaluate what the reasonable defaults are across a customer base over time */
142
- if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
143
- if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
144
- if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
145
- if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee)
146
- if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
147
- if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
148
- if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
149
-
150
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee)
151
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee)
152
- this.drain()
153
- }
124
+ this.initializeRecording(
125
+ (Math.random() * 100) < error_sampling_rate,
126
+ (Math.random() * 100) < sampling_rate
127
+ )
128
+ }).then(() => {
129
+ if (this.mode === MODE.OFF) args?.recorder?.stopRecording() // stop any conservative preload recording launched by instrument
130
+ sharedChannel.onReplayReady(this.mode) // notify watchers that replay started with the mode
131
+ })
132
+
133
+ /** Detect if the default configs have been altered and report a SM. This is useful to evaluate what the reasonable defaults are across a customer base over time */
134
+ if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
135
+ if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
136
+ if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
137
+ if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee)
138
+ if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
139
+ if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
140
+ if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
141
+
142
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee)
143
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee)
144
+ this.drain()
154
145
  }
155
146
 
156
147
  switchToFull () {
@@ -197,6 +188,12 @@ export class Aggregate extends AggregateBase {
197
188
  }
198
189
  }
199
190
 
191
+ if (this.recorder?.getEvents().type === 'preloaded') {
192
+ this.prepUtils().then(() => {
193
+ this.scheduler.runHarvest()
194
+ })
195
+ }
196
+
200
197
  if (!this.recorder) {
201
198
  try {
202
199
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
@@ -374,7 +371,7 @@ export class Aggregate extends AggregateBase {
374
371
  this.syncWithSessionManager({ sessionReplayMode: this.mode })
375
372
  this.recorder?.clearTimestamps?.()
376
373
  this.ee.emit('REPLAY_ABORTED')
377
- this.recorder?.clearBuffer?.()
374
+ while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.()
378
375
  }
379
376
 
380
377
  syncWithSessionManager (state = {}) {
@@ -3,6 +3,11 @@ import { FEATURE_NAMES } from '../../loaders/features/features'
3
3
 
4
4
  export const FEATURE_NAME = FEATURE_NAMES.sessionReplay
5
5
 
6
+ export const SR_EVENT_EMITTER_TYPES = {
7
+ RECORD: 'recordReplay',
8
+ PAUSE: 'pauseReplay'
9
+ }
10
+
6
11
  export const AVG_COMPRESSION = 0.12
7
12
  export const RRWEB_EVENT_TYPES = {
8
13
  DomContentLoaded: 0,
@@ -9,23 +9,37 @@
9
9
  * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
10
10
  * functionality is validated and a full user experience is curated.
11
11
  */
12
- import { MODE } from '../../../common/session/constants'
12
+ import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants'
13
13
  import { InstrumentBase } from '../../utils/instrument-base'
14
14
  import { FEATURE_NAME } from '../constants'
15
+ import { isPreloadAllowed } from '../shared/utils'
15
16
 
16
17
  export class Instrument extends InstrumentBase {
17
18
  static featureName = FEATURE_NAME
18
19
  constructor (agentIdentifier, aggregator, auto = true) {
19
20
  super(agentIdentifier, aggregator, FEATURE_NAME, auto)
21
+ let session
20
22
  try {
21
- const session = JSON.parse(localStorage.getItem('NRBA_SESSION'))
22
- if (session.sessionReplayMode !== MODE.OFF) {
23
- this.#startRecording(session.sessionReplayMode)
24
- } else {
25
- this.importAggregator({})
26
- }
27
- } catch (err) {
28
- this.importAggregator({})
23
+ session = JSON.parse(localStorage.getItem(`${PREFIX}_${DEFAULT_KEY}`))
24
+ } catch (err) { }
25
+
26
+ if (this.#canPreloadRecorder(session)) {
27
+ this.#startRecording(session?.sessionReplayMode)
28
+ } else {
29
+ this.importAggregator()
30
+ }
31
+ }
32
+
33
+ // At this point wherein session state exists already but we haven't init SessionEntity aka verify timers.
34
+ #canPreloadRecorder (session) {
35
+ if (!session) { // this might be a new session if entity initializes: conservatively start recording if first-time config allows
36
+ // Note: users with SR enabled, as well as these other configs enabled by-default, will be penalized by the recorder overhead EVEN IF they don't actually have or get
37
+ // entitlement or sampling decision, or otherwise intentionally opted-in for the feature.
38
+ return isPreloadAllowed(this.agentIdentifier)
39
+ } else if (session.sessionReplayMode === MODE.FULL || session.sessionReplayMode === MODE.ERROR) {
40
+ return true // existing sessions get to continue recording, regardless of this page's configs or if it has expired (conservatively)
41
+ } else { // SR mode was OFF but may potentially be turned on if session resets and configs allows the new session to have replay...
42
+ return isPreloadAllowed(this.agentIdentifier)
29
43
  }
30
44
  }
31
45
 
@@ -33,6 +47,7 @@ export class Instrument extends InstrumentBase {
33
47
  const { Recorder } = (await import(/* webpackChunkName: "recorder" */'../shared/recorder'))
34
48
  this.recorder = new Recorder({ mode, agentIdentifier: this.agentIdentifier })
35
49
  this.recorder.startRecording()
50
+ this.abortHandler = this.recorder.stopRecording
36
51
  this.importAggregator({ recorder: this.recorder })
37
52
  }
38
53
  }
@@ -0,0 +1,19 @@
1
+ import { getConfigurationValue, originals } from '../../../common/config/config'
2
+ import { isBrowserScope } from '../../../common/constants/runtime'
3
+
4
+ export const enableSessionTracking = (agentId) => isBrowserScope && getConfigurationValue(agentId, 'privacy.cookies_enabled') === true
5
+
6
+ function hasReplayPrerequisite (agentId) {
7
+ return originals.MO && // Session Replay cannot work without Mutation Observer
8
+ enableSessionTracking && // requires session tracking to be running (hence "session" replay...)
9
+ getConfigurationValue(agentId, 'session_trace.enabled') === true // Session Replay as of now is tightly coupled with Session Trace in the UI
10
+ }
11
+
12
+ export function isPreloadAllowed (agentId) {
13
+ return getConfigurationValue(agentId, 'session_replay.preload') === true && hasReplayPrerequisite(agentId)
14
+ }
15
+
16
+ export function canImportReplayAgg (agentId, sessionMgr) {
17
+ if (!hasReplayPrerequisite(agentId)) return false
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
+ }
@@ -0,0 +1,57 @@
1
+ import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer'
2
+ import { NODE_TYPE } from '../constants'
3
+ import { BelNode } from './bel-node'
4
+
5
+ export class AjaxNode extends BelNode {
6
+ constructor (agentIdentifier, ajaxEvent) {
7
+ super(agentIdentifier)
8
+ this.belType = NODE_TYPE.AJAX
9
+ this.method = ajaxEvent.method
10
+ this.status = ajaxEvent.status
11
+ this.domain = ajaxEvent.domain
12
+ this.path = ajaxEvent.path
13
+ this.txSize = ajaxEvent.requestSize
14
+ this.rxSize = ajaxEvent.responseSize
15
+ this.requestedWith = ajaxEvent.type === 'fetch' ? 1 : '' // 'xhr' and 'beacon' types get the empty string
16
+ this.spanId = ajaxEvent.spanId
17
+ this.traceId = ajaxEvent.traceId
18
+ this.spanTimestamp = ajaxEvent.spanTimestamp
19
+ this.gql = ajaxEvent.gql
20
+
21
+ this.start = ajaxEvent.startTime
22
+ this.end = ajaxEvent.endTime
23
+ }
24
+
25
+ serialize (parentStartTimestamp) {
26
+ const addString = getAddStringContext(this.agentIdentifier)
27
+ const nodeList = []
28
+
29
+ // IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
30
+ const fields = [
31
+ numeric(this.belType),
32
+ 0, // this will be overwritten below with number of attached nodes
33
+ numeric(this.start - parentStartTimestamp), // start relative to parent start (if part of first node in payload) or first parent start
34
+ numeric(this.end - this.start), // end is relative to start
35
+ numeric(this.callbackEnd),
36
+ numeric(this.callbackDuration),
37
+ addString(this.method),
38
+ numeric(this.status),
39
+ addString(this.domain),
40
+ addString(this.path),
41
+ numeric(this.txSize),
42
+ numeric(this.rxSize),
43
+ this.requestedWith,
44
+ addString(this.nodeId),
45
+ nullable(this.spanId, addString, true) + nullable(this.traceId, addString, true) + nullable(this.spanTimestamp, numeric)
46
+ ]
47
+ let allAttachedNodes = []
48
+ if (typeof this.gql === 'object') allAttachedNodes = addCustomAttributes(this.gql, addString)
49
+ this.children.forEach(node => allAttachedNodes.push(node.serialize())) // no children is expected under ajax nodes at this time
50
+
51
+ fields[1] = numeric(allAttachedNodes.length)
52
+ nodeList.push(fields)
53
+ if (allAttachedNodes.length) nodeList.push(allAttachedNodes.join(';'))
54
+
55
+ return nodeList.join(';')
56
+ }
57
+ }
@@ -0,0 +1,26 @@
1
+ import { now } from '../../../common/timing/now'
2
+
3
+ let nodesSeen = 0
4
+
5
+ export class BelNode {
6
+ belType
7
+ /** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
8
+ children = []
9
+ start = now()
10
+ end
11
+ callbackEnd = 0
12
+ callbackDuration = 0
13
+ nodeId = ++nodesSeen
14
+
15
+ constructor (agentIdentifier) {
16
+ if (!agentIdentifier) throw new Error('Interaction is missing core attributes')
17
+ this.agentIdentifier = agentIdentifier
18
+ }
19
+
20
+ addChild (child) {
21
+ this.children.push(child)
22
+ }
23
+
24
+ /** Virtual fn for stringifying an instance. */
25
+ serialize () {}
26
+ }