@newrelic/browser-agent 1.249.0 → 1.251.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 (143) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/cjs/common/config/state/init.js +1 -3
  3. package/dist/cjs/common/config/state/originals.js +1 -2
  4. package/dist/cjs/common/constants/env.cdn.js +4 -8
  5. package/dist/cjs/common/constants/env.js +4 -8
  6. package/dist/cjs/common/constants/env.npm.js +4 -8
  7. package/dist/cjs/common/constants/runtime.js +13 -24
  8. package/dist/cjs/common/constants/shared-channel.js +2 -3
  9. package/dist/cjs/common/event-emitter/contextual-ee.js +2 -4
  10. package/dist/cjs/common/event-emitter/handle.js +1 -2
  11. package/dist/cjs/common/harvest/harvest-scheduler.js +4 -4
  12. package/dist/cjs/common/harvest/harvest.js +6 -5
  13. package/dist/cjs/common/harvest/types.js +1 -2
  14. package/dist/cjs/common/ids/bundle-id.js +1 -2
  15. package/dist/cjs/common/ids/unique-id.js +1 -1
  16. package/dist/cjs/common/session/constants.js +19 -7
  17. package/dist/cjs/common/session/session-entity.js +8 -26
  18. package/dist/cjs/common/timer/interaction-timer.js +0 -1
  19. package/dist/cjs/common/timing/nav-timing.js +1 -2
  20. package/dist/cjs/common/url/encode.js +2 -0
  21. package/dist/cjs/common/util/feature-flags.js +1 -2
  22. package/dist/cjs/common/vitals/constants.js +2 -3
  23. package/dist/cjs/common/vitals/cumulative-layout-shift.js +1 -2
  24. package/dist/cjs/common/vitals/first-contentful-paint.js +1 -2
  25. package/dist/cjs/common/vitals/first-input-delay.js +1 -2
  26. package/dist/cjs/common/vitals/first-paint.js +1 -2
  27. package/dist/cjs/common/vitals/interaction-to-next-paint.js +1 -2
  28. package/dist/cjs/common/vitals/largest-contentful-paint.js +1 -2
  29. package/dist/cjs/common/vitals/long-task.js +1 -3
  30. package/dist/cjs/common/vitals/time-to-first-byte.js +1 -2
  31. package/dist/cjs/common/window/nreum.js +1 -2
  32. package/dist/cjs/common/wrap/wrap-function.js +2 -4
  33. package/dist/cjs/features/ajax/aggregate/index.js +0 -2
  34. package/dist/cjs/features/ajax/constants.js +1 -2
  35. package/dist/cjs/features/jserrors/aggregate/index.js +0 -1
  36. package/dist/cjs/features/jserrors/aggregate/string-hash-code.js +0 -1
  37. package/dist/cjs/features/jserrors/constants.js +1 -2
  38. package/dist/cjs/features/metrics/aggregate/index.js +3 -1
  39. package/dist/cjs/features/metrics/constants.js +5 -10
  40. package/dist/cjs/features/page_action/constants.js +1 -2
  41. package/dist/cjs/features/page_view_event/aggregate/index.js +2 -2
  42. package/dist/cjs/features/page_view_event/constants.js +1 -2
  43. package/dist/cjs/features/page_view_event/instrument/index.js +2 -2
  44. package/dist/cjs/features/page_view_timing/constants.js +1 -2
  45. package/dist/cjs/features/session_replay/aggregate/index.js +128 -289
  46. package/dist/cjs/features/session_replay/constants.js +50 -3
  47. package/dist/cjs/features/session_replay/instrument/index.js +30 -8
  48. package/dist/cjs/features/session_replay/shared/recorder-events.js +33 -0
  49. package/dist/cjs/features/session_replay/shared/recorder.js +201 -0
  50. package/dist/cjs/features/session_replay/{replay-mode.js → shared/replay-mode.js} +5 -5
  51. package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +94 -0
  52. package/dist/cjs/features/session_trace/aggregate/index.js +25 -27
  53. package/dist/cjs/features/session_trace/constants.js +8 -16
  54. package/dist/cjs/features/session_trace/instrument/index.js +2 -2
  55. package/dist/cjs/features/spa/aggregate/index.js +2 -2
  56. package/dist/cjs/features/spa/aggregate/interaction-node.js +0 -1
  57. package/dist/cjs/features/spa/constants.js +22 -44
  58. package/dist/cjs/features/spa/instrument/index.js +2 -2
  59. package/dist/cjs/features/utils/instrument-base.js +6 -7
  60. package/dist/cjs/features/utils/lazy-feature-loader.js +2 -2
  61. package/dist/cjs/loaders/agent-base.js +61 -15
  62. package/dist/cjs/loaders/agent.js +0 -38
  63. package/dist/cjs/loaders/api/api.js +7 -7
  64. package/dist/cjs/loaders/api/interaction-types.js +1 -2
  65. package/dist/cjs/loaders/features/features.js +3 -5
  66. package/dist/cjs/loaders/micro-agent.js +2 -2
  67. package/dist/esm/common/config/state/init.js +1 -3
  68. package/dist/esm/common/constants/env.cdn.js +1 -1
  69. package/dist/esm/common/constants/env.npm.js +1 -1
  70. package/dist/esm/common/harvest/harvest-scheduler.js +1 -1
  71. package/dist/esm/common/harvest/harvest.js +4 -3
  72. package/dist/esm/common/ids/unique-id.js +1 -1
  73. package/dist/esm/common/session/constants.js +16 -1
  74. package/dist/esm/common/session/session-entity.js +2 -16
  75. package/dist/esm/common/timer/interaction-timer.js +0 -1
  76. package/dist/esm/common/url/encode.js +2 -0
  77. package/dist/esm/common/vitals/long-task.js +0 -1
  78. package/dist/esm/features/ajax/aggregate/index.js +0 -2
  79. package/dist/esm/features/jserrors/aggregate/index.js +0 -1
  80. package/dist/esm/features/jserrors/aggregate/string-hash-code.js +0 -1
  81. package/dist/esm/features/metrics/aggregate/index.js +3 -1
  82. package/dist/esm/features/session_replay/aggregate/index.js +98 -255
  83. package/dist/esm/features/session_replay/constants.js +49 -1
  84. package/dist/esm/features/session_replay/instrument/index.js +24 -1
  85. package/dist/esm/features/session_replay/shared/recorder-events.js +26 -0
  86. package/dist/esm/features/session_replay/shared/recorder.js +194 -0
  87. package/dist/esm/features/session_replay/{replay-mode.js → shared/replay-mode.js} +4 -4
  88. package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +88 -0
  89. package/dist/esm/features/session_trace/aggregate/index.js +2 -4
  90. package/dist/esm/features/spa/aggregate/interaction-node.js +0 -1
  91. package/dist/esm/features/utils/instrument-base.js +0 -1
  92. package/dist/esm/loaders/agent-base.js +62 -15
  93. package/dist/esm/loaders/agent.js +0 -38
  94. package/dist/types/common/harvest/harvest.d.ts +1 -1
  95. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  96. package/dist/types/common/session/constants.d.ts +15 -0
  97. package/dist/types/common/session/session-entity.d.ts +0 -15
  98. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  99. package/dist/types/common/url/encode.d.ts +1 -1
  100. package/dist/types/common/url/encode.d.ts.map +1 -1
  101. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  102. package/dist/types/features/session_replay/aggregate/index.d.ts +7 -63
  103. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  104. package/dist/types/features/session_replay/constants.d.ts +55 -0
  105. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  106. package/dist/types/features/session_replay/instrument/index.d.ts +2 -0
  107. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  108. package/dist/types/features/session_replay/shared/recorder-events.d.ts +23 -0
  109. package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -0
  110. package/dist/types/features/session_replay/shared/recorder.d.ts +53 -0
  111. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -0
  112. package/dist/types/features/session_replay/shared/replay-mode.d.ts.map +1 -0
  113. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts +22 -0
  114. package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -0
  115. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  116. package/dist/types/loaders/agent-base.d.ts +48 -12
  117. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  118. package/dist/types/loaders/agent.d.ts +0 -33
  119. package/dist/types/loaders/agent.d.ts.map +1 -1
  120. package/package.json +49 -49
  121. package/src/common/config/state/init.js +1 -1
  122. package/src/common/harvest/harvest-scheduler.js +1 -1
  123. package/src/common/harvest/harvest.js +4 -3
  124. package/src/common/ids/__mocks__/bundle-id.js +1 -1
  125. package/src/common/ids/__mocks__/unique-id.js +2 -2
  126. package/src/common/ids/unique-id.js +1 -1
  127. package/src/common/session/__mocks__/session-entity.js +0 -6
  128. package/src/common/session/constants.js +18 -0
  129. package/src/common/session/session-entity.js +1 -17
  130. package/src/common/url/encode.js +2 -1
  131. package/src/features/metrics/aggregate/index.js +3 -1
  132. package/src/features/session_replay/aggregate/index.js +91 -246
  133. package/src/features/session_replay/constants.js +45 -0
  134. package/src/features/session_replay/instrument/index.js +18 -1
  135. package/src/features/session_replay/shared/recorder-events.js +27 -0
  136. package/src/features/session_replay/shared/recorder.js +190 -0
  137. package/src/features/session_replay/{replay-mode.js → shared/replay-mode.js} +4 -4
  138. package/src/features/session_replay/shared/stylesheet-evaluator.js +84 -0
  139. package/src/features/session_trace/aggregate/index.js +2 -2
  140. package/src/loaders/agent-base.js +59 -15
  141. package/src/loaders/agent.js +0 -38
  142. package/dist/types/features/session_replay/replay-mode.d.ts.map +0 -1
  143. /package/dist/types/features/session_replay/{replay-mode.d.ts → shared/replay-mode.d.ts} +0 -0
