@newrelic/browser-agent 1.252.1 → 1.254.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 (252) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +2 -2
  3. package/dist/cjs/cdn/experimental.js +6 -2
  4. package/dist/cjs/cdn/polyfills.js +2 -1
  5. package/dist/cjs/cdn/spa.js +5 -3
  6. package/dist/cjs/common/aggregate/aggregator.js +1 -8
  7. package/dist/cjs/common/config/state/init.js +7 -0
  8. package/dist/cjs/common/config/state/runtime.js +4 -1
  9. package/dist/cjs/common/constants/env.cdn.js +1 -1
  10. package/dist/cjs/common/constants/env.npm.js +1 -1
  11. package/dist/cjs/common/drain/drain.js +41 -27
  12. package/dist/cjs/common/event-emitter/contextual-ee.js +17 -12
  13. package/dist/cjs/common/harvest/harvest.js +5 -1
  14. package/dist/cjs/common/session/constants.js +2 -1
  15. package/dist/cjs/common/timing/nav-timing.js +8 -3
  16. package/dist/cjs/common/timing/now.js +1 -1
  17. package/dist/cjs/common/timing/time-keeper.js +94 -0
  18. package/dist/cjs/common/util/feature-flags.js +14 -31
  19. package/dist/cjs/common/wrap/index.js +0 -7
  20. package/dist/cjs/features/ajax/aggregate/index.js +41 -29
  21. package/dist/cjs/features/jserrors/aggregate/index.js +96 -84
  22. package/dist/cjs/features/metrics/aggregate/index.js +25 -24
  23. package/dist/cjs/features/page_action/aggregate/index.js +6 -4
  24. package/dist/cjs/features/page_view_event/aggregate/index.js +23 -3
  25. package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +1 -0
  26. package/dist/cjs/features/page_view_timing/aggregate/index.js +15 -16
  27. package/dist/cjs/features/session_replay/aggregate/index.js +102 -92
  28. package/dist/cjs/features/session_replay/constants.js +5 -1
  29. package/dist/cjs/features/session_replay/instrument/index.js +24 -8
  30. package/dist/cjs/features/session_replay/shared/utils.js +26 -0
  31. package/dist/cjs/features/session_trace/aggregate/index.js +11 -8
  32. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +50 -0
  33. package/dist/cjs/features/soft_navigations/aggregate/bel-node.js +29 -0
  34. package/dist/cjs/features/soft_navigations/aggregate/index.js +268 -0
  35. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +62 -0
  36. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +146 -0
  37. package/dist/cjs/features/soft_navigations/constants.js +31 -0
  38. package/dist/cjs/features/soft_navigations/index.js +12 -0
  39. package/dist/cjs/features/soft_navigations/instrument/index.js +79 -0
  40. package/dist/cjs/features/spa/aggregate/index.js +23 -18
  41. package/dist/cjs/features/utils/agent-session.js +2 -1
  42. package/dist/cjs/features/utils/aggregate-base.js +18 -5
  43. package/dist/cjs/features/utils/feature-base.js +2 -0
  44. package/dist/cjs/features/utils/instrument-base.js +7 -9
  45. package/dist/cjs/features/utils/lazy-feature-loader.js +2 -0
  46. package/dist/cjs/loaders/agent-base.js +13 -3
  47. package/dist/cjs/loaders/agent.js +19 -22
  48. package/dist/cjs/loaders/api/api-methods.js +2 -1
  49. package/dist/cjs/loaders/api/api.js +15 -12
  50. package/dist/cjs/loaders/configure/configure.js +5 -2
  51. package/dist/cjs/loaders/configure/nonce.cdn.js +13 -0
  52. package/dist/cjs/loaders/configure/nonce.js +2 -13
  53. package/dist/cjs/loaders/configure/public-path.cdn.js +16 -0
  54. package/dist/cjs/loaders/configure/public-path.js +2 -8
  55. package/dist/cjs/loaders/features/enabled-features.js +1 -1
  56. package/dist/cjs/loaders/features/features.js +3 -1
  57. package/dist/esm/cdn/experimental.js +5 -2
  58. package/dist/esm/cdn/polyfills.js +2 -1
  59. package/dist/esm/cdn/spa.js +3 -1
  60. package/dist/esm/common/aggregate/aggregator.js +1 -8
  61. package/dist/esm/common/config/state/init.js +7 -0
  62. package/dist/esm/common/config/state/runtime.js +4 -1
  63. package/dist/esm/common/constants/env.cdn.js +1 -1
  64. package/dist/esm/common/constants/env.npm.js +1 -1
  65. package/dist/esm/common/drain/drain.js +40 -27
  66. package/dist/esm/common/event-emitter/contextual-ee.js +17 -12
  67. package/dist/esm/common/harvest/harvest.js +5 -1
  68. package/dist/esm/common/session/constants.js +1 -0
  69. package/dist/esm/common/timing/nav-timing.js +8 -3
  70. package/dist/esm/common/timing/now.js +1 -1
  71. package/dist/esm/common/timing/time-keeper.js +88 -0
  72. package/dist/esm/common/util/feature-flags.js +14 -31
  73. package/dist/esm/common/wrap/index.js +1 -2
  74. package/dist/esm/features/ajax/aggregate/index.js +43 -31
  75. package/dist/esm/features/jserrors/aggregate/index.js +96 -84
  76. package/dist/esm/features/metrics/aggregate/index.js +25 -24
  77. package/dist/esm/features/page_action/aggregate/index.js +6 -4
  78. package/dist/esm/features/page_view_event/aggregate/index.js +23 -3
  79. package/dist/esm/features/page_view_event/aggregate/initialized-features.js +1 -0
  80. package/dist/esm/features/page_view_timing/aggregate/index.js +15 -16
  81. package/dist/esm/features/session_replay/aggregate/index.js +103 -93
  82. package/dist/esm/features/session_replay/constants.js +4 -0
  83. package/dist/esm/features/session_replay/instrument/index.js +25 -9
  84. package/dist/esm/features/session_replay/shared/utils.js +17 -0
  85. package/dist/esm/features/session_trace/aggregate/index.js +11 -8
  86. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +43 -0
  87. package/dist/esm/features/soft_navigations/aggregate/bel-node.js +22 -0
  88. package/dist/esm/features/soft_navigations/aggregate/index.js +261 -0
  89. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +55 -0
  90. package/dist/esm/features/soft_navigations/aggregate/interaction.js +140 -0
  91. package/dist/esm/features/soft_navigations/constants.js +25 -0
  92. package/dist/esm/features/soft_navigations/index.js +1 -0
  93. package/dist/esm/features/soft_navigations/instrument/index.js +73 -0
  94. package/dist/esm/features/spa/aggregate/index.js +23 -18
  95. package/dist/esm/features/utils/agent-session.js +2 -1
  96. package/dist/esm/features/utils/aggregate-base.js +18 -5
  97. package/dist/esm/features/utils/feature-base.js +2 -0
  98. package/dist/esm/features/utils/instrument-base.js +8 -10
  99. package/dist/esm/features/utils/lazy-feature-loader.js +2 -0
  100. package/dist/esm/loaders/agent-base.js +13 -3
  101. package/dist/esm/loaders/agent.js +19 -22
  102. package/dist/esm/loaders/api/api-methods.js +2 -1
  103. package/dist/esm/loaders/api/api.js +15 -12
  104. package/dist/esm/loaders/configure/configure.js +5 -2
  105. package/dist/esm/loaders/configure/nonce.cdn.js +11 -0
  106. package/dist/esm/loaders/configure/nonce.js +1 -11
  107. package/dist/esm/loaders/configure/public-path.cdn.js +9 -0
  108. package/dist/esm/loaders/configure/public-path.js +2 -8
  109. package/dist/esm/loaders/features/enabled-features.js +1 -1
  110. package/dist/esm/loaders/features/features.js +3 -1
  111. package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
  112. package/dist/types/common/config/state/init.d.ts.map +1 -1
  113. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  114. package/dist/types/common/drain/drain.d.ts +6 -0
  115. package/dist/types/common/drain/drain.d.ts.map +1 -1
  116. package/dist/types/common/event-emitter/contextual-ee.d.ts +2 -1
  117. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  118. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  119. package/dist/types/common/session/constants.d.ts +1 -0
  120. package/dist/types/common/session/constants.d.ts.map +1 -1
  121. package/dist/types/common/timing/nav-timing.d.ts.map +1 -1
  122. package/dist/types/common/timing/time-keeper.d.ts +31 -0
  123. package/dist/types/common/timing/time-keeper.d.ts.map +1 -0
  124. package/dist/types/common/util/feature-flags.d.ts +11 -2
  125. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  126. package/dist/types/common/wrap/index.d.ts +1 -2
  127. package/dist/types/common/wrap/index.d.ts.map +1 -1
  128. package/dist/types/features/ajax/aggregate/index.d.ts +5 -5
  129. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  130. package/dist/types/features/jserrors/aggregate/index.d.ts +4 -3
  131. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  132. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  133. package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
  134. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  135. package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
  136. package/dist/types/features/page_view_timing/aggregate/index.d.ts +0 -2
  137. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  138. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  139. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  140. package/dist/types/features/session_replay/constants.d.ts +4 -0
  141. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  142. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  143. package/dist/types/features/session_replay/shared/utils.d.ts +4 -0
  144. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -0
  145. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +19 -0
  146. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -0
  147. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts +16 -0
  148. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts.map +1 -0
  149. package/dist/types/features/soft_navigations/aggregate/index.d.ts +34 -0
  150. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -0
  151. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts +12 -0
  152. package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -0
  153. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +50 -0
  154. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -0
  155. package/dist/types/features/soft_navigations/constants.d.ts +20 -0
  156. package/dist/types/features/soft_navigations/constants.d.ts.map +1 -0
  157. package/dist/types/features/soft_navigations/index.d.ts +2 -0
  158. package/dist/types/features/soft_navigations/index.d.ts.map +1 -0
  159. package/dist/types/features/soft_navigations/instrument/index.d.ts +7 -0
  160. package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -0
  161. package/dist/types/features/spa/aggregate/index.d.ts +2 -0
  162. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  163. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  164. package/dist/types/features/utils/aggregate-base.d.ts +2 -2
  165. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  166. package/dist/types/features/utils/feature-base.d.ts +1 -0
  167. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  168. package/dist/types/features/utils/instrument-base.d.ts +1 -7
  169. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  170. package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -1
  171. package/dist/types/loaders/agent-base.d.ts +5 -1
  172. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  173. package/dist/types/loaders/agent.d.ts +2 -2
  174. package/dist/types/loaders/agent.d.ts.map +1 -1
  175. package/dist/types/loaders/api/api-methods.d.ts.map +1 -1
  176. package/dist/types/loaders/api/api.d.ts +3 -5
  177. package/dist/types/loaders/api/api.d.ts.map +1 -1
  178. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  179. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  180. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  181. package/dist/types/loaders/features/features.d.ts +1 -0
  182. package/dist/types/loaders/features/features.d.ts.map +1 -1
  183. package/dist/types/loaders/micro-agent.d.ts +0 -1
  184. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  185. package/package.json +1 -1
  186. package/src/cdn/experimental.js +4 -2
  187. package/src/cdn/polyfills.js +1 -0
  188. package/src/cdn/spa.js +3 -1
  189. package/src/common/aggregate/aggregator.js +2 -11
  190. package/src/common/config/state/init.js +3 -1
  191. package/src/common/config/state/runtime.js +4 -1
  192. package/src/common/drain/drain.js +41 -28
  193. package/src/common/event-emitter/contextual-ee.js +21 -13
  194. package/src/common/harvest/harvest.js +4 -1
  195. package/src/common/session/constants.js +1 -0
  196. package/src/common/timing/nav-timing.js +7 -3
  197. package/src/common/timing/now.js +1 -1
  198. package/src/common/timing/time-keeper.js +96 -0
  199. package/src/common/util/feature-flags.js +13 -31
  200. package/src/common/wrap/index.js +1 -2
  201. package/src/features/ajax/aggregate/index.js +43 -33
  202. package/src/features/jserrors/aggregate/index.js +82 -87
  203. package/src/features/metrics/aggregate/index.js +18 -17
  204. package/src/features/page_action/aggregate/index.js +6 -5
  205. package/src/features/page_view_event/aggregate/index.js +19 -3
  206. package/src/features/page_view_event/aggregate/initialized-features.js +1 -0
  207. package/src/features/page_view_timing/aggregate/index.js +15 -15
  208. package/src/features/session_replay/aggregate/index.js +95 -92
  209. package/src/features/session_replay/constants.js +5 -0
  210. package/src/features/session_replay/instrument/index.js +24 -9
  211. package/src/features/session_replay/shared/utils.js +19 -0
  212. package/src/features/session_trace/aggregate/index.js +2 -2
  213. package/src/features/soft_navigations/aggregate/ajax-node.js +57 -0
  214. package/src/features/soft_navigations/aggregate/bel-node.js +26 -0
  215. package/src/features/soft_navigations/aggregate/index.js +256 -0
  216. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +53 -0
  217. package/src/features/soft_navigations/aggregate/interaction.js +159 -0
  218. package/src/features/soft_navigations/constants.js +29 -0
  219. package/src/features/soft_navigations/index.js +1 -0
  220. package/src/features/soft_navigations/instrument/index.js +67 -0
  221. package/src/features/spa/aggregate/index.js +20 -17
  222. package/src/features/utils/agent-session.js +2 -1
  223. package/src/features/utils/aggregate-base.js +16 -8
  224. package/src/features/utils/feature-base.js +3 -0
  225. package/src/features/utils/instrument-base.js +8 -10
  226. package/src/features/utils/lazy-feature-loader.js +2 -0
  227. package/src/loaders/agent-base.js +13 -3
  228. package/src/loaders/agent.js +20 -19
  229. package/src/loaders/api/api-methods.js +4 -1
  230. package/src/loaders/api/api.js +15 -13
  231. package/src/loaders/configure/configure.js +4 -1
  232. package/src/loaders/configure/nonce.cdn.js +12 -0
  233. package/src/loaders/configure/nonce.js +1 -12
  234. package/src/loaders/configure/public-path.cdn.js +9 -0
  235. package/src/loaders/configure/public-path.js +2 -8
  236. package/src/loaders/features/enabled-features.js +1 -1
  237. package/src/loaders/features/features.js +3 -1
  238. package/dist/cjs/common/wrap/wrap-raf.js +0 -55
  239. package/dist/cjs/loaders/configure/nonce.npm.js +0 -2
  240. package/dist/cjs/loaders/configure/public-path.npm.js +0 -10
  241. package/dist/esm/common/wrap/wrap-raf.js +0 -48
  242. package/dist/esm/loaders/configure/nonce.npm.js +0 -1
  243. package/dist/esm/loaders/configure/public-path.npm.js +0 -3
  244. package/dist/types/common/wrap/wrap-raf.d.ts +0 -16
  245. package/dist/types/common/wrap/wrap-raf.d.ts.map +0 -1
  246. package/dist/types/loaders/configure/nonce.npm.d.ts +0 -1
  247. package/dist/types/loaders/configure/nonce.npm.d.ts.map +0 -1
  248. package/dist/types/loaders/configure/public-path.npm.d.ts +0 -2
  249. package/dist/types/loaders/configure/public-path.npm.d.ts.map +0 -1
  250. package/src/common/wrap/wrap-raf.js +0 -52
  251. package/src/loaders/configure/nonce.npm.js +0 -1
  252. package/src/loaders/configure/public-path.npm.js +0 -3
