@newrelic/browser-agent 1.232.1 → 1.233.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 (233) hide show
  1. package/dist/cjs/cdn/polyfills.js +5 -2
  2. package/dist/cjs/common/config/state/configurable.js +15 -26
  3. package/dist/cjs/common/config/state/info.js +1 -1
  4. package/dist/cjs/common/config/state/init.js +101 -56
  5. package/dist/cjs/common/config/state/loader-config.js +1 -1
  6. package/dist/cjs/common/config/state/runtime.js +1 -5
  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/drain/drain.js +1 -1
  10. package/dist/cjs/common/harvest/harvest-scheduler.js +30 -10
  11. package/dist/cjs/common/harvest/harvest.js +119 -55
  12. package/dist/cjs/common/session/session-entity.js +35 -22
  13. package/dist/cjs/common/session/session-entity.test.js +73 -49
  14. package/dist/cjs/common/timer/interaction-timer.js +9 -12
  15. package/dist/cjs/common/url/protocol.test.js +0 -1
  16. package/dist/cjs/common/util/feature-flags.js +2 -1
  17. package/dist/cjs/common/util/submit-data.js +57 -18
  18. package/dist/cjs/common/wrap/wrap-fetch.js +1 -1
  19. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  20. package/dist/cjs/common/wrap/wrap-promise.js +1 -1
  21. package/dist/cjs/features/ajax/aggregate/index.js +2 -2
  22. package/dist/cjs/features/jserrors/aggregate/index.js +7 -5
  23. package/dist/cjs/features/metrics/aggregate/framework-detection.js +67 -0
  24. package/dist/cjs/features/metrics/aggregate/framework-detection.test.js +137 -0
  25. package/dist/cjs/features/metrics/aggregate/index.js +7 -3
  26. package/dist/cjs/features/metrics/aggregate/polyfill-detection.es5.js +14 -0
  27. package/dist/cjs/features/metrics/aggregate/polyfill-detection.es5.test.js +17 -0
  28. package/dist/cjs/features/metrics/aggregate/polyfill-detection.js +53 -0
  29. package/dist/cjs/features/metrics/aggregate/polyfill-detection.test.js +165 -0
  30. package/dist/cjs/features/page_action/aggregate/index.js +2 -2
  31. package/dist/cjs/features/page_view_event/aggregate/index.js +6 -3
  32. package/dist/cjs/features/page_view_timing/aggregate/index.js +2 -2
  33. package/dist/cjs/features/session_replay/aggregate/index.js +333 -0
  34. package/dist/cjs/features/session_replay/constants.js +9 -0
  35. package/dist/cjs/features/session_replay/index.js +12 -0
  36. package/dist/cjs/features/session_replay/instrument/index.js +29 -0
  37. package/dist/cjs/features/session_trace/aggregate/index.js +163 -162
  38. package/dist/cjs/features/session_trace/constants.js +2 -9
  39. package/dist/cjs/features/session_trace/instrument/index.js +24 -66
  40. package/dist/cjs/features/spa/aggregate/index.js +2 -2
  41. package/dist/cjs/features/utils/agent-session.js +1 -2
  42. package/dist/cjs/features/utils/aggregate-base.js +64 -0
  43. package/dist/cjs/features/utils/feature-base.js +0 -31
  44. package/dist/cjs/features/utils/handler-cache.js +3 -4
  45. package/dist/cjs/features/utils/instrument-base.js +42 -10
  46. package/dist/cjs/features/utils/{lazy-loader.js → lazy-feature-loader.js} +4 -2
  47. package/dist/cjs/loaders/agent.js +1 -1
  48. package/dist/cjs/loaders/api/apiAsync.js +3 -1
  49. package/dist/cjs/loaders/configure/configure.js +3 -3
  50. package/dist/cjs/loaders/features/featureDependencies.js +0 -12
  51. package/dist/cjs/loaders/features/features.js +3 -1
  52. package/dist/cjs/loaders/micro-agent.js +6 -6
  53. package/dist/esm/cdn/polyfills.js +5 -2
  54. package/dist/esm/common/config/state/configurable.js +14 -24
  55. package/dist/esm/common/config/state/info.js +2 -2
  56. package/dist/esm/common/config/state/init.js +102 -57
  57. package/dist/esm/common/config/state/loader-config.js +2 -2
  58. package/dist/esm/common/config/state/runtime.js +2 -4
  59. package/dist/esm/common/constants/env.cdn.js +1 -1
  60. package/dist/esm/common/constants/env.npm.js +1 -1
  61. package/dist/esm/common/drain/drain.js +1 -1
  62. package/dist/esm/common/harvest/harvest-scheduler.js +30 -10
  63. package/dist/esm/common/harvest/harvest.js +121 -56
  64. package/dist/esm/common/session/session-entity.js +35 -22
  65. package/dist/esm/common/session/session-entity.test.js +73 -49
  66. package/dist/esm/common/timer/interaction-timer.js +9 -12
  67. package/dist/esm/common/url/protocol.test.js +0 -1
  68. package/dist/esm/common/util/feature-flags.js +2 -1
  69. package/dist/esm/common/util/submit-data.js +57 -18
  70. package/dist/esm/common/wrap/wrap-fetch.js +1 -1
  71. package/dist/esm/common/wrap/wrap-function.js +1 -1
  72. package/dist/esm/common/wrap/wrap-promise.js +1 -1
  73. package/dist/esm/features/ajax/aggregate/index.js +2 -2
  74. package/dist/esm/features/jserrors/aggregate/index.js +7 -5
  75. package/dist/esm/features/metrics/aggregate/framework-detection.js +61 -0
  76. package/dist/esm/features/metrics/aggregate/framework-detection.test.js +133 -0
  77. package/dist/esm/features/metrics/aggregate/index.js +7 -3
  78. package/dist/esm/features/metrics/aggregate/polyfill-detection.es5.js +8 -0
  79. package/dist/esm/features/metrics/aggregate/polyfill-detection.es5.test.js +15 -0
  80. package/dist/esm/features/metrics/aggregate/polyfill-detection.js +47 -0
  81. package/dist/esm/features/metrics/aggregate/polyfill-detection.test.js +163 -0
  82. package/dist/esm/features/page_action/aggregate/index.js +2 -2
  83. package/dist/esm/features/page_view_event/aggregate/index.js +6 -3
  84. package/dist/esm/features/page_view_timing/aggregate/index.js +2 -2
  85. package/dist/esm/features/session_replay/aggregate/index.js +327 -0
  86. package/dist/esm/features/session_replay/constants.js +2 -0
  87. package/dist/esm/features/session_replay/index.js +12 -0
  88. package/dist/esm/features/session_replay/instrument/index.js +21 -0
  89. package/dist/esm/features/session_trace/aggregate/index.js +163 -162
  90. package/dist/esm/features/session_trace/constants.js +1 -5
  91. package/dist/esm/features/session_trace/instrument/index.js +24 -66
  92. package/dist/esm/features/spa/aggregate/index.js +2 -2
  93. package/dist/esm/features/utils/agent-session.js +1 -2
  94. package/dist/esm/features/utils/aggregate-base.js +57 -0
  95. package/dist/esm/features/utils/feature-base.js +1 -32
  96. package/dist/esm/features/utils/handler-cache.js +3 -4
  97. package/dist/esm/features/utils/instrument-base.js +42 -10
  98. package/dist/esm/features/utils/{lazy-loader.js → lazy-feature-loader.js} +3 -1
  99. package/dist/esm/loaders/agent.js +1 -1
  100. package/dist/esm/loaders/api/apiAsync.js +3 -1
  101. package/dist/esm/loaders/configure/configure.js +3 -3
  102. package/dist/esm/loaders/features/featureDependencies.js +0 -11
  103. package/dist/esm/loaders/features/features.js +3 -1
  104. package/dist/esm/loaders/micro-agent.js +6 -6
  105. package/dist/types/common/config/state/configurable.d.ts +1 -3
  106. package/dist/types/common/config/state/configurable.d.ts.map +1 -1
  107. package/dist/types/common/config/state/init.d.ts.map +1 -1
  108. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  109. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  110. package/dist/types/common/harvest/harvest.d.ts +37 -34
  111. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  112. package/dist/types/common/session/session-entity.d.ts +6 -3
  113. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  114. package/dist/types/common/timer/interaction-timer.d.ts +2 -1
  115. package/dist/types/common/timer/interaction-timer.d.ts.map +1 -1
  116. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  117. package/dist/types/common/util/submit-data.d.ts +40 -14
  118. package/dist/types/common/util/submit-data.d.ts.map +1 -1
  119. package/dist/types/features/ajax/aggregate/index.d.ts +2 -2
  120. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  121. package/dist/types/features/jserrors/aggregate/index.d.ts +2 -2
  122. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  123. package/dist/types/features/metrics/aggregate/framework-detection.d.ts.map +1 -0
  124. package/dist/types/features/metrics/aggregate/index.d.ts +2 -2
  125. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  126. package/dist/types/features/metrics/aggregate/polyfill-detection.d.ts +6 -0
  127. package/dist/types/features/metrics/aggregate/polyfill-detection.d.ts.map +1 -0
  128. package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts +7 -0
  129. package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts.map +1 -0
  130. package/dist/types/features/page_action/aggregate/index.d.ts +2 -2
  131. package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
  132. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -2
  133. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  134. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -2
  135. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  136. package/dist/types/features/session_replay/aggregate/index.d.ts +96 -0
  137. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -0
  138. package/dist/types/features/session_replay/constants.d.ts +2 -0
  139. package/dist/types/features/session_replay/constants.d.ts.map +1 -0
  140. package/dist/types/features/session_replay/index.d.ts +2 -0
  141. package/dist/types/features/session_replay/index.d.ts.map +1 -0
  142. package/dist/types/features/session_replay/instrument/index.d.ts +6 -0
  143. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -0
  144. package/dist/types/features/session_trace/aggregate/index.d.ts +8 -57
  145. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  146. package/dist/types/features/session_trace/constants.d.ts +0 -3
  147. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  148. package/dist/types/features/session_trace/instrument/index.d.ts +1 -3
  149. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  150. package/dist/types/features/spa/aggregate/index.d.ts +2 -2
  151. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  152. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  153. package/dist/types/features/utils/aggregate-base.d.ts +11 -0
  154. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -0
  155. package/dist/types/features/utils/feature-base.d.ts +0 -5
  156. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  157. package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
  158. package/dist/types/features/utils/instrument-base.d.ts +3 -1
  159. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  160. package/dist/types/features/utils/{lazy-loader.d.ts → lazy-feature-loader.d.ts} +2 -2
  161. package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -0
  162. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  163. package/dist/types/loaders/features/featureDependencies.d.ts +0 -1
  164. package/dist/types/loaders/features/featureDependencies.d.ts.map +1 -1
  165. package/dist/types/loaders/features/features.d.ts +1 -0
  166. package/dist/types/loaders/features/features.d.ts.map +1 -1
  167. package/package.json +28 -19
  168. package/src/cdn/polyfills.js +4 -1
  169. package/src/common/config/state/configurable.js +18 -24
  170. package/src/common/config/state/info.js +2 -2
  171. package/src/common/config/state/init.js +62 -28
  172. package/src/common/config/state/loader-config.js +2 -2
  173. package/src/common/config/state/runtime.js +2 -4
  174. package/src/common/drain/drain.js +1 -1
  175. package/src/common/harvest/harvest-scheduler.js +35 -10
  176. package/src/common/harvest/harvest.js +73 -50
  177. package/src/common/session/session-entity.js +34 -23
  178. package/src/common/session/session-entity.test.js +57 -51
  179. package/src/common/timer/interaction-timer.js +9 -12
  180. package/src/common/url/protocol.test.js +0 -1
  181. package/src/common/util/feature-flags.js +2 -2
  182. package/src/common/util/submit-data.js +28 -17
  183. package/src/common/wrap/wrap-fetch.js +1 -1
  184. package/src/common/wrap/wrap-function.js +1 -1
  185. package/src/common/wrap/wrap-promise.js +1 -1
  186. package/src/features/ajax/aggregate/index.js +2 -2
  187. package/src/features/jserrors/aggregate/index.js +7 -5
  188. package/src/features/metrics/aggregate/framework-detection.js +73 -0
  189. package/src/features/metrics/aggregate/framework-detection.test.js +201 -0
  190. package/src/features/metrics/aggregate/index.js +8 -3
  191. package/src/features/metrics/aggregate/polyfill-detection.es5.js +9 -0
  192. package/src/features/metrics/aggregate/polyfill-detection.es5.test.js +16 -0
  193. package/src/features/metrics/aggregate/polyfill-detection.js +48 -0
  194. package/src/features/metrics/aggregate/polyfill-detection.test.js +163 -0
  195. package/src/features/page_action/aggregate/index.js +2 -2
  196. package/src/features/page_view_event/aggregate/index.js +5 -5
  197. package/src/features/page_view_timing/aggregate/index.js +2 -2
  198. package/src/features/session_replay/aggregate/index.js +314 -0
  199. package/src/features/session_replay/constants.js +3 -0
  200. package/src/features/session_replay/index.js +12 -0
  201. package/src/features/session_replay/instrument/index.js +22 -0
  202. package/src/features/session_trace/aggregate/index.js +148 -187
  203. package/src/features/session_trace/constants.js +0 -4
  204. package/src/features/session_trace/instrument/index.js +17 -69
  205. package/src/features/spa/aggregate/index.js +2 -2
  206. package/src/features/utils/agent-session.js +1 -2
  207. package/src/features/utils/aggregate-base.js +51 -0
  208. package/src/features/utils/feature-base.js +1 -31
  209. package/src/features/utils/handler-cache.js +3 -4
  210. package/src/features/utils/instrument-base.js +40 -8
  211. package/src/features/utils/{lazy-loader.js → lazy-feature-loader.js} +3 -1
  212. package/src/loaders/agent.js +1 -1
  213. package/src/loaders/api/apiAsync.js +1 -1
  214. package/src/loaders/configure/configure.js +4 -3
  215. package/src/loaders/features/featureDependencies.js +0 -12
  216. package/src/loaders/features/features.js +3 -1
  217. package/src/loaders/micro-agent.js +4 -4
  218. package/dist/cjs/common/metrics/framework-detection.js +0 -72
  219. package/dist/cjs/common/util/user-agent.js +0 -57
  220. package/dist/cjs/common/window/supports-performance-observer.js +0 -15
  221. package/dist/esm/common/metrics/framework-detection.js +0 -66
  222. package/dist/esm/common/util/user-agent.js +0 -48
  223. package/dist/esm/common/window/supports-performance-observer.js +0 -9
  224. package/dist/types/common/metrics/framework-detection.d.ts.map +0 -1
  225. package/dist/types/common/util/user-agent.d.ts +0 -5
  226. package/dist/types/common/util/user-agent.d.ts.map +0 -1
  227. package/dist/types/common/window/supports-performance-observer.d.ts +0 -2
  228. package/dist/types/common/window/supports-performance-observer.d.ts.map +0 -1
  229. package/dist/types/features/utils/lazy-loader.d.ts.map +0 -1
  230. package/src/common/metrics/framework-detection.js +0 -71
  231. package/src/common/util/user-agent.js +0 -56
  232. package/src/common/window/supports-performance-observer.js +0 -10
  233. /package/dist/types/{common/metrics → features/metrics/aggregate}/framework-detection.d.ts +0 -0
