@newrelic/browser-agent 1.252.0 → 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 (217) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +6 -6
  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/session/session-entity.js +3 -1
  13. package/dist/cjs/common/timing/nav-timing.js +8 -3
  14. package/dist/cjs/common/timing/now.js +1 -1
  15. package/dist/cjs/common/util/feature-flags.js +1 -1
  16. package/dist/cjs/common/wrap/index.js +0 -7
  17. package/dist/cjs/common/wrap/wrap-events.js +2 -2
  18. package/dist/cjs/common/wrap/wrap-fetch.js +2 -1
  19. package/dist/cjs/common/wrap/wrap-function.js +5 -7
  20. package/dist/cjs/common/wrap/wrap-promise.js +2 -1
  21. package/dist/cjs/features/ajax/aggregate/index.js +34 -16
  22. package/dist/cjs/features/jserrors/aggregate/index.js +77 -66
  23. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  24. package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +1 -0
  25. package/dist/cjs/features/session_replay/aggregate/index.js +96 -94
  26. package/dist/cjs/features/session_replay/constants.js +5 -1
  27. package/dist/cjs/features/session_replay/instrument/index.js +24 -8
  28. package/dist/cjs/features/session_replay/shared/recorder.js +5 -4
  29. package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +8 -7
  30. package/dist/cjs/features/session_replay/shared/utils.js +26 -0
  31. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +50 -0
  32. package/dist/cjs/features/soft_navigations/aggregate/bel-node.js +29 -0
  33. package/dist/cjs/features/soft_navigations/aggregate/index.js +263 -0
  34. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +62 -0
  35. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +146 -0
  36. package/dist/cjs/features/soft_navigations/constants.js +31 -0
  37. package/dist/cjs/features/soft_navigations/index.js +12 -0
  38. package/dist/cjs/features/soft_navigations/instrument/index.js +79 -0
  39. package/dist/cjs/features/spa/aggregate/index.js +4 -4
  40. package/dist/cjs/features/utils/agent-session.js +2 -1
  41. package/dist/cjs/features/utils/instrument-base.js +6 -9
  42. package/dist/cjs/features/utils/lazy-feature-loader.js +2 -0
  43. package/dist/cjs/loaders/agent-base.js +18 -3
  44. package/dist/cjs/loaders/agent.js +15 -18
  45. package/dist/cjs/loaders/api/api-methods.js +9 -0
  46. package/dist/cjs/loaders/api/api.js +17 -18
  47. package/dist/cjs/loaders/configure/configure.js +5 -2
  48. package/dist/cjs/loaders/features/enabled-features.js +1 -1
  49. package/dist/cjs/loaders/features/features.js +3 -1
  50. package/dist/esm/cdn/experimental.js +5 -2
  51. package/dist/esm/cdn/spa.js +3 -1
  52. package/dist/esm/common/aggregate/aggregator.js +1 -8
  53. package/dist/esm/common/config/state/init.js +7 -0
  54. package/dist/esm/common/constants/env.cdn.js +1 -1
  55. package/dist/esm/common/constants/env.npm.js +1 -1
  56. package/dist/esm/common/context/observation-context-manager.js +49 -0
  57. package/dist/esm/common/event-emitter/contextual-ee.js +12 -9
  58. package/dist/esm/common/session/constants.js +1 -0
  59. package/dist/esm/common/session/session-entity.js +3 -1
  60. package/dist/esm/common/timing/nav-timing.js +8 -3
  61. package/dist/esm/common/timing/now.js +1 -1
  62. package/dist/esm/common/util/feature-flags.js +1 -1
  63. package/dist/esm/common/wrap/index.js +1 -2
  64. package/dist/esm/common/wrap/wrap-events.js +3 -3
  65. package/dist/esm/common/wrap/wrap-fetch.js +3 -2
  66. package/dist/esm/common/wrap/wrap-function.js +4 -5
  67. package/dist/esm/common/wrap/wrap-promise.js +3 -2
  68. package/dist/esm/features/ajax/aggregate/index.js +36 -18
  69. package/dist/esm/features/jserrors/aggregate/index.js +77 -66
  70. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  71. package/dist/esm/features/page_view_event/aggregate/initialized-features.js +1 -0
  72. package/dist/esm/features/session_replay/aggregate/index.js +97 -95
  73. package/dist/esm/features/session_replay/constants.js +4 -0
  74. package/dist/esm/features/session_replay/instrument/index.js +25 -9
  75. package/dist/esm/features/session_replay/shared/recorder.js +5 -4
  76. package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +8 -7
  77. package/dist/esm/features/session_replay/shared/utils.js +17 -0
  78. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +43 -0
  79. package/dist/esm/features/soft_navigations/aggregate/bel-node.js +22 -0
  80. package/dist/esm/features/soft_navigations/aggregate/index.js +256 -0
  81. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +55 -0
  82. package/dist/esm/features/soft_navigations/aggregate/interaction.js +140 -0
  83. package/dist/esm/features/soft_navigations/constants.js +25 -0
  84. package/dist/esm/features/soft_navigations/index.js +1 -0
  85. package/dist/esm/features/soft_navigations/instrument/index.js +73 -0
  86. package/dist/esm/features/spa/aggregate/index.js +4 -4
  87. package/dist/esm/features/utils/agent-session.js +2 -1
  88. package/dist/esm/features/utils/instrument-base.js +7 -10
  89. package/dist/esm/features/utils/lazy-feature-loader.js +2 -0
  90. package/dist/esm/loaders/agent-base.js +18 -3
  91. package/dist/esm/loaders/agent.js +15 -18
  92. package/dist/esm/loaders/api/api-methods.js +3 -0
  93. package/dist/esm/loaders/api/api.js +17 -17
  94. package/dist/esm/loaders/configure/configure.js +5 -2
  95. package/dist/esm/loaders/features/enabled-features.js +1 -1
  96. package/dist/esm/loaders/features/features.js +3 -1
  97. package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
  98. package/dist/types/common/config/state/init.d.ts.map +1 -1
  99. package/dist/types/common/context/event-context.d.ts.map +1 -0
  100. package/dist/types/common/context/observation-context-manager.d.ts +28 -0
  101. package/dist/types/common/context/observation-context-manager.d.ts.map +1 -0
  102. package/dist/types/common/event-emitter/contextual-ee.d.ts +2 -2
  103. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  104. package/dist/types/common/session/constants.d.ts +1 -0
  105. package/dist/types/common/session/constants.d.ts.map +1 -1
  106. package/dist/types/common/session/session-entity.d.ts +0 -1
  107. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  108. package/dist/types/common/timing/nav-timing.d.ts.map +1 -1
  109. package/dist/types/common/wrap/index.d.ts +1 -2
  110. package/dist/types/common/wrap/index.d.ts.map +1 -1
  111. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  112. package/dist/types/common/wrap/wrap-function.d.ts +0 -1
  113. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  114. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  115. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  116. package/dist/types/features/jserrors/aggregate/index.d.ts +4 -3
  117. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  118. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
  119. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  120. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  121. package/dist/types/features/session_replay/constants.d.ts +4 -0
  122. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  123. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  124. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  125. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
  126. package/dist/types/features/session_replay/shared/utils.d.ts +4 -0
  127. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -0
  128. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +19 -0
  129. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -0
  130. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts +16 -0
  131. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts.map +1 -0
  132. package/dist/types/features/soft_navigations/aggregate/index.d.ts +36 -0
  133. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -0
  134. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts +12 -0
  135. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -0
  136. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +50 -0
  137. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -0
  138. package/dist/types/features/soft_navigations/constants.d.ts +20 -0
  139. package/dist/types/features/soft_navigations/constants.d.ts.map +1 -0
  140. package/dist/types/features/soft_navigations/index.d.ts +2 -0
  141. package/dist/types/features/soft_navigations/index.d.ts.map +1 -0
  142. package/dist/types/features/soft_navigations/instrument/index.d.ts +7 -0
  143. package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -0
  144. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  145. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  146. package/dist/types/features/utils/instrument-base.d.ts +1 -7
  147. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  148. package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -1
  149. package/dist/types/loaders/agent-base.d.ts +5 -1
  150. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  151. package/dist/types/loaders/agent.d.ts +2 -2
  152. package/dist/types/loaders/agent.d.ts.map +1 -1
  153. package/dist/types/loaders/api/api-methods.d.ts +3 -0
  154. package/dist/types/loaders/api/api-methods.d.ts.map +1 -0
  155. package/dist/types/loaders/api/api.d.ts +3 -6
  156. package/dist/types/loaders/api/api.d.ts.map +1 -1
  157. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  158. package/dist/types/loaders/features/features.d.ts +1 -0
  159. package/dist/types/loaders/features/features.d.ts.map +1 -1
  160. package/dist/types/loaders/micro-agent.d.ts +0 -1
  161. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  162. package/package.json +1 -1
  163. package/src/cdn/experimental.js +4 -2
  164. package/src/cdn/spa.js +3 -1
  165. package/src/common/aggregate/aggregator.js +2 -11
  166. package/src/common/config/state/init.js +3 -1
  167. package/src/common/context/observation-context-manager.js +55 -0
  168. package/src/common/event-emitter/contextual-ee.js +20 -10
  169. package/src/common/session/constants.js +1 -0
  170. package/src/common/session/session-entity.js +3 -1
  171. package/src/common/timing/nav-timing.js +7 -3
  172. package/src/common/timing/now.js +1 -1
  173. package/src/common/util/feature-flags.js +1 -1
  174. package/src/common/wrap/index.js +1 -2
  175. package/src/common/wrap/wrap-events.js +3 -3
  176. package/src/common/wrap/wrap-fetch.js +3 -2
  177. package/src/common/wrap/wrap-function.js +4 -6
  178. package/src/common/wrap/wrap-promise.js +3 -2
  179. package/src/features/ajax/aggregate/index.js +36 -18
  180. package/src/features/jserrors/aggregate/index.js +70 -73
  181. package/src/features/page_view_event/aggregate/index.js +1 -1
  182. package/src/features/page_view_event/aggregate/initialized-features.js +1 -0
  183. package/src/features/session_replay/aggregate/index.js +92 -95
  184. package/src/features/session_replay/constants.js +5 -0
  185. package/src/features/session_replay/instrument/index.js +24 -9
  186. package/src/features/session_replay/shared/recorder.js +5 -4
  187. package/src/features/session_replay/shared/stylesheet-evaluator.js +8 -7
  188. package/src/features/session_replay/shared/utils.js +19 -0
  189. package/src/features/soft_navigations/aggregate/ajax-node.js +57 -0
  190. package/src/features/soft_navigations/aggregate/bel-node.js +26 -0
  191. package/src/features/soft_navigations/aggregate/index.js +254 -0
  192. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +53 -0
  193. package/src/features/soft_navigations/aggregate/interaction.js +159 -0
  194. package/src/features/soft_navigations/constants.js +29 -0
  195. package/src/features/soft_navigations/index.js +1 -0
  196. package/src/features/soft_navigations/instrument/index.js +67 -0
  197. package/src/features/spa/aggregate/index.js +5 -4
  198. package/src/features/utils/agent-session.js +2 -1
  199. package/src/features/utils/instrument-base.js +7 -10
  200. package/src/features/utils/lazy-feature-loader.js +2 -0
  201. package/src/loaders/agent-base.js +18 -3
  202. package/src/loaders/agent.js +18 -17
  203. package/src/loaders/api/api-methods.js +12 -0
  204. package/src/loaders/api/api.js +17 -28
  205. package/src/loaders/configure/configure.js +4 -1
  206. package/src/loaders/features/enabled-features.js +1 -1
  207. package/src/loaders/features/features.js +3 -1
  208. package/dist/cjs/common/wrap/wrap-raf.js +0 -55
  209. package/dist/esm/common/wrap/wrap-raf.js +0 -48
  210. package/dist/types/common/event-emitter/event-context.d.ts.map +0 -1
  211. package/dist/types/common/wrap/wrap-raf.d.ts +0 -16
  212. package/dist/types/common/wrap/wrap-raf.d.ts.map +0 -1
  213. package/src/common/wrap/wrap-raf.js +0 -52
  214. /package/dist/cjs/common/{event-emitter → context}/event-context.js +0 -0
  215. /package/dist/esm/common/{event-emitter → context}/event-context.js +0 -0
  216. /package/dist/types/common/{event-emitter → context}/event-context.d.ts +0 -0
  217. /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';