@@ -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';
@@ -27,6 +27,7 @@ import { now } from '../../../common/timing/now';
27
27
  import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants';
28
28
  import { stringify } from '../../../common/util/stringify';
29
29
  import { stylesheetEvaluator } from '../shared/stylesheet-evaluator';
30
+ import { deregisterDrain } from '../../../common/drain/drain';
30
31
  export class Aggregate extends AggregateBase {
31
32
  static featureName = FEATURE_NAME;
32
33
  // pass the recorder into the aggregator
@@ -53,103 +54,103 @@ export class Aggregate extends AggregateBase {
53
54
  this.recorder = args?.recorder;
54
55
  if (this.recorder) this.recorder.parent = this;
55
56
  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
57
 
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);
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
+ });
109
63
 
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);
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
120
72
  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);
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
+
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
+ registerHandler(SR_EVENT_EMITTER_TYPES.RECORD, () => {
93
+ // if it has aborted or BCS returned bad entitlements, do not allow
94
+ if (this.blocked || !this.entitled) return;
95
+ // if it isnt already (fully) initialized... initialize it
96
+ if (!this.recorder) this.initializeRecording(false, true, true);
97
+ // its been initialized and imported the recorder but its not recording (mode === off || error)
98
+ else if (this.mode !== MODE.FULL) this.switchToFull();
99
+ // if it gets all the way to here, that means a full session is already recording... do nothing
100
+ }, this.featureName, this.ee);
101
+ registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
102
+ this.forceStop(this.mode !== MODE.ERROR);
103
+ }, this.featureName, this.ee);
104
+
105
+ // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
106
+ // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
107
+ registerHandler('errorAgg', e => {
108
+ this.errorNoticed = true;
109
+ if (this.recorder) this.recorder.currentBufferTarget.hasError = true;
110
+ // run once
111
+ if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
112
+ this.switchToFull();
113
+ }
114
+ }, this.featureName, this.ee);
115
+ const {
116
+ error_sampling_rate,
117
+ sampling_rate,
118
+ autoStart,
119
+ block_selector,
120
+ mask_text_selector,
121
+ mask_all_inputs,
122
+ inline_stylesheet,
123
+ inline_images,
124
+ collect_fonts
125
+ } = getConfigurationValue(this.agentIdentifier, 'session_replay');
126
+ this.waitForFlags(['sr']).then(_ref => {
127
+ let [flagOn] = _ref;
128
+ this.entitled = flagOn;
129
+ if (!this.entitled) {
130
+ deregisterDrain(this.agentIdentifier, this.featureName);
131
+ if (this.recorder?.recording) {
132
+ this.abort(ABORT_REASONS.ENTITLEMENTS);
136
133
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee);
137
134
  }
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
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
- 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);
135
+ return;
136
+ }
151
137
  this.drain();