@@ -0,0 +1,327 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * @file Records, aggregates, and harvests session replay data.
7
+ *
8
+ * NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
9
+ * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
10
+ * functionality is validated and a full user experience is curated.
11
+ */
12
+
13
+ import { drain } from '../../../common/drain/drain';
14
+ import { registerHandler } from '../../../common/event-emitter/register-handler';
15
+ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
16
+ import { FEATURE_NAME } from '../constants';
17
+ import { stringify } from '../../../common/util/stringify';
18
+ import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
19
+ import { SESSION_EVENTS } from '../../../common/session/session-entity';
20
+ import { AggregateBase } from '../../utils/aggregate-base';
21
+
22
+ // would be better to get this dynamically in some way
23
+ export const RRWEB_VERSION = '2.0.0-alpha.8';
24
+ let recorder, gzipper, u8;
25
+
26
+ /** The "mode" with which the session replay is recording */
27
+ const MODE = {
28
+ OFF: 0,
29
+ FULL: 1,
30
+ ERROR: 2
31
+ };
32
+ /** Vortex caps payload sizes at 1MB */
33
+ const MAX_PAYLOAD_SIZE = 1000000;
34
+ /** Unloading caps around 64kb */
35
+ const IDEAL_PAYLOAD_SIZE = 64000;
36
+ /** Interval between forcing new full snapshots in "error" mode */
37
+ const CHECKOUT_MS = 30000;
38
+ export class Aggregate extends AggregateBase {
39
+ static featureName = FEATURE_NAME;
40
+ constructor(agentIdentifier, aggregator) {
41
+ super(agentIdentifier, aggregator, FEATURE_NAME);
42
+
43
+ /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
44
+ this.events = [];
45
+ /** The interval to harvest at. This gets overridden if the size of the payload exceeds certain thresholds */
46
+ this.harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'session_replay.harvestTimeSeconds') || 60;
47
+ /** Set once the recorder has fully initialized after flag checks and sampling */
48
+ this.initialized = false;
49
+ /** Set once an error has been detected on the page. */
50
+ this.errorNoticed = false;
51
+ /** The "mode" to record in. Defaults to "OFF" until flags and sampling are checked. See "MODE" constant. */
52
+ this.mode = MODE.OFF;
53
+ /** Set once the feature has been "aborted" to prevent other side-effects from continuing */
54
+ this.blocked = false;
55
+
56
+ /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
57
+ this.isFirstChunk = false;
58
+ /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
59
+ * -- When the recording library begins recording, it starts by taking a DOM snapshot
60
+ * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
61
+ */
62
+ this.hasSnapshot = false;
63
+ /** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
64
+ this.hasError = false;
65
+
66
+ /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
67
+ this.payloadBytesEstimation = 0;
68
+
69
+ /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
70
+ this.stopRecording = () => {/* no-op until set by rrweb initializer */};
71
+
72
+ // 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.
73
+ this.ee.on('session-reset', () => {
74
+ this.abort();
75
+ });
76
+
77
+ // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
78
+ this.ee.on(SESSION_EVENTS.PAUSE, () => {
79
+ this.stopRecording();
80
+ });
81
+ // 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.
82
+ this.ee.on(SESSION_EVENTS.RESUME, () => {
83
+ if (!this.initialized || this.mode === MODE.OFF) return;
84
+ this.startRecording();
85
+ this.takeFullSnapshot();
86
+ });
87
+
88
+ // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
89
+ this.scheduler = new HarvestScheduler('blob', {
90
+ onFinished: this.onHarvestFinished.bind(this),
91
+ retryDelay: this.harvestTimeSeconds,
92
+ getPayload: this.prepareHarvest.bind(this),
93
+ // TODO -- this stuff needs a better way to be handled
94
+ includeBaseParams: false,
95
+ customUrl: 'https://vortex-alb.stg-single-tooth.cell.us.nr-data.net/blob',
96
+ raw: true,
97
+ gzip: true
98
+ }, this);
99
+
100
+ // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
101
+ // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
102
+ registerHandler('errorAgg', e => {
103
+ this.hasError = true;
104
+ // run once
105
+ if (this.mode === MODE.ERROR) {
106
+ this.mode = MODE.FULL;
107
+ // if the error was noticed AFTER the recorder was already imported....
108
+ if (recorder && this.initialized) {
109
+ this.stopRecording();
110
+ this.startRecording();
111
+ this.scheduler.startTimer(this.harvestTimeSeconds);
112
+ const {
113
+ session
114
+ } = getRuntime(this.agentIdentifier);
115
+ session.state.sessionReplay = this.mode;
116
+ }
117
+ }
118
+ }, this.featureName, this.ee);
119
+
120
+ // new handler for waiting for multiple flags. will be useful if/when backend designs multiple flags, or for evaluating multiple feature flags simultaneously (stn vs sr)
121
+ this.waitForFlags(['sr']).then(_ref => {
122
+ let [{
123
+ value
124
+ }] = _ref;
125
+ this.initializeRecording(value, Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.errorSampleRate'), Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.sampleRate'));
126
+ });
127
+ drain(this.agentIdentifier, this.featureName);
128
+ }
129
+
130
+ /**
131
+ * Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
132
+ * @param {boolean} entitlements - the true/false state of the "sr" flag from RUM response
133
+ * @param {boolean} errorSample - the true/false state of the error sampling decision
134
+ * @param {boolean} fullSample - the true/false state of the full sampling decision
135
+ * @returns {void}
136
+ */
137
+ async initializeRecording(entitlements, errorSample, fullSample) {
138
+ this.initialized = true;
139
+ if (!entitlements) return;
140
+ const {
141
+ session
142
+ } = getRuntime(this.agentIdentifier);
143
+ // if theres an existing session replay in progress, there's no need to sample, just check the entitlements response
144
+ // if not, these sample flags need to be checked
145
+ // if this isnt the FIRST load of a session AND
146
+ // we are not actively recording SR... DO NOT import or run the recording library
147
+ // session replay samples can only be decided on the first load of a session
148
+ // session replays can continue if already in progress
149
+ if (!session.isNew) {
150
+ // inherit the mode of the existing session
151
+ this.mode = session.state.sessionReplay;
152
+ } else {
153
+ // The session is new... determine the mode the new session should start in
154
+ if (fullSample) this.mode = MODE.FULL; // full mode has precedence over error mode
155
+ else if (errorSample) this.mode = MODE.ERROR;
156
+ // If neither are selected, then don't record (early return)
157
+ return;
158
+ }
159
+
160
+ // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
161
+ // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
162
+ // If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
163
+ if (this.mode === MODE.FULL) {
164
+ // We only report (harvest) in FULL mode
165
+ this.scheduler.startTimer(this.harvestTimeSeconds);
166
+ }
167
+ // We record in FULL or ERROR mode
168
+
169
+ recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
170
+ this.startRecording();
171
+ const {
172
+ gzipSync,
173
+ strToU8
174
+ } = await import( /* webpackChunkName: "compressor" */'fflate');
175
+ gzipper = gzipSync;
176
+ u8 = strToU8;
177
+ this.isFirstChunk = !!session.isNew;
178
+ session.state.sessionReplay = this.mode;
179
+ }
180
+ prepareHarvest(options) {
181
+ if (this.events.length === 0) return;
182
+ const payload = this.getHarvestContents();
183
+ try {
184
+ payload.body = gzipper(u8(stringify(payload.body)));
185
+ this.scheduler.opts.gzip = true;
186
+ } catch (err) {
187
+ // failed to gzip
188
+ this.scheduler.opts.gzip = false;
189
+ }
190
+ // TODO -- Gracefully handle the buffer for retries.
191
+ this.clearBuffer();
192
+ return [payload];
193
+ }
194
+ getHarvestContents() {
195
+ const agentRuntime = getRuntime(this.agentIdentifier);
196
+ const info = getInfo(this.agentIdentifier);
197
+ return {
198
+ qs: {
199
+ protocol_version: '0'
200
+ },
201
+ body: {
202
+ type: 'SessionReplay',
203
+ appId: Number(info.applicationID),
204
+ timestamp: Date.now(),
205
+ blob: JSON.stringify(this.events),
206
+ // this needs to be a stringified JSON array of rrweb nodes
207
+ attributes: {
208
+ session: agentRuntime.session.state.value,
209
+ hasSnapshot: this.hasSnapshot,
210
+ hasError: this.hasError,
211
+ agentVersion: agentRuntime.version,
212
+ isFirstChunk: this.isFirstChunk,
213
+ 'nr.rrweb.version': RRWEB_VERSION
214
+ }
215
+ }
216
+ };
217
+ }
218
+ onHarvestFinished(result) {
219
+ // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
220
+ if (result.status === 429) {
221
+ this.abort();
222
+ }
223
+ if (this.blocked) this.scheduler.stopTimer(true);
224
+ }
225
+
226
+ /** Clears the buffer (this.events), and resets all payload metadata properties */
227
+ clearBuffer() {
228
+ this.events = [];
229
+ this.isFirstChunk = false;
230
+ this.hasSnapshot = false;
231
+ this.hasError = false;
232
+ this.payloadBytesEstimation = 0;
233
+ }
234
+
235
+ /** Begin recording using configured recording lib */
236
+ startRecording() {
237
+ if (!recorder) {
238
+ warn('Recording library was never imported');
239
+ return this.abort();
240
+ }
241
+ const {
242
+ blockClass,
243
+ ignoreClass,
244
+ maskTextClass,
245
+ blockSelector,
246
+ maskInputOptions,
247
+ maskTextSelector,
248
+ maskAllInputs
249
+ } = getConfigurationValue(this.agentIdentifier, 'session_replay');
250
+ this.hasSnapshot = true;
251
+ // set up rrweb configurations for maximum privacy --
252
+ // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
253
+ this.stopRecording = recorder({
254
+ emit: this.store.bind(this),
255
+ blockClass,
256
+ ignoreClass,
257
+ maskTextClass,
258
+ blockSelector,
259
+ maskInputOptions,
260
+ maskTextSelector,
261
+ maskAllInputs,
262
+ ...(this.mode === MODE.ERROR && {
263
+ checkoutEveryNms: CHECKOUT_MS
264
+ })
265
+ });
266
+ }
267
+
268
+ /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
269
+ store(event, isCheckout) {
270
+ if (this.blocked) return;
271
+ const eventBytes = stringify(event).length;
272
+ /** The estimated size of the payload after compression */
273
+ const payloadSize = this.getPayloadSize(eventBytes);
274
+ // Vortex will block payloads at a certain size, we might as well not send.
275
+ if (payloadSize > MAX_PAYLOAD_SIZE) {
276
+ return this.abort();
277
+ }
278
+ // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
279
+ // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
280
+ // each time we see a new checkout, we can drop the old data.
281
+ if (this.mode === MODE.ERROR && isCheckout) {
282
+ // we are still waiting for an error to throw, so keep wiping the buffer over time
283
+ this.clearBuffer();
284
+ }
285
+ this.events.push(event);
286
+ this.payloadBytesEstimation += eventBytes;
287
+
288
+ // We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
289
+ // it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
290
+ if (payloadSize > IDEAL_PAYLOAD_SIZE) {
291
+ // if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
292
+ this.scheduler.runHarvest();
293
+ }
294
+ }
295
+
296
+ /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
297
+ takeFullSnapshot() {
298
+ if (!recorder) return;
299
+ recorder.takeFullSnapshot();
300
+ this.hasSnapshot = true;
301
+ }
302
+
303
+ /** Estimate the payload size */
304
+ getPayloadSize() {
305
+ let newBytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
306
+ // the 1KB gives us some padding for the other metadata
307
+ return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000;
308
+ }
309
+
310
+ /** Abort the feature, once aborted it will not resume */
311
+ abort() {
312
+ this.blocked = true;
313
+ this.stopRecording();
314
+ const {
315
+ session
316
+ } = getRuntime(this.agentIdentifier);
317
+ session.state.sessionReplay = this.mode;
318
+ }
319
+
320
+ /** Extensive research has yielded about an 88% compression factor on these payloads.
321
+ * This is an estimation using that factor as to not cause performance issues while evaluating
322
+ * https://staging.onenr.io/037jbJWxbjy
323
+ * */
324
+ estimateCompression(data) {
325
+ return data * 0.12;
326
+ }
327
+ }
@@ -0,0 +1,2 @@
1
+ import { FEATURE_NAMES } from '../../loaders/features/features';
2
+ export const FEATURE_NAME = FEATURE_NAMES.sessionReplay;
@@ -0,0 +1,12 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * @file The top-level entry point for the eventual Session Replay feature.
7
+ *
8
+ * NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
9
+ * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
10
+ * functionality is validated and a full user experience is curated.
11
+ */
12
+ export { Instrument as SessionReplay } from './instrument/index';
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * @file Primes the Session Replay feature for lazy loading.
7
+ *
8
+ * NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
9
+ * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
10
+ * functionality is validated and a full user experience is curated.
11
+ */
12
+ import { InstrumentBase } from '../../utils/instrument-base';
13
+ import { FEATURE_NAME } from '../constants';
14
+ export class Instrument extends InstrumentBase {
15
+ static featureName = FEATURE_NAME;
16
+ constructor(agentIdentifier, aggregator) {
17
+ let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
18
+ super(agentIdentifier, aggregator, FEATURE_NAME, auto);
19
+ this.importAggregator();
20
+ }
21
+ }