@newrelic/browser-agent 1.255.0 → 1.256.1

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 (66) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cjs/common/constants/env.cdn.js +2 -2
  3. package/dist/cjs/common/constants/env.npm.js +2 -2
  4. package/dist/cjs/common/constants/runtime.js +1 -1
  5. package/dist/cjs/common/session/session-entity.js +2 -1
  6. package/dist/cjs/common/timer/interaction-timer.js +16 -2
  7. package/dist/cjs/common/timing/time-keeper.js +1 -2
  8. package/dist/cjs/features/jserrors/aggregate/index.js +16 -6
  9. package/dist/cjs/features/jserrors/instrument/index.js +8 -3
  10. package/dist/cjs/features/session_replay/aggregate/index.js +48 -29
  11. package/dist/cjs/features/session_replay/constants.js +2 -1
  12. package/dist/cjs/features/session_replay/instrument/index.js +9 -2
  13. package/dist/cjs/features/session_replay/shared/recorder-events.js +1 -9
  14. package/dist/cjs/features/session_replay/shared/recorder.js +22 -50
  15. package/dist/cjs/features/session_replay/shared/utils.js +12 -0
  16. package/dist/cjs/loaders/api/api.js +7 -1
  17. package/dist/esm/common/constants/env.cdn.js +2 -2
  18. package/dist/esm/common/constants/env.npm.js +2 -2
  19. package/dist/esm/common/constants/runtime.js +1 -1
  20. package/dist/esm/common/session/session-entity.js +2 -1
  21. package/dist/esm/common/timer/interaction-timer.js +16 -2
  22. package/dist/esm/common/timing/time-keeper.js +1 -3
  23. package/dist/esm/features/jserrors/aggregate/index.js +16 -6
  24. package/dist/esm/features/jserrors/instrument/index.js +8 -3
  25. package/dist/esm/features/session_replay/aggregate/index.js +48 -29
  26. package/dist/esm/features/session_replay/constants.js +2 -1
  27. package/dist/esm/features/session_replay/instrument/index.js +9 -2
  28. package/dist/esm/features/session_replay/shared/recorder-events.js +1 -9
  29. package/dist/esm/features/session_replay/shared/recorder.js +23 -51
  30. package/dist/esm/features/session_replay/shared/utils.js +11 -0
  31. package/dist/esm/loaders/api/api.js +7 -1
  32. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  33. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  34. package/dist/types/common/timer/interaction-timer.d.ts +2 -0
  35. package/dist/types/common/timer/interaction-timer.d.ts.map +1 -1
  36. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  37. package/dist/types/features/jserrors/aggregate/index.d.ts +2 -1
  38. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  39. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  40. package/dist/types/features/session_replay/aggregate/index.d.ts +5 -2
  41. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  42. package/dist/types/features/session_replay/constants.d.ts +1 -0
  43. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  44. package/dist/types/features/session_replay/instrument/index.d.ts +1 -0
  45. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  46. package/dist/types/features/session_replay/shared/recorder-events.d.ts +0 -8
  47. package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
  48. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -17
  49. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  50. package/dist/types/features/session_replay/shared/utils.d.ts +8 -0
  51. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
  52. package/dist/types/loaders/api/api.d.ts.map +1 -1
  53. package/package.json +2 -2
  54. package/src/common/constants/runtime.js +1 -1
  55. package/src/common/session/session-entity.js +2 -1
  56. package/src/common/timer/interaction-timer.js +17 -2
  57. package/src/common/timing/time-keeper.js +1 -3
  58. package/src/features/jserrors/aggregate/index.js +15 -6
  59. package/src/features/jserrors/instrument/index.js +9 -4
  60. package/src/features/session_replay/aggregate/index.js +43 -25
  61. package/src/features/session_replay/constants.js +2 -1
  62. package/src/features/session_replay/instrument/index.js +7 -2
  63. package/src/features/session_replay/shared/recorder-events.js +1 -6
  64. package/src/features/session_replay/shared/recorder.js +21 -27
  65. package/src/features/session_replay/shared/utils.js +12 -0
  66. package/src/loaders/api/api.js +10 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.256.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.256.0...v1.256.1) (2024-04-15)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Revert "Generate PTID in Agent" ([#976](https://github.com/newrelic/newrelic-browser-agent/issues/976)) ([34b317f](https://github.com/newrelic/newrelic-browser-agent/commit/34b317fe577487af56d48861b7f256ec8d644d69))
12
+
13
+ ## [1.256.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.255.0...v1.256.0) (2024-04-11)
14
+
15
+
16
+ ### Features
17
+
18
+ * Adjust Session Replay Error Tracking ([#951](https://github.com/newrelic/newrelic-browser-agent/issues/951)) ([91d65b5](https://github.com/newrelic/newrelic-browser-agent/commit/91d65b5b7b5b7e753a6603150fd4bb7d2543babd))
19
+ * Allow unmasking elements with nr-unmask selectors ([#949](https://github.com/newrelic/newrelic-browser-agent/issues/949)) ([e17aa25](https://github.com/newrelic/newrelic-browser-agent/commit/e17aa25ee098115ad23a5fb9ae268a4b5769fac1))
20
+ * Generate PTID in Agent ([#964](https://github.com/newrelic/newrelic-browser-agent/issues/964)) ([af7b676](https://github.com/newrelic/newrelic-browser-agent/commit/af7b6764f40cb1ddfb3ab2ca16d05d8e4f459f4e))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * Resume Page Focus Now Checks Session State ([#961](https://github.com/newrelic/newrelic-browser-agent/issues/961)) ([e48af6b](https://github.com/newrelic/newrelic-browser-agent/commit/e48af6beb369daf6ddc8231daa040f0d9d204d5f))
26
+ * stabilize timestamp corrections ([#966](https://github.com/newrelic/newrelic-browser-agent/issues/966)) ([4fbe962](https://github.com/newrelic/newrelic-browser-agent/commit/4fbe962d7b268968df96da59058e2e53c527c5eb))
27
+
6
28
  ## [1.255.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.254.1...v1.255.0) (2024-04-04)
7
29
 
8
30
 
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = exports.VERSION = "1.255.0";
15
+ const VERSION = exports.VERSION = "1.256.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -28,4 +28,4 @@ const DIST_METHOD = exports.DIST_METHOD = 'CDN';
28
28
  /**
29
29
  * Exposes the lib version of rrweb
30
30
  */
31
- const RRWEB_VERSION = exports.RRWEB_VERSION = "2.0.0-alpha.11";
31
+ const RRWEB_VERSION = exports.RRWEB_VERSION = "2.0.0-alpha.12";
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = exports.VERSION = "1.255.0";
15
+ const VERSION = exports.VERSION = "1.256.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -29,4 +29,4 @@ const DIST_METHOD = exports.DIST_METHOD = 'NPM';
29
29
  /**
30
30
  * Exposes the lib version of rrweb
31
31
  */
32
- const RRWEB_VERSION = exports.RRWEB_VERSION = "2.0.0-alpha.11";
32
+ const RRWEB_VERSION = exports.RRWEB_VERSION = "2.0.0-alpha.12";
@@ -44,4 +44,4 @@ const ffVersion = exports.ffVersion = (() => {
44
44
  const isIE = exports.isIE = Boolean(isBrowserScope && window.document.documentMode); // deprecated property that only works in IE
45
45
 
46
46
  const supportsSendBeacon = exports.supportsSendBeacon = !!globalScope.navigator?.sendBeacon;
47
- const offset = exports.offset = Math.floor(globalScope?.performance?.timeOrigin || globalScope?.performance?.timing?.navigationStart || Date.now());
47
+ const offset = exports.offset = Math.floor(Date.now() - performance.now());
@@ -127,7 +127,8 @@ class SessionEntity {
127
127
  this.write((0, _configurable.getModeledObject)(this.state, model));
128
128
  },
129
129
  ee: this.ee,
130
- refreshEvents: ['click', 'keydown', 'scroll']
130
+ refreshEvents: ['click', 'keydown', 'scroll'],
131
+ readStorage: () => this.storage.get(this.lookupKey)
131
132
  }, this.state.inactiveAt - Date.now());
132
133
  } else {
133
134
  this.state.inactiveAt = Infinity;
@@ -15,6 +15,9 @@ class InteractionTimer extends _timer.Timer {
15
15
  this.onRefresh = typeof opts.onRefresh === 'function' ? opts.onRefresh : () => {/* noop */};
16
16
  this.onResume = typeof opts.onResume === 'function' ? opts.onResume : () => {/* noop */};
17
17
 
18
+ /** used to double-check LS state at resume time */
19
+ this.readStorage = opts.readStorage;
20
+
18
21
  // used by pause/resume
19
22
  this.remainingMs = undefined;
20
23
  if (!opts.refreshEvents) opts.refreshEvents = ['click', 'keydown', 'scroll'];
@@ -68,8 +71,19 @@ class InteractionTimer extends _timer.Timer {
68
71
  this.remainingMs = this.initialMs - (Date.now() - this.startTimestamp);
69
72
  }
70
73
  resume() {
71
- this.refresh();
72
- this.onResume(); // emit resume event after state updated
74
+ try {
75
+ const lsData = this.readStorage();
76
+ const obj = typeof lsData === 'string' ? JSON.parse(lsData) : lsData;
77
+ if (isExpired(obj.expiresAt) || isExpired(obj.inactiveAt)) this.end();else {
78
+ this.refresh();
79
+ this.onResume(); // emit resume event after state updated
80
+ }
81
+ } catch (err) {
82
+ this.end();
83
+ }
84
+ function isExpired(timestamp) {
85
+ return Date.now() > timestamp;
86
+ }
73
87
  }
74
88
  refresh(cb, ms) {
75
89
  this.clear();
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.TimeKeeper = void 0;
7
- var _runtime = require("../constants/runtime");
8
7
  /**
9
8
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
10
9
  * is done by tracking the performance timings of the RUM call and applying a calculation
@@ -37,7 +36,7 @@ class TimeKeeper {
37
36
  */
38
37
  #ready = false;
39
38
  constructor() {
40
- this.#originTime = _runtime.globalScope.performance.timeOrigin || _runtime.globalScope.performance.timing.navigationStart;
39
+ this.#originTime = Date.now() - performance.now();
41
40
  }
42
41
  get ready() {
43
42
  return this.#ready;
@@ -42,9 +42,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
42
42
  this.bufferedErrorsUnderSpa = {};
43
43
  this.currentBody = undefined;
44
44
  this.errorOnPage = false;
45
+ this.replayAborted = false;
45
46
 
46
47
  // this will need to change to match whatever ee we use in the instrument
47
48
  this.ee.on('interactionDone', (interaction, wasSaved) => this.onInteractionDone(interaction, wasSaved));
49
+ this.ee.on('REPLAY_ABORTED', () => {
50
+ this.replayAborted = true;
51
+ });
48
52
  (0, _registerHandler.registerHandler)('err', function () {
49
53
  return _this.storeError(...arguments);
50
54
  }, this.featureName, this.ee);
@@ -89,9 +93,16 @@ class Aggregate extends _aggregateBase.AggregateBase {
89
93
  if (releaseIds !== '{}') {
90
94
  payload.qs.ri = releaseIds;
91
95
  }
92
- if (body && body.err && body.err.length && !this.errorOnPage) {
93
- payload.qs.pve = '1';
94
- this.errorOnPage = true;
96
+ if (body && body.err && body.err.length) {
97
+ if (this.replayAborted) {
98
+ body.err.forEach(e => {
99
+ delete e.params?.hasReplay;
100
+ });
101
+ }
102
+ if (!this.errorOnPage) {
103
+ payload.qs.pve = '1';
104
+ this.errorOnPage = true;
105
+ }
95
106
  }
96
107
  return payload;
97
108
  }
@@ -136,7 +147,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
136
147
  }
137
148
  return canonicalStackString;
138
149
  }
139
- storeError(err, time, internal, customAttributes) {
150
+ storeError(err, time, internal, customAttributes, hasReplay) {
140
151
  // are we in an interaction
141
152
  time = time || (0, _now.now)();
142
153
  const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
@@ -187,7 +198,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
187
198
  params.pageview = 1;
188
199
  this.pageviewReported[bucketHash] = true;
189
200
  }
190
- if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true;
201
+ if (hasReplay && !this.replayAborted) params.hasReplay = hasReplay;
191
202
  params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
192
203
  params.timestamp = this.observedAt[bucketHash];
193
204
  var type = internal ? 'ierr' : 'err';
@@ -198,7 +209,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
198
209
  // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
199
210
  const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
200
211
  (0, _handle.handle)('errorAgg', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
201
- (0, _handle.handle)('errorAgg', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionReplay, this.ee);
202
212
  // still send EE events for other features such as above, but stop this one from aggregating internal data
203
213
  if (this.blocked) return;
204
214
  const softNavInUse = Boolean((0, _nreum.getNREUMInitializedAgent)(this.agentIdentifier)?.features[_features.FEATURE_NAMES.softNav]);
@@ -13,6 +13,7 @@ var _eventListenerOpts = require("../../../common/event-listener/event-listener-
13
13
  var _stringify = require("../../../common/util/stringify");
14
14
  var _uncaughtError = require("./uncaught-error");
15
15
  var _now = require("../../../common/timing/now");
16
+ var _constants2 = require("../../session_replay/constants");
16
17
  /*
17
18
  * Copyright 2020 New Relic Corporation. All rights reserved.
18
19
  * SPDX-License-Identifier: Apache-2.0
@@ -21,6 +22,7 @@ var _now = require("../../../common/timing/now");
21
22
  class Instrument extends _instrumentBase.InstrumentBase {
22
23
  static featureName = _constants.FEATURE_NAME;
23
24
  #seenErrors = new Set();
25
+ #replayRunning = false;
24
26
  constructor(agentIdentifier, aggregator) {
25
27
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
26
28
  super(agentIdentifier, aggregator, _constants.FEATURE_NAME, auto);
@@ -37,13 +39,16 @@ class Instrument extends _instrumentBase.InstrumentBase {
37
39
  });
38
40
  this.ee.on('internal-error', error => {
39
41
  if (!this.abortHandler) return;
40
- (0, _handle.handle)('ierr', [this.#castError(error), (0, _now.now)(), true], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
42
+ (0, _handle.handle)('ierr', [this.#castError(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
43
+ });
44
+ this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
45
+ this.#replayRunning = isRunning;
41
46
  });
42
47
  _runtime.globalScope.addEventListener('unhandledrejection', promiseRejectionEvent => {
43
48
  if (!this.abortHandler) return;
44
49
  (0, _handle.handle)('err', [this.#castPromiseRejectionEvent(promiseRejectionEvent), (0, _now.now)(), false, {
45
50
  unhandledPromiseRejection: 1
46
- }], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
51
+ }, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
47
52
  }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
48
53
  _runtime.globalScope.addEventListener('error', errorEvent => {
49
54
  if (!this.abortHandler) return;
@@ -56,7 +61,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
56
61
  this.#seenErrors.delete(errorEvent.error);
57
62
  return;
58
63
  }
59
- (0, _handle.handle)('err', [this.#castErrorEvent(errorEvent), (0, _now.now)()], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
64
+ (0, _handle.handle)('err', [this.#castErrorEvent(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
60
65
  }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
61
66
  this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
62
67
  this.importAggregator();
@@ -22,6 +22,7 @@ var _stringify = require("../../../common/util/stringify");
22
22
  var _stylesheetEvaluator = require("../shared/stylesheet-evaluator");
23
23
  var _drain = require("../../../common/drain/drain");
24
24
  var _now = require("../../../common/timing/now");
25
+ var _utils = require("../shared/utils");
25
26
  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); }
26
27
  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; } /*
27
28
  * Copyright 2023 New Relic Corporation. All rights reserved.
@@ -35,6 +36,8 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
35
36
  */
36
37
  class Aggregate extends _aggregateBase.AggregateBase {
37
38
  static featureName = _constants.FEATURE_NAME;
39
+ mode = _constants3.MODE.OFF;
40
+
38
41
  // pass the recorder into the aggregator
39
42
  constructor(agentIdentifier, aggregator, args) {
40
43
  super(agentIdentifier, aggregator, _constants.FEATURE_NAME);
@@ -48,23 +51,18 @@ class Aggregate extends _aggregateBase.AggregateBase {
48
51
  this.gzipper = undefined;
49
52
  /** populated with the u8 string lib async */
50
53
  this.u8 = undefined;
51
- /** the mode to start in. Defaults to off */
52
- const {
53
- session
54
- } = (0, _config.getRuntime)(this.agentIdentifier);
55
- this.mode = session.state.sessionReplayMode || _constants3.MODE.OFF;
56
54
 
57
55
  /** set by BCS response */
58
56
  this.entitled = false;
59
57
  /** set at BCS response, stored in runtime */
60
58
  this.timeKeeper = undefined;
61
59
  this.recorder = args?.recorder;
62
- if (this.recorder) this.recorder.parent = this;
60
+ this.preloaded = !!this.recorder;
61
+ this.errorNoticed = args?.errorNoticed || false;
63
62
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
64
63
 
65
64
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
66
65
  this.ee.on(_constants3.SESSION_EVENTS.RESET, () => {
67
- this.scheduler.runHarvest();
68
66
  this.abort(_constants.ABORT_REASONS.RESET);
69
67
  });
70
68
 
@@ -108,17 +106,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
108
106
  (0, _registerHandler.registerHandler)(_constants.SR_EVENT_EMITTER_TYPES.PAUSE, () => {
109
107
  this.forceStop(this.mode !== _constants3.MODE.ERROR);
110
108
  }, this.featureName, this.ee);
111
-
112
- // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
113
- // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
114
- (0, _registerHandler.registerHandler)('errorAgg', e => {
115
- this.errorNoticed = true;
116
- if (this.recorder) this.recorder.currentBufferTarget.hasError = true;
117
- // run once
118
- if (this.mode === _constants3.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
119
- this.switchToFull();
120
- }
121
- }, this.featureName, this.ee);
122
109
  const {
123
110
  error_sampling_rate,
124
111
  sampling_rate,
@@ -159,6 +146,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
159
146
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, _features.FEATURE_NAMES.metrics, this.ee);
160
147
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, _features.FEATURE_NAMES.metrics, this.ee);
161
148
  }
149
+ handleError(e) {
150
+ if (this.recorder) this.recorder.currentBufferTarget.hasError = true;
151
+ // run once
152
+ if (this.mode === _constants3.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
153
+ this.switchToFull();
154
+ }
155
+ }
162
156
  switchToFull() {
163
157
  this.mode = _constants3.MODE.FULL;
164
158
  // if the error was noticed AFTER the recorder was already imported....
@@ -223,12 +217,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
223
217
  } catch (err) {
224
218
  return this.abort(_constants.ABORT_REASONS.IMPORT);
225
219
  }
220
+ } else {
221
+ this.recorder.parent = this;
226
222
  }
227
223
 
228
224
  // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
229
- if (this.mode === _constants3.MODE.ERROR && this.errorNoticed) {
230
- this.mode = _constants3.MODE.FULL;
231
- }
225
+ if (this.mode === _constants3.MODE.ERROR && this.errorNoticed) this.mode = _constants3.MODE.FULL;
226
+ if (!this.preloaded) this.ee.on('err', e => this.handleError(e));
232
227
 
233
228
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
234
229
  // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
@@ -269,21 +264,39 @@ class Aggregate extends _aggregateBase.AggregateBase {
269
264
  this.recorder.clearBuffer();
270
265
  return;
271
266
  }
267
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Harvest/Attempts'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
272
268
  let len = 0;
273
269
  if (!!this.gzipper && !!this.u8) {
274
- payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => {
275
- if (e.__serialized) return e.__serialized;
276
- return (0, _stringify.stringify)(e);
270
+ payload.body = this.gzipper(this.u8("[".concat(payload.body.map(_ref2 => {
271
+ let {
272
+ __serialized,
273
+ ...e
274
+ } = _ref2;
275
+ if (e.__newrelic && __serialized) return __serialized;
276
+ const output = {
277
+ ...e
278
+ };
279
+ if (!output.__newrelic) {
280
+ output.__newrelic = (0, _utils.buildNRMetaNode)(e.timestamp, this.timeKeeper);
281
+ output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(e.timestamp);
282
+ }
283
+ return (0, _stringify.stringify)(output);
277
284
  }).join(','), "]")));
278
285
  len = payload.body.length;
279
286
  this.scheduler.opts.gzip = true;
280
287
  } else {
281
- payload.body = payload.body.map(_ref2 => {
288
+ payload.body = payload.body.map(_ref3 => {
282
289
  let {
283
290
  __serialized,
284
291
  ...node
285
- } = _ref2;
286
- return node;
292
+ } = _ref3;
293
+ if (node.__newrelic) return node;
294
+ const output = {
295
+ ...node
296
+ };
297
+ output.__newrelic = (0, _utils.buildNRMetaNode)(node.timestamp, this.timeKeeper);
298
+ output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
299
+ return output;
287
300
  });
288
301
  len = (0, _stringify.stringify)(payload.body).length;
289
302
  this.scheduler.opts.gzip = false;
@@ -303,6 +316,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
303
316
  if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
304
317
  return [payload];
305
318
  }
319
+ getCorrectedTimestamp(node) {
320
+ if (!node.timestamp) return;
321
+ if (node.__newrelic) return node.timestamp;
322
+ return this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
323
+ }
306
324
  getHarvestContents(recorderEvents) {
307
325
  recorderEvents ??= this.recorder.getEvents();
308
326
  let events = recorderEvents.events;
@@ -328,8 +346,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
328
346
  recorderEvents.hasMeta = !!events.find(x => x.type === _constants.RRWEB_EVENT_TYPES.Meta);
329
347
  }
330
348
  const relativeNow = (0, _now.now)();
331
- const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
332
- const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
349
+ const firstEventTimestamp = this.getCorrectedTimestamp(events[0]); // from rrweb node
350
+ const lastEventTimestamp = this.getCorrectedTimestamp(events[events.length - 1]); // from rrweb node
333
351
  const firstTimestamp = firstEventTimestamp || this.timeKeeper.correctAbsoluteTimestamp(recorderEvents.cycleTimestamp); // from rrweb node || from when the harvest cycle started
334
352
  const lastTimestamp = lastEventTimestamp || this.timeKeeper.convertRelativeTimestamp(relativeNow);
335
353
  const agentMetadata = agentRuntime.appMetadata?.agents?.[0] || {};
@@ -364,6 +382,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
364
382
  invalidStylesheetsDetected: _stylesheetEvaluator.stylesheetEvaluator.invalidStylesheetsDetected,
365
383
  inlinedAllStylesheets: recorderEvents.inlinedAllStylesheets,
366
384
  'rrweb.version': _env.RRWEB_VERSION,
385
+ 'payload.type': recorderEvents.type,
367
386
  // customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
368
387
  ...(endUserId && {
369
388
  'enduser.id': endUserId
@@ -9,7 +9,8 @@ var _features = require("../../loaders/features/features");
9
9
  const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.sessionReplay;
10
10
  const SR_EVENT_EMITTER_TYPES = exports.SR_EVENT_EMITTER_TYPES = {
11
11
  RECORD: 'recordReplay',
12
- PAUSE: 'pauseReplay'
12
+ PAUSE: 'pauseReplay',
13
+ REPLAY_RUNNING: 'replayRunning'
13
14
  };
14
15
  const AVG_COMPRESSION = exports.AVG_COMPRESSION = 0.12;
15
16
  const RRWEB_EVENT_TYPES = exports.RRWEB_EVENT_TYPES = {
@@ -29,6 +29,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
29
29
  session = JSON.parse(localStorage.getItem("".concat(_constants.PREFIX, "_").concat(_constants.DEFAULT_KEY)));
30
30
  } catch (err) {}
31
31
  if (this.#canPreloadRecorder(session)) {
32
+ /** If this is preloaded, set up a buffer, if not, later when sampling we will set up a .on for live events */
33
+ this.ee.on('err', e => {
34
+ this.errorNoticed = true;
35
+ if (this.featAggregate) this.featAggregate.handleError();
36
+ });
32
37
  this.#startRecording(session?.sessionReplayMode);
33
38
  } else {
34
39
  this.importAggregator();
@@ -55,12 +60,14 @@ class Instrument extends _instrumentBase.InstrumentBase {
55
60
  } = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'../shared/recorder')));
56
61
  this.recorder = new Recorder({
57
62
  mode,
58
- agentIdentifier: this.agentIdentifier
63
+ agentIdentifier: this.agentIdentifier,
64
+ ee: this.ee
59
65
  });
60
66
  this.recorder.startRecording();
61
67
  this.abortHandler = this.recorder.stopRecording;
62
68
  this.importAggregator({
63
- recorder: this.recorder
69
+ recorder: this.recorder,
70
+ errorNoticed: this.errorNoticed
64
71
  });
65
72
  }
66
73
  }
@@ -5,21 +5,13 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.RecorderEvents = void 0;
7
7
  class RecorderEvents {
8
- constructor(_ref) {
9
- let {
10
- canCorrectTimestamps
11
- } = _ref;
8
+ constructor() {
12
9
  /** The buffer to hold recorder event nodes */
13
10
  this.events = [];
14
11
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
15
12
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
16
13
  */
17
14
  this.cycleTimestamp = Date.now();
18
- /** Payload metadata -- Whether timestamps can be corrected, defaults as false, can be set to true if timekeeper is present at init time. Used to determine
19
- * if harvest needs to re-loop through nodes and correct them before sending. Ideal behavior is to correct them as they flow into the recorder
20
- * to prevent re-looping, but is not always possible since the timekeeper is not set until after page load and the recorder can be preloaded.
21
- */
22
- this.canCorrectTimestamps = !!canCorrectTimestamps;
23
15
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
24
16
  this.payloadBytesEstimation = 0;
25
17
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
@@ -14,6 +14,7 @@ var _stylesheetEvaluator = require("./stylesheet-evaluator");
14
14
  var _handle = require("../../../common/event-emitter/handle");
15
15
  var _constants3 = require("../../metrics/constants");
16
16
  var _features = require("../../../loaders/features/features");
17
+ var _utils = require("./utils");
17
18
  class Recorder {
18
19
  /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
19
20
  #events;
@@ -24,15 +25,9 @@ class Recorder {
24
25
  /** flag that if true, blocks events from being "stored". Only set to true when a full snapshot has incomplete nodes (only stylesheets ATM) */
25
26
  #fixing = false;
26
27
  constructor(parent) {
27
- this.#events = new _recorderEvents.RecorderEvents({
28
- canCorrectTimestamps: !!parent.timeKeeper?.ready
29
- });
30
- this.#backloggedEvents = new _recorderEvents.RecorderEvents({
31
- canCorrectTimestamps: !!parent.timeKeeper?.ready
32
- });
33
- this.#preloaded = [new _recorderEvents.RecorderEvents({
34
- canCorrectTimestamps: !!parent.timeKeeper?.ready
35
- })];
28
+ this.#events = new _recorderEvents.RecorderEvents();
29
+ this.#backloggedEvents = new _recorderEvents.RecorderEvents();
30
+ this.#preloaded = [new _recorderEvents.RecorderEvents()];
36
31
  /** True when actively recording, false when paused or stopped */
37
32
  this.recording = false;
38
33
  /** The pointer to the current bucket holding rrweb events */
@@ -49,18 +44,12 @@ class Recorder {
49
44
  this.stopRecording = () => {/* no-op until set by rrweb initializer */};
50
45
  }
51
46
  getEvents() {
52
- if (this.#preloaded[0]?.events.length) {
53
- const preloadedEvents = this.returnCorrectTimestamps(this.#preloaded[0]);
54
- return {
55
- ...this.#preloaded[0],
56
- events: preloadedEvents,
57
- type: 'preloaded'
58
- };
59
- }
60
- const backloggedEvents = this.returnCorrectTimestamps(this.#backloggedEvents);
61
- const events = this.returnCorrectTimestamps(this.#events);
47
+ if (this.#preloaded[0]?.events.length) return {
48
+ ...this.#preloaded[0],
49
+ type: 'preloaded'
50
+ };
62
51
  return {
63
- events: [...backloggedEvents, ...events].filter(x => x),
52
+ events: [...this.#backloggedEvents.events, ...this.#events.events].filter(x => x),
64
53
  type: 'standard',
65
54
  cycleTimestamp: Math.min(this.#backloggedEvents.cycleTimestamp, this.#events.cycleTimestamp),
66
55
  payloadBytesEstimation: this.#backloggedEvents.payloadBytesEstimation + this.#events.payloadBytesEstimation,
@@ -71,34 +60,10 @@ class Recorder {
71
60
  };
72
61
  }
73
62
 
74
- /**
75
- * Returns time-corrected events. If the events were correctable from the beginning, this correction will have already been applied.
76
- * @param {SessionReplayEvent[]} events The array of buffered SR nodes
77
- * @returns {CorrectedSessionReplayEvent[]}
78
- */
79
- returnCorrectTimestamps(events) {
80
- if (!this.parent.timeKeeper?.ready) return events.events;
81
- return events.canCorrectTimestamps ? events.events : events.events.map(_ref => {
82
- let {
83
- __serialized,
84
- timestamp,
85
- ...e
86
- } = _ref;
87
- return {
88
- timestamp: this.parent.timeKeeper.correctAbsoluteTimestamp(timestamp),
89
- ...e
90
- };
91
- });
92
- }
93
-
94
63
  /** Clears the buffer (this.#events), and resets all payload metadata properties */
95
64
  clearBuffer() {
96
- 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({
97
- canCorrectTimestamps: !!this.parent.timeKeeper?.ready
98
- });
99
- this.#events = new _recorderEvents.RecorderEvents({
100
- canCorrectTimestamps: !!this.parent.timeKeeper?.ready
101
- });
65
+ 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();
66
+ this.#events = new _recorderEvents.RecorderEvents();
102
67
  }
103
68
 
104
69
  /** Begin recording using configured recording lib */
@@ -116,6 +81,10 @@ class Recorder {
116
81
  inline_images,
117
82
  collect_fonts
118
83
  } = (0, _config.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay');
84
+ const customMasker = (text, element) => {
85
+ if (element?.type?.toLowerCase() !== 'password' && (element?.dataset.nrUnmask !== undefined || element?.classList.contains('nr-unmask'))) return text;
86
+ return '*'.repeat(text.length);
87
+ };
119
88
  // set up rrweb configurations for maximum privacy --
120
89
  // https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
121
90
  const stop = (0, _rrweb.record)({
@@ -126,14 +95,18 @@ class Recorder {
126
95
  blockSelector: block_selector,
127
96
  maskInputOptions: mask_input_options,
128
97
  maskTextSelector: mask_text_selector,
98
+ maskTextFn: customMasker,
129
99
  maskAllInputs: mask_all_inputs,
100
+ maskInputFn: customMasker,
130
101
  inlineStylesheet: inline_stylesheet,
131
102
  inlineImages: inline_images,
132
103
  collectFonts: collect_fonts,
133
104
  checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode]
134
105
  });
106
+ this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [true, this.parent.mode]);
135
107
  this.stopRecording = () => {
136
108
  this.recording = false;
109
+ this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode]);
137
110
  stop();
138
111
  };
139
112
  }
@@ -177,7 +150,8 @@ class Recorder {
177
150
  if (!event) return;
178
151
  if (!this.parent.scheduler && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
179
152
  if (this.parent.blocked) return;
180
- if (this.currentBufferTarget.canCorrectTimestamps) {
153
+ if (this.parent.timeKeeper?.ready && !event.__newrelic) {
154
+ event.__newrelic = (0, _utils.buildNRMetaNode)(event.timestamp, this.parent.timeKeeper);
181
155
  event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
182
156
  }
183
157
  event.__serialized = (0, _stringify.stringify)(event);
@@ -212,9 +186,7 @@ class Recorder {
212
186
  this.parent.scheduler.runHarvest();
213
187
  } else {
214
188
  // we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
215
- this.#preloaded.push(new _recorderEvents.RecorderEvents({
216
- canCorrectTimestamps: !!this.parent.timeKeeper?.ready
217
- }));
189
+ this.#preloaded.push(new _recorderEvents.RecorderEvents());
218
190
  }
219
191
  }
220
192
  }
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.buildNRMetaNode = buildNRMetaNode;
6
7
  exports.canImportReplayAgg = canImportReplayAgg;
7
8
  exports.enableSessionTracking = void 0;
8
9
  exports.isPreloadAllowed = isPreloadAllowed;
@@ -23,4 +24,15 @@ function isPreloadAllowed(agentId) {
23
24
  function canImportReplayAgg(agentId, sessionMgr) {
24
25
  if (!hasReplayPrerequisite(agentId)) return false;
25
26
  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
27
+ }
28
+ function buildNRMetaNode(timestamp, timeKeeper) {
29
+ const correctedTimestamp = timeKeeper.correctAbsoluteTimestamp(timestamp);
30
+ return {
31
+ originalTimestamp: timestamp,
32
+ correctedTimestamp,
33
+ timestampDiff: timestamp - correctedTimestamp,
34
+ timeKeeperOriginTime: timeKeeper.originTime,
35
+ timeKeeperCorrectedOriginTime: timeKeeper.correctedOriginTime,
36
+ timeKeeperDiff: Math.floor(timeKeeper.originTime - timeKeeper.correctedOriginTime)
37
+ };
26
38
  }