@@ -3,7 +3,54 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.FEATURE_NAME = void 0;
6
+ exports.RRWEB_EVENT_TYPES = exports.QUERY_PARAM_PADDING = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.FEATURE_NAME = exports.CHECKOUT_MS = exports.AVG_COMPRESSION = exports.ABORT_REASONS = void 0;
7
+ var _constants = require("../../common/session/constants");
7
8
  var _features = require("../../loaders/features/features");
8
- const FEATURE_NAME = _features.FEATURE_NAMES.sessionReplay;
9
- exports.FEATURE_NAME = FEATURE_NAME;
9
+ const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.sessionReplay;
10
+ const AVG_COMPRESSION = exports.AVG_COMPRESSION = 0.12;
11
+ const RRWEB_EVENT_TYPES = exports.RRWEB_EVENT_TYPES = {
12
+ DomContentLoaded: 0,
13
+ Load: 1,
14
+ FullSnapshot: 2,
15
+ IncrementalSnapshot: 3,
16
+ Meta: 4,
17
+ Custom: 5
18
+ };
19
+ /** Vortex caps payload sizes at 1MB */
20
+ const MAX_PAYLOAD_SIZE = exports.MAX_PAYLOAD_SIZE = 1000000;
21
+ /** Unloading caps around 64kb */
22
+ const IDEAL_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = 64000;
23
+ /** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
24
+ const CHECKOUT_MS = exports.CHECKOUT_MS = {
25
+ [_constants.MODE.ERROR]: 15000,
26
+ [_constants.MODE.FULL]: 300000,
27
+ [_constants.MODE.OFF]: 0
28
+ };
29
+ const ABORT_REASONS = exports.ABORT_REASONS = {
30
+ RESET: {
31
+ message: 'Session was reset',
32
+ sm: 'Reset'
33
+ },
34
+ IMPORT: {
35
+ message: 'Recorder failed to import',
36
+ sm: 'Import'
37
+ },
38
+ TOO_MANY: {
39
+ message: '429: Too Many Requests',
40
+ sm: 'Too-Many'
41
+ },
42
+ TOO_BIG: {
43
+ message: 'Payload was too large',
44
+ sm: 'Too-Big'
45
+ },
46
+ CROSS_TAB: {
47
+ message: 'Session Entity was set to OFF on another tab',
48
+ sm: 'Cross-Tab'
49
+ },
50
+ ENTITLEMENTS: {
51
+ message: 'Session Replay is not allowed and will not be started',
52
+ sm: 'Entitlement'
53
+ }
54
+ };
55
+ /** Reserved room for query param attrs */
56
+ const QUERY_PARAM_PADDING = exports.QUERY_PARAM_PADDING = 5000;
@@ -4,26 +4,48 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.Instrument = void 0;
7
+ var _constants = require("../../../common/session/constants");
7
8
  var _instrumentBase = require("../../utils/instrument-base");