152
- }
138
+ this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
139
+ }).then(() => {
140
+ if (this.mode === MODE.OFF) args?.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
141
+ sharedChannel.onReplayReady(this.mode); // notify watchers that replay started with the mode
142
+ });
143
+
144
+ /** 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 */
145
+ if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
146
+ if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
147
+ if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
148
+ if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee);
149
+ if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
150
+ if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
151
+ if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
152
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
153
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
153
154
  }
154
155
  switchToFull() {
155
156
  this.mode = MODE.FULL;
@@ -197,6 +198,11 @@ export class Aggregate extends AggregateBase {
197
198
  return;
198
199
  }
199
200
  }
201
+ if (this.recorder?.getEvents().type === 'preloaded') {
202
+ this.prepUtils().then(() => {
203
+ this.scheduler.runHarvest();
204
+ });
205
+ }
200
206
  if (!this.recorder) {
201
207
  try {
202
208
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
@@ -315,6 +321,7 @@ export class Aggregate extends AggregateBase {
315
321
  const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
316
322
  const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
317
323
  const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
324
+ const agentMetadata = agentRuntime.appMetadata?.agents?.[0] || {};
318
325
  return {
319
326
  qs: {
320
327
  browser_monitoring_key: info.licenseKey,
@@ -327,6 +334,9 @@ export class Aggregate extends AggregateBase {
327
334
  ...(!!this.gzipper && !!this.u8 && {
328
335
  content_encoding: 'gzip'
329
336
  }),
337
+ ...(agentMetadata.entityGuid && {
338
+ entityGuid: agentMetadata.entityGuid
339
+ }),
330
340
  'replay.firstTimestamp': firstTimestamp,
331
341
  'replay.firstTimestampOffset': firstTimestamp - agentOffset,
332
342
  'replay.lastTimestamp': lastTimestamp,
@@ -389,7 +399,7 @@ export class Aggregate extends AggregateBase {
389
399
  });
390
400
  this.recorder?.clearTimestamps?.();
391
401
  this.ee.emit('REPLAY_ABORTED');
392
- this.recorder?.clearBuffer?.();
402
+ while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
393
403
  }
394
404
  syncWithSessionManager() {
395
405
  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
  });
@@ -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
+ }
@@ -116,7 +116,10 @@ export class Aggregate extends AggregateBase {
116
116
  if (!sessionEntity) {
117
117
  // Since session manager isn't around, do the old Trace behavior of waiting for RUM response to decide feature activation.
118
118
  this.isStandalone = true;
119
- registerHandler('rumresp-stn', on => controlTraceOp(on), this.featureName, this.ee);
119
+ this.waitForFlags(['stn']).then(_ref => {
120
+ let [on] = _ref;
121
+ return controlTraceOp(on);
122
+ }, this.featureName, this.ee);
120
123
  } else {
121
124
  registerHandler('errorAgg', () => {
122
125
  seenAnError = true;
@@ -133,8 +136,8 @@ export class Aggregate extends AggregateBase {
133
136
  };
134
137
 
135
138
  // CAUTION: everything inside this promise runs post-load; event subscribers must be pre-load aka synchronous with constructor
136
- this.waitForFlags(['stn', 'sr']).then(async _ref => {
137
- let [traceOn, replayOn] = _ref;
139
+ this.waitForFlags(['stn', 'sr']).then(async _ref2 => {
140
+ let [traceOn, replayOn] = _ref2;
138
141
  if (!replayOn) {
139
142
  // When sr = 0 from BCS, also do the old Trace behavior:
140
143
  this.isStandalone = true;
@@ -164,7 +167,7 @@ export class Aggregate extends AggregateBase {
164
167
  if (replayMode === MODE.OFF) this.isStandalone = true; // without SR, Traces are still subject to old harvest limits
165
168
 
166
169
  let startingMode;
167
- if (traceOn === true) {
170
+ if (traceOn) {
168
171
  // CASE: both trace (entitlement+sampling) & replay (entitlement) flags are true from RUM
169
172
  startingMode = MODE.FULL; // always full capture regardless of replay sampling decisions
170
173
  } else {
@@ -252,8 +255,8 @@ export class Aggregate extends AggregateBase {
252
255
  }
253
256
  if (result.sent && result.retry && this.sentTrace) {
254
257
  // merge previous trace back into buffer to retry for next harvest
255
- Object.entries(this.sentTrace).forEach(_ref2 => {
256
- let [name, listOfSTNodes] = _ref2;
258
+ Object.entries(this.sentTrace).forEach(_ref3 => {
259
+ let [name, listOfSTNodes] = _ref3;
257
260
  if (this.nodeCount >= this.maxNodesPerHarvest) return;
258
261
  this.nodeCount += listOfSTNodes.length;
259
262
  this.trace[name] = this.trace[name] ? listOfSTNodes.concat(this.trace[name]) : listOfSTNodes;
@@ -499,8 +502,8 @@ export class Aggregate extends AggregateBase {
499
502
  this.storeResources(window.performance.getEntriesByType('resource'));
500
503
  }
501
504
  let earliestTimeStamp = Infinity;
502
- const stns = Object.entries(this.trace).flatMap(_ref3 => {
503
- let [name, listOfSTNodes] = _ref3;
505
+ const stns = Object.entries(this.trace).flatMap(_ref4 => {
506
+ let [name, listOfSTNodes] = _ref4;
504
507
  // basically take the "this.trace" map-obj and concat all the list-type values
505
508
  const oldestNodeTS = listOfSTNodes.reduce((acc, next) => !acc || next.s < acc ? next.s : acc, undefined);
506
509
  if (oldestNodeTS < earliestTimeStamp) earliestTimeStamp = oldestNodeTS;
@@ -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
+ }