@@ -53,103 +53,100 @@ export class Aggregate extends AggregateBase {
53
53
  this.recorder = args?.recorder;
54
54
  if (this.recorder) this.recorder.parent = this;
55
55
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
56
- const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
57
- if (shouldSetup) {
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, () => {
66
- this.recorder?.stopRecording();
67
- });
68
- // 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.
69
- this.ee.on(SESSION_EVENTS.RESUME, () => {
70
- if (!this.recorder) return;
71
- // if the mode changed on a different tab, it needs to update this instance to match
72
- const {
73
- session
74
- } = getRuntime(this.agentIdentifier);
75
- this.mode = session.state.sessionReplayMode;
76
- if (!this.initialized || this.mode === MODE.OFF) return;
77
- this.recorder?.startRecording();
78
- });
79
- this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
80
- if (!this.recorder || !this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
81
- if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
82
- this.mode = data.sessionReplay;
83
- });
84
56
 
85
- // Bespoke logic for blobs endpoint.
86
- this.scheduler = new HarvestScheduler('browser/blobs', {
87
- onFinished: this.onHarvestFinished.bind(this),
88
- retryDelay: this.harvestTimeSeconds,
89
- getPayload: this.prepareHarvest.bind(this),
90
- raw: true
91
- }, this);
92
- if (this.recorder?.getEvents().type === 'preloaded') {
93
- this.prepUtils().then(() => {
94
- this.scheduler.runHarvest();
95
- });
96
- }
97
- registerHandler('recordReplay', () => {
98
- // if it has aborted or BCS returned bad entitlements, do not allow
99
- if (this.blocked || !this.entitled) return;
100
- // if it isnt already (fully) initialized... initialize it
101
- if (!this.recorder) this.initializeRecording(false, true, true);
102
- // its been initialized and imported the recorder but its not recording (mode === off || error)
103
- else if (this.mode !== MODE.FULL) this.switchToFull();
104
- // if it gets all the way to here, that means a full session is already recording... do nothing
105
- }, this.featureName, this.ee);
106
- registerHandler('pauseReplay', () => {
107
- this.forceStop(this.mode !== MODE.ERROR);
108
- }, this.featureName, this.ee);
57
+ // 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.
58
+ this.ee.on(SESSION_EVENTS.RESET, () => {
59
+ this.scheduler.runHarvest();
60
+ this.abort(ABORT_REASONS.RESET);
61
+ });
109
62
 