8
- var _constants = require("../constants");
9
- /*
9
+ var _constants2 = require("../constants");
10
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
11
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /*
10
12
  * Copyright 2023 New Relic Corporation. All rights reserved.
11
13
  * SPDX-License-Identifier: Apache-2.0
12
- */
13
- /**
14
+ */ /**
14
15
  * @file Primes the Session Replay feature for lazy loading.
15
16
  *
16
17
  * NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
17
18
  * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
18
19
  * functionality is validated and a full user experience is curated.
19
20
  */
20
-
21
21
  class Instrument extends _instrumentBase.InstrumentBase {
22
- static featureName = _constants.FEATURE_NAME;
22
+ static featureName = _constants2.FEATURE_NAME;
23
23
  constructor(agentIdentifier, aggregator) {
24
24
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
25
- super(agentIdentifier, aggregator, _constants.FEATURE_NAME, auto);
26
- this.importAggregator();
25
+ super(agentIdentifier, aggregator, _constants2.FEATURE_NAME, auto);
26
+ try {
27
+ const session = JSON.parse(localStorage.getItem('NRBA_SESSION'));
28
+ if (session.sessionReplayMode !== _constants.MODE.OFF) {
29
+ this.#startRecording(session.sessionReplayMode);
30
+ } else {
31
+ this.importAggregator({});
32
+ }
33
+ } catch (err) {
34
+ this.importAggregator({});
35
+ }
36
+ }
37
+ async #startRecording(mode) {
38
+ const {
39
+ Recorder
40
+ } = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'../shared/recorder')));
41
+ this.recorder = new Recorder({
42
+ mode,
43
+ agentIdentifier: this.agentIdentifier
44
+ });
45
+ this.recorder.startRecording();
46
+ this.importAggregator({
47
+ recorder: this.recorder
48
+ });
27
49
  }
28
50
  }
29
51
  exports.Instrument = Instrument;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RecorderEvents = void 0;
7
+ class RecorderEvents {
8
+ constructor() {
9
+ /** The buffer to hold recorder event nodes */
10
+ this.events = [];
11
+ /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
12
+ * cycle timestamps are used as fallbacks if event timestamps cannot be used
13
+ */
14
+ this.cycleTimestamp = Date.now();
15
+ /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
16
+ this.payloadBytesEstimation = 0;
17
+ /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
18
+ * -- When the recording library begins recording, it starts by taking a DOM snapshot
19
+ * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
20
+ */
21
+ this.hasSnapshot = false;
22
+ /** Payload metadata -- Should indicate that the payload being sent has a meta node. The meta node should always precede a snapshot node. */
23
+ this.hasMeta = false;
24
+ /** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
25
+ this.hasError = false;
26
+ /** Payload metadata -- Denotes whether all stylesheet elements were able to be inlined */
27
+ this.inlinedAllStylesheets = true;
28
+ }
29
+ add(event) {
30
+ this.events.push(event);
31
+ }
32
+ }
33
+ exports.RecorderEvents = RecorderEvents;
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Recorder = void 0;
7
+ var _rrweb = require("rrweb");
8
+ var _stringify = require("../../../common/util/stringify");
9
+ var _constants = require("../constants");
10
+ var _config = require("../../../common/config/config");
11
+ var _recorderEvents = require("./recorder-events");
12
+ var _constants2 = require("../../../common/session/constants");
13
+ var _stylesheetEvaluator = require("./stylesheet-evaluator");
14
+ var _handle = require("../../../common/event-emitter/handle");
15
+ var _constants3 = require("../../metrics/constants");
16
+ var _features = require("../../../loaders/features/features");
17
+ class Recorder {
18
+ /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
19
+ #events = new _recorderEvents.RecorderEvents();
20
+ /** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
21
+ #backloggedEvents = new _recorderEvents.RecorderEvents();
22
+ /** array of recorder events -- Will be filled only if forced harvest was triggered and harvester does not exist */
23
+ #preloaded = [new _recorderEvents.RecorderEvents()];
24
+ /** flag that if true, blocks events from being "stored". Only set to true when a full snapshot has incomplete nodes (only stylesheets ATM) */
25
+ #fixing = false;
26
+ constructor(parent) {
27
+ /** True when actively recording, false when paused or stopped */
28
+ this.recording = false;
29
+ /** The pointer to the current bucket holding rrweb events */
30
+ this.currentBufferTarget = this.#events;
31
+ /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
32
+ this.lastMeta = false;
33
+ /** The parent class that instantiated the recorder */
34
+ this.parent = parent;
35
+ /** Config to inform to inline stylesheet contents (true default) */
36
+ this.shouldInlineStylesheets = (0, _config.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay.inline_stylesheet');
37
+ /** A flag that can be set to false by failing conversions to stop the fetching process */
38
+ this.shouldFix = this.shouldInlineStylesheets;
39
+ /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
40
+ this.stopRecording = () => {/* no-op until set by rrweb initializer */};
41
+ }
42
+ getEvents() {
43
+ if (this.#preloaded[0]?.events.length) return {
44
+ ...this.#preloaded[0],
45
+ type: 'preloaded'
46
+ };
47
+ return {
48
+ events: [...this.#backloggedEvents.events, ...this.#events.events].filter(x => x),
49
+ type: 'standard',
50
+ cycleTimestamp: Math.min(this.#backloggedEvents.cycleTimestamp, this.#events.cycleTimestamp),
51
+ payloadBytesEstimation: this.#backloggedEvents.payloadBytesEstimation + this.#events.payloadBytesEstimation,
52
+ hasError: this.#backloggedEvents.hasError || this.#events.hasError,
53
+ hasMeta: this.#backloggedEvents.hasMeta || this.#events.hasMeta,
54
+ hasSnapshot: this.#backloggedEvents.hasSnapshot || this.#events.hasSnapshot,
55
+ inlinedAllStylesheets: !!this.#backloggedEvents.events.length && this.#backloggedEvents.inlinedAllStylesheets || this.#events.inlinedAllStylesheets
56
+ };
57
+ }
58
+
59
+ /** Clears the buffer (this.#events), and resets all payload metadata properties */
60
+ clearBuffer() {
61
+ if (this.#preloaded[0]?.events.length) this.#preloaded.shift();else if (this.parent.mode === _constants2.MODE.ERROR) this.#backloggedEvents = this.#events;else this.#backloggedEvents = new _recorderEvents.RecorderEvents();
62
+ this.#events = new _recorderEvents.RecorderEvents();
63
+ }
64
+
65
+ /** Begin recording using configured recording lib */
66
+ startRecording() {
67
+ this.recording = true;
68
+ const {
69
+ block_class,
70
+ ignore_class,
71
+ mask_text_class,
72
+ block_selector,
73
+ mask_input_options,
74
+ mask_text_selector,
75
+ mask_all_inputs,
76
+ inline_stylesheet,
77
+ inline_images,
78
+ collect_fonts
79
+ } = (0, _config.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay');
80
+ // set up rrweb configurations for maximum privacy --
81
+ // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
82
+ const stop = (0, _rrweb.record)({
83
+ emit: this.audit.bind(this),
84
+ blockClass: block_class,
85
+ ignoreClass: ignore_class,
86
+ maskTextClass: mask_text_class,
87
+ blockSelector: block_selector,
88
+ maskInputOptions: mask_input_options,
89
+ maskTextSelector: mask_text_selector,
90
+ maskAllInputs: mask_all_inputs,
91
+ inlineStylesheet: inline_stylesheet,
92
+ inlineImages: inline_images,
93
+ collectFonts: collect_fonts,
94
+ checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode]
95
+ });
96
+ this.stopRecording = () => {
97
+ this.recording = false;
98
+ stop();
99
+ };
100
+ }
101
+
102
+ /**
103
+ * audit - Checks if the event node payload is missing certain attributes
104
+ * will forward on to the "store" method if nothing needs async fixing
105
+ * @param {*} event - An RRWEB event node
106
+ * @param {*} isCheckout - Flag indicating if the payload was triggered as a checkout
107
+ */
108
+ audit(event, isCheckout) {
109
+ /** only run the audit if inline_stylesheets is configured as on (default behavior) */
110
+ if (this.shouldInlineStylesheets === false || !this.shouldFix) {
111
+ this.currentBufferTarget.inlinedAllStylesheets = false;
112
+ return this.store(event, isCheckout);
113
+ }
114
+ /** An count of stylesheet objects that were blocked from accessing contents via JS */
115
+ const incompletes = _stylesheetEvaluator.stylesheetEvaluator.evaluate();
116
+ /** 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) */
117
+ if (!incompletes && this.#fixing && event.type === _constants.RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
118
+ if (incompletes) {
119
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
120
+ /** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
121
+ _stylesheetEvaluator.stylesheetEvaluator.fix().then(failedToFix => {
122
+ if (failedToFix) {
123
+ this.currentBufferTarget.inlinedAllStylesheets = false;
124
+ this.shouldFix = false;
125
+ }
126
+ this.takeFullSnapshot();
127
+ });
128
+ /** Only start ignoring data if got a faulty snapshot */
129
+ if (event.type === _constants.RRWEB_EVENT_TYPES.FullSnapshot || event.type === _constants.RRWEB_EVENT_TYPES.Meta) this.#fixing = true;
130
+ }
131
+ /** Only store the data if not being "fixed" (full snapshots that have broken css) */
132
+ if (!this.#fixing) this.store(event, isCheckout);
133
+ }
134
+
135
+ /** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
136
+ store(event, isCheckout) {
137
+ if (!event) return;
138
+ event.__serialized = (0, _stringify.stringify)(event);
139
+ if (!this.parent.scheduler && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
140
+ if (this.parent.blocked) return;
141
+ const eventBytes = event.__serialized.length;
142
+ /** The estimated size of the payload after compression */
143
+ const payloadSize = this.getPayloadSize(eventBytes);
144
+ // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
145
+ // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
146
+ // each time we see a new checkout, we can drop the old data.
147
+ // we need to check for meta because rrweb will flag it as checkout twice, once for meta, then once for snapshot
148
+ if (this.parent.mode === _constants2.MODE.ERROR && isCheckout && event.type === _constants.RRWEB_EVENT_TYPES.Meta) {
149
+ // we are still waiting for an error to throw, so keep wiping the buffer over time
150
+ this.clearBuffer();
151
+ }
152
+
153
+ // meta event
154
+ if (event.type === _constants.RRWEB_EVENT_TYPES.Meta) {
155
+ this.currentBufferTarget.hasMeta = true;
156
+ }
157
+ // snapshot event
158
+ if (event.type === _constants.RRWEB_EVENT_TYPES.FullSnapshot) {
159
+ this.currentBufferTarget.hasSnapshot = true;
160
+ }
161
+ this.currentBufferTarget.add(event);
162
+ this.currentBufferTarget.payloadBytesEstimation += eventBytes;
163
+
164
+ // We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
165
+ // it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
166
+ if (payloadSize > _constants.IDEAL_PAYLOAD_SIZE && this.parent.mode !== _constants2.MODE.ERROR) {
167
+ // if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
168
+ if (this.parent.scheduler) {
169
+ this.parent.scheduler.runHarvest();
170
+ } else {
171
+ // we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
172
+ this.#preloaded.push(new _recorderEvents.RecorderEvents());
173
+ }
174
+ }
175
+ }
176
+
177
+ /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
178
+ takeFullSnapshot() {
179
+ _rrweb.record.takeFullSnapshot();
180
+ }
181
+ clearTimestamps() {
182
+ this.currentBufferTarget.cycleTimestamp = undefined;
183
+ }
184
+
185
+ /** Estimate the payload size */
186
+ getPayloadSize() {
187
+ let newBytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
188
+ // the query param padding constant gives us some padding for the other metadata to be safely injected
189
+ return this.estimateCompression(this.currentBufferTarget.payloadBytesEstimation + newBytes) + _constants.QUERY_PARAM_PADDING;
190
+ }
191
+
192
+ /** Extensive research has yielded about an 88% compression factor on these payloads.
193
+ * This is an estimation using that factor as to not cause performance issues while evaluating
194
+ * https://staging.onenr.io/037jbJWxbjy
195
+ * */
196
+ estimateCompression(data) {
197
+ if (this.shouldCompress) return data * _constants.AVG_COMPRESSION;
198
+ return data;
199
+ }
200
+ }
201
+ exports.Recorder = Recorder;
@@ -4,10 +4,10 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getSessionReplayMode = getSessionReplayMode;
7
- var _config = require("../../common/config/config");
8
- var _sessionEntity = require("../../common/session/session-entity");
9
- var _nreum = require("../../common/window/nreum");
10
- var _sharedChannel = require("../../common/constants/shared-channel");
7
+ var _config = require("../../../common/config/config");
8
+ var _nreum = require("../../../common/window/nreum");
9
+ var _sharedChannel = require("../../../common/constants/shared-channel");
10
+ var _constants = require("../../../common/session/constants");
11
11
  /**
12
12
  * Figure out if the Replay feature is running (what mode it's in).
13
13
  * IMPORTANT: Session tracking is assumed to be ON; if applicable, check init's privacy.cookies_enabled setting before using this fn!
@@ -24,5 +24,5 @@ async function getSessionReplayMode(agentId) {
24
24
  if (srInitialized) return await _sharedChannel.sharedChannel.sessionReplayInitialized; // wait for replay to determine which mode it's after running its sampling logic
25
25
  }
26
26
  } catch (e) {/* exception ==> off */}
27
- return _sessionEntity.MODE.OFF; // at any step of the way s.t. SR cannot be on by implication or is explicitly off
27
+ return _constants.MODE.OFF; // at any step of the way s.t. SR cannot be on by implication or is explicitly off
28
28
  }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.stylesheetEvaluator = void 0;
7
+ var _config = require("../../../common/config/config");
8
+ var _runtime = require("../../../common/constants/runtime");
9
+ class StylesheetEvaluator {
10
+ #evaluated = new WeakSet();
11
+ #fetchProms = [];
12
+ /**
13
+ * Flipped to true if stylesheets that cannot be natively inlined are detected by the stylesheetEvaluator class
14
+ * Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
15
+ * */
16
+ invalidStylesheetsDetected = false;
17
+ failedToFix = false;
18
+
19
+ /**
20
+ * this works by checking (only ever once) each cssRules obj in the style sheets array. The try/catch will catch an error if the cssRules obj blocks access, triggering the module to try to "fix" the asset`. Returns the count of incomplete assets discovered.
21
+ * @returns {Number}
22
+ */
23
+ evaluate() {
24
+ let incompletes = 0;
25
+ if (_runtime.isBrowserScope) {
26
+ for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
27
+ const ss = document.styleSheets[i];
28
+ if (!this.#evaluated.has(ss)) {
29
+ this.#evaluated.add(ss);
30
+ try {
31
+ // eslint-disable-next-line
32
+ const temp = ss.cssRules;
33
+ } catch (err) {
34
+ incompletes++;
35
+ this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i], ss.href));
36
+ }
37
+ }
38
+ }
39
+ }
40
+ if (incompletes) this.invalidStylesheetsDetected = true;
41
+ return incompletes;
42
+ }
43
+
44
+ /**
45
+ * Resolves promise once all stylesheets have been fetched and overridden
46
+ * @returns {Promise}
47
+ */
48
+ async fix() {
49
+ await Promise.all(this.#fetchProms);
50
+ this.#fetchProms = [];
51
+ const failedToFix = this.failedToFix;
52
+ this.failedToFix = false;
53
+ return failedToFix;
54
+ }
55
+
56
+ /**
57
+ * Fetches stylesheet contents and overrides the target getters
58
+ * @param {*} target - The stylesheet object target - ex. document.styleSheets[0]
59
+ * @param {*} href - The asset href to fetch
60
+ * @returns {Promise}
61
+ */
62
+ async #fetchAndOverride(target, href) {
63
+ const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
64
+ if (!stylesheetContents.ok) {
65
+ this.failedToFix = true;
66
+ return;
67
+ }
68
+ const stylesheetText = await stylesheetContents.text();
69
+ try {
70
+ const cssSheet = new CSSStyleSheet();
71
+ await cssSheet.replace(stylesheetText);
72
+ Object.defineProperty(target, 'cssRules', {
73
+ get() {
74
+ return cssSheet.cssRules;
75
+ }
76
+ });
77
+ Object.defineProperty(target, 'rules', {
78
+ get() {
79
+ return cssSheet.rules;
80
+ }
81
+ });
82
+ } catch (err) {
83
+ // cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
84
+ // this is appended in prep of forking rrweb
85
+ Object.defineProperty(target, 'cssText', {
86
+ get() {
87
+ return stylesheetText;
88
+ }
89
+ });
90
+ this.failedToFix = true;
91
+ }
92
+ }
93
+ }
94
+ const stylesheetEvaluator = exports.stylesheetEvaluator = new StylesheetEvaluator();
@@ -11,9 +11,9 @@ var _config = require("../../../common/config/config");
11
11
  var _now = require("../../../common/timing/now");