110
- // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
111
- // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
112
- registerHandler('errorAgg', e => {
113
- this.errorNoticed = true;
114
- if (this.recorder) this.recorder.currentBufferTarget.hasError = true;
115
- // run once
116
- if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
117
- this.switchToFull();
118
- }
119
- }, this.featureName, this.ee);
63
+ // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
64
+ this.ee.on(SESSION_EVENTS.PAUSE, () => {
65
+ this.recorder?.stopRecording();
66
+ });
67
+ // 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.
68
+ this.ee.on(SESSION_EVENTS.RESUME, () => {
69
+ if (!this.recorder) return;
70
+ // if the mode changed on a different tab, it needs to update this instance to match
120
71
  const {
121
- error_sampling_rate,
122
- sampling_rate,
123
- autoStart,
124
- block_selector,
125
- mask_text_selector,
126
- mask_all_inputs,
127
- inline_stylesheet,
128
- inline_images,
129
- collect_fonts
130
- } = getConfigurationValue(this.agentIdentifier, 'session_replay');
131
- this.waitForFlags(['sr']).then(_ref => {
132
- let [flagOn] = _ref;
133
- this.entitled = flagOn;
134
- if (!this.entitled && this.recorder?.recording) {
135
- this.recorder.abort(ABORT_REASONS.ENTITLEMENTS);
136
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee);
137
- }
138
- this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
139
- }).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
72
+ session
73
+ } = getRuntime(this.agentIdentifier);
74
+ this.mode = session.state.sessionReplayMode;
75
+ if (!this.initialized || this.mode === MODE.OFF) return;
76
+ this.recorder?.startRecording();
77
+ });
78
+ this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
79
+ if (!this.recorder || !this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
80
+ if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
81
+ this.mode = data.sessionReplay;
82
+ });
140
83
 
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
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
150
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
151
- this.drain();
152
- }
84
+ // Bespoke logic for blobs endpoint.
85
+ this.scheduler = new HarvestScheduler('browser/blobs', {
86
+ onFinished: this.onHarvestFinished.bind(this),
87
+ retryDelay: this.harvestTimeSeconds,
88
+ getPayload: this.prepareHarvest.bind(this),
89
+ raw: true
90
+ }, this);
91
+ registerHandler(SR_EVENT_EMITTER_TYPES.RECORD, () => {
92
+ // if it has aborted or BCS returned bad entitlements, do not allow
93
+ if (this.blocked || !this.entitled) return;
94
+ // if it isnt already (fully) initialized... initialize it
95
+ if (!this.recorder) this.initializeRecording(false, true, true);
96
+ // its been initialized and imported the recorder but its not recording (mode === off || error)
97
+ else if (this.mode !== MODE.FULL) this.switchToFull();
98
+ // if it gets all the way to here, that means a full session is already recording... do nothing
99
+ }, this.featureName, this.ee);
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);
114
+ const {
115
+ error_sampling_rate,
116
+ sampling_rate,
117
+ autoStart,
118
+ block_selector,
119
+ mask_text_selector,
120
+ mask_all_inputs,
121
+ inline_stylesheet,
122
+ inline_images,
123
+ collect_fonts
124
+ } = getConfigurationValue(this.agentIdentifier, 'session_replay');
125
+ this.waitForFlags(['sr']).then(_ref => {
126
+ let [flagOn] = _ref;
127
+ this.entitled = flagOn;
128
+ if (!this.entitled && this.recorder?.recording) {
129
+ this.abort(ABORT_REASONS.ENTITLEMENTS);
130
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee);
131
+ return;
132
+ }
133
+ this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
134
+ }).then(() => {
135
+ if (this.mode === MODE.OFF) args?.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
136
+ sharedChannel.onReplayReady(this.mode); // notify watchers that replay started with the mode
137
+ });
138
+
139
+ /** 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 */
140
+ if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
141
+ if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
142
+ if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
143
+ if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee);
144
+ if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
145
+ if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
146
+ if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
147
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
148
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
149
+ this.drain();
153
150
  }
154
151
  switchToFull() {
155
152
  this.mode = MODE.FULL;
@@ -197,6 +194,11 @@ export class Aggregate extends AggregateBase {
197
194
  return;
198
195
  }
199
196
  }
197
+ if (this.recorder?.getEvents().type === 'preloaded') {
198
+ this.prepUtils().then(() => {
199
+ this.scheduler.runHarvest();
200
+ });
201
+ }
200
202
  if (!this.recorder) {
201
203
  try {
202
204
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
@@ -389,7 +391,7 @@ export class Aggregate extends AggregateBase {
389
391
  });
390
392
  this.recorder?.clearTimestamps?.();
391
393
  this.ee.emit('REPLAY_ABORTED');
392
- this.recorder?.clearBuffer?.();
394
+ while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
393
395
  }
394
396
  syncWithSessionManager() {
395
397
  let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -1,6 +1,10 @@
1
1
  import { MODE } from '../../common/session/constants';
2
2
  import { FEATURE_NAMES } from '../../loaders/features/features';
3
3
  export const FEATURE_NAME = FEATURE_NAMES.sessionReplay;
4
+ export const SR_EVENT_EMITTER_TYPES = {
5
+ RECORD: 'recordReplay',
6
+ PAUSE: 'pauseReplay'
7
+ };
4
8
  export const AVG_COMPRESSION = 0.12;
5
9
  export const RRWEB_EVENT_TYPES = {
6
10
  DomContentLoaded: 0,
@@ -9,23 +9,38 @@
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
  export class Instrument extends InstrumentBase {
16
17
  static featureName = FEATURE_NAME;
17
18
  constructor(agentIdentifier, aggregator) {
18
19
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 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("".concat(PREFIX, "_").concat(DEFAULT_KEY)));
24
+ } catch (err) {}
25
+ if (this.#canPreloadRecorder(session)) {
26
+ this.#startRecording(session?.sessionReplayMode);
27
+ } else {
28
+ this.importAggregator();
29
+ }
30
+ }
31
+
32
+ // At this point wherein session state exists already but we haven't init SessionEntity aka verify timers.
33
+ #canPreloadRecorder(session) {
34
+ if (!session) {
35
+ // 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 {
42
+ // SR mode was OFF but may potentially be turned on if session resets and configs allows the new session to have replay...
43
+ return isPreloadAllowed(this.agentIdentifier);
29
44
  }
30
45
  }