12
12
  var _constants = require("../constants");
13
13
  var _handlerCache = require("../../utils/handler-cache");
14
- var _sessionEntity = require("../../../common/session/session-entity");
15
- var _replayMode = require("../../session_replay/replay-mode");
14
+ var _replayMode = require("../../session_replay/shared/replay-mode");
16
15
  var _aggregateBase = require("../../utils/aggregate-base");
16
+ var _constants2 = require("../../../common/session/constants");
17
17
  /*
18
18
  * Copyright 2020 New Relic Corporation. All rights reserved.
19
19
  * SPDX-License-Identifier: Apache-2.0
@@ -74,14 +74,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
74
74
  /* --- The following section deals with user sessions concept & contains non-trivial control flow. --- */
75
75
  const controlTraceOp = traceMode => {
76
76
  switch (traceMode) {
77
- case _sessionEntity.MODE.ERROR:
77
+ case _constants2.MODE.ERROR:
78
78
  this.startTracing(operationalGate, true);
79
79
  break;
80
- case _sessionEntity.MODE.FULL:
80
+ case _constants2.MODE.FULL:
81
81
  case true:
82
82
  this.startTracing(operationalGate);
83
83
  break;
84
- case _sessionEntity.MODE.OFF:
84
+ case _constants2.MODE.OFF:
85
85
  case false:
86
86
  default:
87
87
  // this feature becomes "off" (does nothing & nothing is sent)
@@ -91,9 +91,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
91
91
  };
92
92
  let seenAnError = false;
93
93
  let mostRecentModeKnown;
94
- this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (eventType, sessionState) => {
94
+ this.ee.on(_constants2.SESSION_EVENTS.UPDATE, (eventType, sessionState) => {
95
95
  // this will only have an effect if ST is NOT already in full mode
96
- if (sessionState.sessionReplayMode === _sessionEntity.MODE.FULL) switchToFull();
96
+ if (sessionState.sessionReplayMode === _constants2.MODE.FULL) switchToFull();
97
97
  });
98
98
 
99
99
  /**
@@ -102,21 +102,21 @@ class Aggregate extends _aggregateBase.AggregateBase {
102
102
  * "external" input in this case means errors thrown on the page or session replay itself being triggered to run in full mode by the API, which updates the session entity.
103
103
  */
104
104
  const switchToFull = () => {
105
- if (this.agentRuntime?.session?.state?.sessionReplayMode !== _sessionEntity.MODE.FULL) return;
106
- if (mostRecentModeKnown !== _sessionEntity.MODE.FULL) {
105
+ if (this.agentRuntime?.session?.state?.sessionReplayMode !== _constants2.MODE.FULL) return;
106
+ if (mostRecentModeKnown !== _constants2.MODE.FULL) {
107
107
  const prevMode = mostRecentModeKnown;
108
- mostRecentModeKnown = _sessionEntity.MODE.FULL;
108
+ mostRecentModeKnown = _constants2.MODE.FULL;
109
109
  sessionEntity.write({
110
110
  sessionTraceMode: mostRecentModeKnown
111
111
  });
112
112
  this.isStandalone = false;
113
- if (prevMode === _sessionEntity.MODE.ERROR && this.#scheduler) {
113
+ if (prevMode === _constants2.MODE.ERROR && this.#scheduler) {
114
114
  this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
115
115
  this.#scheduler.runHarvest({
116
116
  needResponse: true
117
117
  });
118
118
  } else {
119
- controlTraceOp(_sessionEntity.MODE.FULL);
119
+ controlTraceOp(_constants2.MODE.FULL);
120
120
  }
121
121
  }
122
122
  };
@@ -130,11 +130,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
130
130
  switchToFull();
131
131
  }, this.featureName, this.ee);