31
46
  async #startRecording(mode) {
@@ -37,6 +52,7 @@ export class Instrument extends InstrumentBase {
37
52
  agentIdentifier: this.agentIdentifier
38
53
  });
39
54
  this.recorder.startRecording();
55
+ this.abortHandler = this.recorder.stopRecording;
40
56
  this.importAggregator({
41
57
  recorder: this.recorder
42
58
  });
@@ -109,14 +109,15 @@ export class Recorder {
109
109
  const incompletes = stylesheetEvaluator.evaluate();
110
110
  /** Only stop ignoring data if already ignoring and a new valid snapshap is taking place (0 incompletes and we get a meta node for the snap) */
111
111
  if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
112
- if (incompletes) {
112
+ if (incompletes > 0) {
113
113
  /** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
114
114
  stylesheetEvaluator.fix().then(failedToFix => {
115
- if (failedToFix) {
115
+ if (failedToFix > 0) {
116
116
  this.currentBufferTarget.inlinedAllStylesheets = false;
117
117
  this.shouldFix = false;
118
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
119
- } else handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
118
+ }
119
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
120
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
120
121
  this.takeFullSnapshot();
121
122
  });
122
123
  /** Only start ignoring data if got a faulty snapshot */
@@ -18,15 +18,15 @@ class StylesheetEvaluator {
18
18
  let incompletes = 0;
19
19
  if (isBrowserScope) {
20
20
  for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
21
- const ss = document.styleSheets[i];
22
- if (!this.#evaluated.has(ss)) {
23
- this.#evaluated.add(ss);
21
+ if (!this.#evaluated.has(document.styleSheets[i])) {
22
+ this.#evaluated.add(document.styleSheets[i]);
24
23
  try {
25
24
  // eslint-disable-next-line
26
- const temp = ss.cssRules;
25
+ const temp = document.styleSheets[i].cssRules;
27
26
  } catch (err) {
27
+ if (!document.styleSheets[i].href) return;
28
28
  incompletes++;
29
- this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i], ss.href));
29
+ this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i]));
30
30
  }
31
31
  }