132
132
  const stopTracePerm = () => {
133
- if (sessionEntity.state.sessionTraceMode !== _sessionEntity.MODE.OFF) sessionEntity.write({
134
- sessionTraceMode: _sessionEntity.MODE.OFF
133
+ if (sessionEntity.state.sessionTraceMode !== _constants2.MODE.OFF) sessionEntity.write({
134
+ sessionTraceMode: _constants2.MODE.OFF
135
135
  });
136
136
  operationalGate.permanentlyDecide(false);
137
- if (mostRecentModeKnown === _sessionEntity.MODE.FULL) this.#scheduler?.runHarvest(); // allow queued nodes (past opGate) to final harvest, unless they were buffered in other modes
137
+ if (mostRecentModeKnown === _constants2.MODE.FULL) this.#scheduler?.runHarvest(); // allow queued nodes (past opGate) to final harvest, unless they were buffered in other modes
138
138
  this.#scheduler?.stopTimer(true); // the 'true' arg here will forcibly block any future call to runHarvest, so the last runHarvest above must be prior
139
139
  this.#scheduler = null;
140
140
  };
@@ -151,33 +151,33 @@ class Aggregate extends _aggregateBase.AggregateBase {
151
151
  /* Assuming on page visible that the trace mode is updated from shared session,
152
152
  - if trace is turned off from the other page, it should be likewise here.
153
153
  - if trace switches to Full mode, harvest should start (prev: Error) if not already running (prev: Full). */
154
- this.ee.on(_sessionEntity.SESSION_EVENTS.RESUME, () => {
154
+ this.ee.on(_constants2.SESSION_EVENTS.RESUME, () => {
155
155
  const updatedTraceMode = sessionEntity.state.sessionTraceMode;
156
- if (updatedTraceMode === _sessionEntity.MODE.OFF) stopTracePerm();else if (updatedTraceMode === _sessionEntity.MODE.FULL && this.#scheduler && !this.#scheduler.started) this.#scheduler.runHarvest({
156
+ if (updatedTraceMode === _constants2.MODE.OFF) stopTracePerm();else if (updatedTraceMode === _constants2.MODE.FULL && this.#scheduler && !this.#scheduler.started) this.#scheduler.runHarvest({
157
157
  needResponse: true
158
158
  });
159
159
  mostRecentModeKnown = updatedTraceMode;
160
160
  });
161
- this.ee.on(_sessionEntity.SESSION_EVENTS.PAUSE, () => {
161
+ this.ee.on(_constants2.SESSION_EVENTS.PAUSE, () => {
162
162
  mostRecentModeKnown = sessionEntity.state.sessionTraceMode;
163
163
  });
164
164
  if (!sessionEntity.isNew) {
165
165
  // inherit the same mode as existing session's Trace
166
- if (sessionEntity.state.sessionReplayMode === _sessionEntity.MODE.OFF) this.isStandalone = true;
166
+ if (sessionEntity.state.sessionReplayMode === _constants2.MODE.OFF) this.isStandalone = true;
167
167
  controlTraceOp(mostRecentModeKnown = sessionEntity.state.sessionTraceMode);
168
168
  } else {
169
169
  // for new sessions, see the truth table associated with NEWRELIC-8662 wrt the new Trace behavior under session management
170
170
  const replayMode = await (0, _replayMode.getSessionReplayMode)(agentIdentifier);
171
- if (replayMode === _sessionEntity.MODE.OFF) this.isStandalone = true; // without SR, Traces are still subject to old harvest limits
171
+ if (replayMode === _constants2.MODE.OFF) this.isStandalone = true; // without SR, Traces are still subject to old harvest limits
172
172
 
173
173
  let startingMode;
174
174
  if (traceOn === true) {
175
175
  // CASE: both trace (entitlement+sampling) & replay (entitlement) flags are true from RUM
176
- startingMode = _sessionEntity.MODE.FULL; // always full capture regardless of replay sampling decisions
176
+ startingMode = _constants2.MODE.FULL; // always full capture regardless of replay sampling decisions
177
177
  } else {
178
178
  // CASE: trace flag is off, BUT it must still run if replay is on (possibly)
179
179
  // At this point, it's possible that 1 or more exception was thrown, in which case just start in full if Replay originally started in ERROR mode.
180
- if (replayMode === _sessionEntity.MODE.ERROR && seenAnError) startingMode = _sessionEntity.MODE.FULL;else startingMode = replayMode;
180
+ if (replayMode === _constants2.MODE.ERROR && seenAnError) startingMode = _constants2.MODE.FULL;else startingMode = replayMode;
181
181
  }
182
182
  sessionEntity.write({
183
183
  sessionTraceMode: mostRecentModeKnown = startingMode
@@ -251,7 +251,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
251
251
  }); // sends first stn harvest immediately
252
252
  startupBuffer.decide(true); // signal to ALLOW & process data in EE's buffer into internal nodes queued for next harvest
253
253
  }
254
-
255
254
  #onHarvestFinished(result) {
256
255
  if (result.sent && result.responseText && !this.ptid) {
257
256
  // continue interval harvest only if ptid was returned by server on the first
@@ -287,10 +286,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
287
286
  const currentMode = this.agentRuntime.session.state.sessionTraceMode;
288
287
  /* There could still be nodes previously collected even after Trace (w/ session mgmt) is turned off. Hence, continue to send the last batch.
289
288
  * The intermediary controller SHOULD be already switched off so that no nodes are further queued. */
290
- if (currentMode === _sessionEntity.MODE.OFF && Object.keys(this.trace).length === 0) return;
291
- if (currentMode === _sessionEntity.MODE.ERROR) return; // Trace in this mode should never be harvesting, even on unload
289
+ if (currentMode === _constants2.MODE.OFF && Object.keys(this.trace).length === 0) return;
290
+ if (currentMode === _constants2.MODE.ERROR) return; // Trace in this mode should never be harvesting, even on unload
292
291
  }
293
-
294
292
  return this.takeSTNs(options.retry);
295
293
  }
296
294
 
@@ -464,7 +462,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
464
462
  storeSTN(stn) {
465
463
  if (this.nodeCount >= this.maxNodesPerHarvest) {
466
464
  // limit the amount of pending data awaiting next harvest
467
- if (this.isStandalone || this.agentRuntime.session.state.sessionTraceMode !== _sessionEntity.MODE.ERROR) return;
465
+ if (this.isStandalone || this.agentRuntime.session.state.sessionTraceMode !== _constants2.MODE.ERROR) return;
468
466
  const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
469
467
  if (openedSpace === 0) return;
470
468
  }
@@ -5,19 +5,11 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.START = exports.RESOURCE = exports.PUSH_STATE = exports.FN_START = exports.FN_END = exports.FEATURE_NAME = exports.END = exports.BST_RESOURCE = void 0;
7
7
  var _features = require("../../loaders/features/features");
8
- const FEATURE_NAME = _features.FEATURE_NAMES.sessionTrace;
9
- exports.FEATURE_NAME = FEATURE_NAME;
10
- const BST_RESOURCE = 'bstResource';
11
- exports.BST_RESOURCE = BST_RESOURCE;
12
- const RESOURCE = 'resource';
13
- exports.RESOURCE = RESOURCE;
14
- const START = '-start';
15
- exports.START = START;
16
- const END = '-end';
17
- exports.END = END;
18
- const FN_START = 'fn' + START;
19
- exports.FN_START = FN_START;
20
- const FN_END = 'fn' + END;
21
- exports.FN_END = FN_END;
22
- const PUSH_STATE = 'pushState';
23
- exports.PUSH_STATE = PUSH_STATE;
8
+ const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.sessionTrace;
9
+ const BST_RESOURCE = exports.BST_RESOURCE = 'bstResource';
10
+ const RESOURCE = exports.RESOURCE = 'resource';
11
+ const START = exports.START = '-start';
12
+ const END = exports.END = '-end';
13
+ const FN_START = exports.FN_START = 'fn' + START;
14
+ const FN_END = exports.FN_END = 'fn' + END;
15
+ const PUSH_STATE = exports.PUSH_STATE = 'pushState';