32
32
  }
@@ -53,9 +53,10 @@ class StylesheetEvaluator {
53
53
  * @param {*} href - The asset href to fetch
54
54
  * @returns {Promise}
55
55
  */
56
- async #fetchAndOverride(target, href) {
56
+ async #fetchAndOverride(target) {
57
+ if (!target?.href) return;
57
58
  try {
58
- const stylesheetContents = await originals.FETCH.bind(window)(href);
59
+ const stylesheetContents = await originals.FETCH.bind(window)(target.href);
59
60
  if (!stylesheetContents.ok) {
60
61
  this.failedToFix++;
61
62
  return;
@@ -0,0 +1,17 @@
1
+ import { getConfigurationValue, originals } from '../../../common/config/config';
2
+ import { isBrowserScope } from '../../../common/constants/runtime';
3
+ export const enableSessionTracking = agentId => isBrowserScope && getConfigurationValue(agentId, 'privacy.cookies_enabled') === true;
4
+ function hasReplayPrerequisite(agentId) {
5
+ return originals.MO &&
6
+ // Session Replay cannot work without Mutation Observer
7
+ enableSessionTracking &&
8
+ // 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
+ export function isPreloadAllowed(agentId) {
12
+ return getConfigurationValue(agentId, 'session_replay.preload') === true && hasReplayPrerequisite(agentId);
13
+ }
14
+ export function canImportReplayAgg(agentId, sessionMgr) {
15
+ if (!hasReplayPrerequisite(agentId)) return false;
16
+ 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
17
+ }
@@ -0,0 +1,43 @@
1
+ import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
2
+ import { NODE_TYPE } from '../constants';
3
+ import { BelNode } from './bel-node';
4
+ export class AjaxNode extends BelNode {
5
+ constructor(agentIdentifier, ajaxEvent) {
6
+ super(agentIdentifier);
7
+ this.belType = NODE_TYPE.AJAX;
8
+ this.method = ajaxEvent.method;
9
+ this.status = ajaxEvent.status;
10
+ this.domain = ajaxEvent.domain;
11
+ this.path = ajaxEvent.path;
12
+ this.txSize = ajaxEvent.requestSize;
13
+ this.rxSize = ajaxEvent.responseSize;
14
+ this.requestedWith = ajaxEvent.type === 'fetch' ? 1 : ''; // 'xhr' and 'beacon' types get the empty string
15
+ this.spanId = ajaxEvent.spanId;
16
+ this.traceId = ajaxEvent.traceId;
17
+ this.spanTimestamp = ajaxEvent.spanTimestamp;
18
+ this.gql = ajaxEvent.gql;
19
+ this.start = ajaxEvent.startTime;
20
+ this.end = ajaxEvent.endTime;
21
+ }
22
+ serialize(parentStartTimestamp) {
23
+ const addString = getAddStringContext(this.agentIdentifier);
24
+ const nodeList = [];
25
+
26
+ // 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.
27
+ const fields = [numeric(this.belType), 0,
28
+ // this will be overwritten below with number of attached nodes
29
+ numeric(this.start - parentStartTimestamp),
30
+ // start relative to parent start (if part of first node in payload) or first parent start
31
+ numeric(this.end - this.start),
32
+ // end is relative to start
33
+ numeric(this.callbackEnd), numeric(this.callbackDuration), addString(this.method), numeric(this.status), addString(this.domain), addString(this.path), numeric(this.txSize), numeric(this.rxSize), this.requestedWith, addString(this.nodeId), nullable(this.spanId, addString, true) + nullable(this.traceId, addString, true) + nullable(this.spanTimestamp, numeric)];
34
+ let allAttachedNodes = [];
35
+ if (typeof this.gql === 'object') allAttachedNodes = addCustomAttributes(this.gql, addString);
36
+ this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
37
+
38
+ fields[1] = numeric(allAttachedNodes.length);
39
+ nodeList.push(fields);
40
+ if (allAttachedNodes.length) nodeList.push(allAttachedNodes.join(';'));
41
+ return nodeList.join(';');
42
+ }
43
+ }
@@ -0,0 +1,22 @@
1
+ import { now } from '../../../common/timing/now';
2
+ let nodesSeen = 0;
3
+ export class BelNode {
4
+ belType;
5
+ /** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
6
+ children = [];
7
+ start = now();
8
+ end;
9
+ callbackEnd = 0;
10
+ callbackDuration = 0;
11
+ nodeId = ++nodesSeen;
12
+ constructor(agentIdentifier) {
13
+ if (!agentIdentifier) throw new Error('Interaction is missing core attributes');
14
+ this.agentIdentifier = agentIdentifier;
15
+ }
16
+ addChild(child) {
17
+ this.children.push(child);
18
+ }
19
+
20
+ /** Virtual fn for stringifying an instance. */
21
+ serialize() {}
22
+ }