@newrelic/browser-agent 1.259.0 → 1.260.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 (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/constants/runtime.js +2 -1
  5. package/dist/cjs/common/session/session-entity.js +8 -1
  6. package/dist/cjs/common/timing/time-keeper.js +11 -32
  7. package/dist/cjs/features/jserrors/aggregate/index.js +1 -2
  8. package/dist/cjs/features/jserrors/instrument/index.js +3 -4
  9. package/dist/cjs/features/page_view_event/aggregate/index.js +7 -5
  10. package/dist/cjs/features/session_replay/shared/recorder.js +8 -1
  11. package/dist/cjs/features/session_trace/aggregate/index.js +1 -0
  12. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +1 -1
  13. package/dist/cjs/features/soft_navigations/aggregate/bel-node.js +1 -2
  14. package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -1
  15. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +5 -4
  16. package/dist/esm/common/constants/env.cdn.js +1 -1
  17. package/dist/esm/common/constants/env.npm.js +1 -1
  18. package/dist/esm/common/constants/runtime.js +3 -1
  19. package/dist/esm/common/session/session-entity.js +8 -1
  20. package/dist/esm/common/timing/time-keeper.js +11 -32
  21. package/dist/esm/features/jserrors/aggregate/index.js +1 -2
  22. package/dist/esm/features/jserrors/instrument/index.js +3 -4
  23. package/dist/esm/features/page_view_event/aggregate/index.js +7 -5
  24. package/dist/esm/features/session_replay/shared/recorder.js +8 -1
  25. package/dist/esm/features/session_trace/aggregate/index.js +1 -0
  26. package/dist/esm/features/session_trace/aggregate/trace/storage.js +1 -1
  27. package/dist/esm/features/soft_navigations/aggregate/bel-node.js +1 -2
  28. package/dist/esm/features/soft_navigations/aggregate/index.js +1 -1
  29. package/dist/esm/features/soft_navigations/aggregate/interaction.js +5 -4
  30. package/dist/types/common/constants/runtime.d.ts +0 -6
  31. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  32. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  33. package/dist/types/common/timing/time-keeper.d.ts +2 -0
  34. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  35. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  36. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  37. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -0
  38. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  39. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  40. package/dist/types/features/session_trace/aggregate/index.d.ts +2 -2
  41. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  42. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  43. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +0 -1
  44. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  45. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts +1 -1
  46. package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts.map +1 -1
  47. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +0 -1
  48. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
  49. package/package.json +2 -1
  50. package/src/common/constants/runtime.js +3 -1
  51. package/src/common/session/session-entity.js +3 -1
  52. package/src/common/timing/time-keeper.js +11 -32
  53. package/src/features/jserrors/aggregate/index.js +1 -2
  54. package/src/features/jserrors/instrument/index.js +3 -4
  55. package/src/features/page_view_event/aggregate/index.js +8 -4
  56. package/src/features/session_replay/shared/recorder.js +8 -1
  57. package/src/features/session_trace/aggregate/index.js +1 -0
  58. package/src/features/session_trace/aggregate/trace/storage.js +1 -2
  59. package/src/features/soft_navigations/aggregate/bel-node.js +1 -3
  60. package/src/features/soft_navigations/aggregate/index.js +1 -1
  61. package/src/features/soft_navigations/aggregate/interaction.js +5 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
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.260.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.260.0...v1.260.1) (2024-05-16)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Undefined stns in blob Trace ([#1039](https://github.com/newrelic/newrelic-browser-agent/issues/1039)) ([1a87991](https://github.com/newrelic/newrelic-browser-agent/commit/1a87991143aa1f220ae29edbf1e8ea89598bcc44))
12
+
13
+ ## [1.260.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.259.0...v1.260.0) (2024-05-13)
14
+
15
+
16
+ ### Features
17
+
18
+ * Improve time stamping of page view events ([#1026](https://github.com/newrelic/newrelic-browser-agent/issues/1026)) ([67a658d](https://github.com/newrelic/newrelic-browser-agent/commit/67a658d2645b680a479175dff06a4fd95bd6086a))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * Add internal error handler to session replay recorder ([#1029](https://github.com/newrelic/newrelic-browser-agent/issues/1029)) ([84c101c](https://github.com/newrelic/newrelic-browser-agent/commit/84c101ccbff8da207bdf215714f49b7d49941388))
24
+ * Adjust session entity to not race between tabs ([#1032](https://github.com/newrelic/newrelic-browser-agent/issues/1032)) ([d86becf](https://github.com/newrelic/newrelic-browser-agent/commit/d86becf2fc02aa133430332aa4e4c2bc26297750))
25
+
6
26
  ## [1.259.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.258.2...v1.259.0) (2024-05-08)
7
27
 
8
28
 
@@ -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.259.0";
15
+ const VERSION = exports.VERSION = "1.260.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -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.259.0";
15
+ const VERSION = exports.VERSION = "1.260.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.supportsSendBeacon = exports.originTime = exports.loadedAsDeferredBrowserScript = exports.isiOS = exports.isWorkerScope = exports.isIE = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
7
+ var _now = require("../timing/now");
7
8
  /**
8
9
  * @file Contains constants about the environment the agent is running
9
10
  * within. These values are derived at the time the agent is first loaded.
@@ -50,4 +51,4 @@ const supportsSendBeacon = exports.supportsSendBeacon = !!globalScope.navigator?
50
51
  * according to the browser's local clock.
51
52
  * @type {number}
52
53
  */
53
- const originTime = exports.originTime = Math.floor(Date.now() - performance.now());
54
+ const originTime = exports.originTime = Date.now() - (0, _now.now)();
@@ -74,8 +74,15 @@ class SessionEntity {
74
74
  expiresMs = _constants.DEFAULT_EXPIRES_MS,
75
75
  inactiveMs = _constants.DEFAULT_INACTIVE_MS
76
76
  } = _ref;
77
+ /** Ensure that certain properties are preserved across a reset if already set */
78
+ const persistentAttributes = {
79
+ serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
80
+ };
77
81
  this.state = {};
78
- this.sync(model);
82
+ this.sync({
83
+ ...model,
84
+ ...persistentAttributes
85
+ });
79
86
 
80
87
  // value is intended to act as the primary value of the k=v pair
81
88
  this.state.value = value;
@@ -5,9 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.TimeKeeper = void 0;
7
7
  var _runtime = require("../constants/runtime");
8
- var _contextualEe = require("../event-emitter/contextual-ee");
9
8
  var _config = require("../config/config");
10
- var _constants = require("../session/constants");
11
9
  /**
12
10
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
13
11
  * is done by tracking the performance timings of the RUM call and applying a calculation
@@ -41,18 +39,7 @@ class TimeKeeper {
41
39
  #ready = false;
42
40
  constructor(agentIdentifier) {
43
41
  this.#session = (0, _config.getRuntime)(agentIdentifier)?.session;
44
- if (this.#session) {
45
- const ee = _contextualEe.ee.get(agentIdentifier);
46
- ee.on(_constants.SESSION_EVENTS.UPDATE, this.#processSessionUpdate.bind(this));
47
- ee.on(_constants.SESSION_EVENTS.STARTED, () => {
48
- if (this.#ready) {
49
- this.#session.write({
50
- serverTimeDiff: this.#localTimeDiff
51
- });
52
- }
53
- });
54
- this.#processSessionUpdate(null, this.#session.read());
55
- }
42
+ this.processStoredDiff();
56
43
  }
57
44
  get ready() {
58
45
  return this.#ready;
@@ -68,22 +55,22 @@ class TimeKeeper {
68
55
  * @param endTime {number} The end time of the RUM request
69
56
  */
70
57
  processRumRequest(rumRequest, startTime, endTime) {
58
+ this.processStoredDiff();
71
59
  if (this.#ready) return; // Server time calculated from session entity
72
-
73
60
  const responseDateHeader = rumRequest.getResponseHeader('Date');
74
61
  if (!responseDateHeader) {
75
62
  throw new Error('Missing date header on rum response.');
76
63
  }
77
64
  const medianRumOffset = (endTime - startTime) / 2;
78
- const serverOffset = Math.floor(startTime + medianRumOffset);
65
+ const serverOffset = startTime + medianRumOffset;
79
66
 
80
67
  // Corrected page origin time
81
68
  this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
82
69
  this.#localTimeDiff = _runtime.originTime - this.#correctedOriginTime;
83
- if (Number.isNaN(this.#correctedOriginTime)) {
70
+ if (isNaN(this.#correctedOriginTime)) {
84
71
  throw new Error('Date header invalid format.');
85
72
  }
86
- if (this.#session) this.#session.write({
73
+ this.#session?.write({
87
74
  serverTimeDiff: this.#localTimeDiff
88
75
  });
89
76
  this.#ready = true;
@@ -96,7 +83,7 @@ class TimeKeeper {
96
83
  * @returns {number} Corrected unix/epoch timestamp
97
84
  */
98
85
  convertRelativeTimestamp(relativeTime) {
99
- return this.#correctedOriginTime + relativeTime;
86
+ return Math.floor(this.#correctedOriginTime + relativeTime);
100
87
  }
101
88
 
102
89
  /**
@@ -108,19 +95,11 @@ class TimeKeeper {
108
95
  return Math.floor(timestamp - this.#localTimeDiff);
109
96
  }
110
97
 
111
- /**
112
- * Processes a session entity update payload to extract the server time calculated.
113
- * @param {import('../session/constants').SESSION_EVENT_TYPES | null} type
114
- * @param {Object} data
115
- */
116
- #processSessionUpdate(type, data) {
117
- if (typeof data?.serverTimeDiff !== 'number') return;
118
- if (!type && !this.#ready ||
119
- // This captures the initial read from the session entity when the timekeeper first initializes
120
- type === _constants.SESSION_EVENT_TYPES.CROSS_TAB // This captures any cross-tab write of the session entity
121
- ) {
122
- // This captures the initial read from the session entity when the timekeeper first initializes
123
- this.#localTimeDiff = data.serverTimeDiff;
98
+ /** Process the session entity and use the info to set the main time calculations if present */
99
+ processStoredDiff() {
100
+ const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff;
101
+ if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
102
+ this.#localTimeDiff = storedServerTimeDiff;
124
103
  this.#correctedOriginTime = _runtime.originTime - this.#localTimeDiff;
125
104
  this.#ready = true;
126
105
  }
@@ -198,8 +198,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
198
198
  time
199
199
  };
200
200
 
201
- // sr, stn and spa aggregators listen to this event - stn sends the error in its payload,
202
- // and spa annotates the error with interaction info
201
+ // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
203
202
  const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
204
203
  (0, _handle.handle)('trace-jserror', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
205
204
  // still send EE events for other features such as above, but stop this one from aggregating internal data
@@ -7,7 +7,6 @@ exports.Instrument = void 0;
7
7
  var _handle = require("../../../common/event-emitter/handle");
8
8
  var _instrumentBase = require("../../utils/instrument-base");
9
9
  var _constants = require("../constants");
10
- var _features = require("../../../loaders/features/features");
11
10
  var _runtime = require("../../../common/constants/runtime");
12
11
  var _eventListenerOpts = require("../../../common/event-listener/event-listener-opts");
13
12
  var _now = require("../../../common/timing/now");
@@ -30,7 +29,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
30
29
  } catch (e) {}
31
30
  this.ee.on('internal-error', error => {
32
31
  if (!this.abortHandler) return;
33
- (0, _handle.handle)('ierr', [(0, _castError.castError)(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
32
+ (0, _handle.handle)('ierr', [(0, _castError.castError)(error), (0, _now.now)(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee);
34
33
  });
35
34
  this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
36
35
  this.#replayRunning = isRunning;
@@ -39,11 +38,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
39
38
  if (!this.abortHandler) return;
40
39
  (0, _handle.handle)('err', [(0, _castError.castPromiseRejectionEvent)(promiseRejectionEvent), (0, _now.now)(), false, {
41
40
  unhandledPromiseRejection: 1
42
- }, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
41
+ }, this.#replayRunning], undefined, this.featureName, this.ee);
43
42
  }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
44
43
  _runtime.globalScope.addEventListener('error', errorEvent => {
45
44
  if (!this.abortHandler) return;
46
- (0, _handle.handle)('err', [(0, _castError.castErrorEvent)(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, _features.FEATURE_NAMES.jserrors, this.ee);
45
+ (0, _handle.handle)('err', [(0, _castError.castErrorEvent)(errorEvent), (0, _now.now)(), false, {}, this.#replayRunning], undefined, this.featureName, this.ee);
47
46
  }, (0, _eventListenerOpts.eventListenerOpts)(false, this.removeOnAbort?.signal));
48
47
  this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
49
48
  this.importAggregator();
@@ -32,7 +32,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
32
32
  this.timeToFirstByte = 0;
33
33
  this.firstByteToWindowLoad = 0; // our "frontend" duration
34
34
  this.firstByteToDomContent = 0; // our "dom processing" duration
35
-
35
+ this.timeKeeper = new _timeKeeper.TimeKeeper(this.agentIdentifier);
36
36
  if (_runtime.isBrowserScope) {
37
37
  _timeToFirstByte.timeToFirstByte.subscribe(_ref => {
38
38
  let {
@@ -118,6 +118,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
118
118
  }
119
119
  queryParameters.fp = _firstPaint.firstPaint.current.value;
120
120
  queryParameters.fcp = _firstContentfulPaint.firstContentfulPaint.current.value;
121
+ if (this.timeKeeper?.ready) {
122
+ queryParameters.timestamp = this.timeKeeper.convertRelativeTimestamp((0, _now.now)());
123
+ }
121
124
  const rumStartTime = (0, _now.now)();
122
125
  harvester.send({
123
126
  endpoint: 'rum',
@@ -142,10 +145,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
142
145
  return;
143
146
  }
144
147
  try {
145
- const timeKeeper = new _timeKeeper.TimeKeeper(this.agentIdentifier);
146
- timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
147
- if (!timeKeeper.ready) throw new Error('TimeKeeper not ready');
148
- agentRuntime.timeKeeper = timeKeeper;
148
+ this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
149
+ if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
150
+ agentRuntime.timeKeeper = this.timeKeeper;
149
151
  } catch (error) {
150
152
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
151
153
  (0, _drain.drain)(this.agentIdentifier, _features.FEATURE_NAMES.metrics, true);
@@ -101,7 +101,14 @@ class Recorder {
101
101
  inlineStylesheet: inline_stylesheet,
102
102
  inlineImages: inline_images,
103
103
  collectFonts: collect_fonts,
104
- checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode]
104
+ checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode],
105
+ /** Emits errors thrown by rrweb directly before bubbling them up to the window */
106
+ errorHandler: err => {
107
+ /** capture rrweb errors as "internal" errors only */
108
+ this.parent.ee.emit('internal-error', [err]);
109
+ /** returning true informs rrweb to swallow the error instead of throwing it to the window */
110
+ return true;
111
+ }
105
112
  });
106
113
  this.stopRecording = () => {
107
114
  this.recording = false;
@@ -137,6 +137,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
137
137
  earliestTimeStamp,
138
138
  latestTimeStamp
139
139
  } = this.traceStorage.takeSTNs();
140
+ if (!stns) return; // there are no trace nodes
140
141
  if (options.retry) {
141
142
  this.sentTrace = stns;
142
143
  }
@@ -278,7 +278,7 @@ class TraceStorage {
278
278
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
279
279
  storeXhrAgg(type, name, params, metrics) {
280
280
  if (type !== 'xhr') return;
281
- this.storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname)));
281
+ this.storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
282
282
  }
283
283
  restoreNode(name, listOfSTNodes) {
284
284
  if (this.nodeCount >= _constants2.MAX_NODES_PER_HARVEST) return;
@@ -4,13 +4,12 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.BelNode = void 0;
7
- var _now = require("../../../common/timing/now");
8
7
  let nodesSeen = 0;
9
8
  class BelNode {
10
9
  belType;
11
10
  /** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
12
11
  children = [];
13
- start = (0, _now.now)();
12
+ start;
14
13
  end;
15
14
  callbackEnd = 0;
16
15
  callbackDuration = 0;
@@ -67,7 +67,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
67
67
  });
68
68
 
69
69
  // By default, a complete UI driven interaction requires event -> URL change -> DOM mod in that exact order.
70
- (0, _registerHandler.registerHandler)('newUIEvent', event => this.startUIInteraction(event.type, event.timeStamp, event.target), this.featureName, this.ee);
70
+ (0, _registerHandler.registerHandler)('newUIEvent', event => this.startUIInteraction(event.type, Math.floor(event.timeStamp), event.target), this.featureName, this.ee);
71
71
  (0, _registerHandler.registerHandler)('newURL', (timestamp, url) => this.interactionInProgress?.updateHistory(timestamp, url), this.featureName, this.ee);
72
72
  (0, _registerHandler.registerHandler)('newDom', timestamp => {
73
73
  this.interactionInProgress?.updateDom(timestamp);
@@ -8,6 +8,7 @@ var _config = require("../../../common/config/config");
8
8
  var _runtime = require("../../../common/constants/runtime");
9
9
  var _uniqueId = require("../../../common/ids/unique-id");
10
10
  var _belSerializer = require("../../../common/serialize/bel-serializer");
11
+ var _now = require("../../../common/timing/now");
11
12
  var _cleanUrl = require("../../../common/url/clean-url");
12
13
  var _constants = require("../constants");
13
14
  var _belNode = require("./bel-node");
@@ -44,11 +45,11 @@ class Interaction extends _belNode.BelNode {
44
45
  if (this.trigger === _constants.API_TRIGGER_NAME) this.createdByApi = true;
45
46
  }
46
47
  updateDom(timestamp) {
47
- this.domTimestamp = timestamp || performance.now(); // default timestamp should be precise for accurate isActiveDuring calculations
48
+ this.domTimestamp = timestamp || (0, _now.now)(); // default timestamp should be precise for accurate isActiveDuring calculations
48
49
  }
49
50
  updateHistory(timestamp, newUrl) {
50
51
  this.newURL = newUrl || '' + _runtime.globalScope?.location;
51
- this.historyTimestamp = timestamp || performance.now();
52
+ this.historyTimestamp = timestamp || (0, _now.now)();
52
53
  }
53
54
  seenHistoryAndDomChange() {
54
55
  return this.historyTimestamp > 0 && this.domTimestamp > this.historyTimestamp; // URL must change before DOM does
@@ -118,9 +119,9 @@ class Interaction extends _belNode.BelNode {
118
119
  // IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
119
120
  const fields = [(0, _belSerializer.numeric)(this.belType), 0,
120
121
  // this will be overwritten below with number of attached nodes
121
- (0, _belSerializer.numeric)(Math.floor(this.start - firstStartTimeOfPayload)),
122
+ (0, _belSerializer.numeric)(this.start - firstStartTimeOfPayload),
122
123
  // relative to first node
123
- (0, _belSerializer.numeric)(Math.floor(this.end - this.start)),
124
+ (0, _belSerializer.numeric)(this.end - this.start),
124
125
  // end -- relative to start
125
126
  (0, _belSerializer.numeric)(this.callbackEnd),
126
127
  // cbEnd -- relative to start; not used by BrowserInteraction events
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.259.0";
9
+ export const VERSION = "1.260.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.259.0";
9
+ export const VERSION = "1.260.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -5,6 +5,8 @@
5
5
  * @license Apache-2.0
6
6
  */
7
7
 
8
+ import { now } from '../timing/now';
9
+
8
10
  /**
9
11
  * Indicates if the agent is running within a normal browser window context.
10
12
  */
@@ -44,4 +46,4 @@ export const supportsSendBeacon = !!globalScope.navigator?.sendBeacon;
44
46
  * according to the browser's local clock.
45
47
  * @type {number}
46
48
  */
47
- export const originTime = Math.floor(Date.now() - performance.now());
49
+ export const originTime = Date.now() - now();
@@ -69,8 +69,15 @@ export class SessionEntity {
69
69
  expiresMs = DEFAULT_EXPIRES_MS,
70
70
  inactiveMs = DEFAULT_INACTIVE_MS
71
71
  } = _ref;
72
+ /** Ensure that certain properties are preserved across a reset if already set */
73
+ const persistentAttributes = {
74
+ serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff
75
+ };
72
76
  this.state = {};
73
- this.sync(model);
77
+ this.sync({
78
+ ...model,
79
+ ...persistentAttributes
80
+ });
74
81
 
75
82
  // value is intended to act as the primary value of the k=v pair
76
83
  this.state.value = value;
@@ -1,7 +1,5 @@
1
1
  import { originTime } from '../constants/runtime';
2
- import { ee as baseEE } from '../event-emitter/contextual-ee';
3
2
  import { getRuntime } from '../config/config';
4
- import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../session/constants';
5
3
 
6
4
  /**
7
5
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
@@ -36,18 +34,7 @@ export class TimeKeeper {
36
34
  #ready = false;
37
35
  constructor(agentIdentifier) {
38
36
  this.#session = getRuntime(agentIdentifier)?.session;
39
- if (this.#session) {
40
- const ee = baseEE.get(agentIdentifier);
41
- ee.on(SESSION_EVENTS.UPDATE, this.#processSessionUpdate.bind(this));
42
- ee.on(SESSION_EVENTS.STARTED, () => {
43
- if (this.#ready) {
44
- this.#session.write({
45
- serverTimeDiff: this.#localTimeDiff
46
- });
47
- }
48
- });
49
- this.#processSessionUpdate(null, this.#session.read());
50
- }
37
+ this.processStoredDiff();
51
38
  }
52
39
  get ready() {
53
40
  return this.#ready;
@@ -63,22 +50,22 @@ export class TimeKeeper {
63
50
  * @param endTime {number} The end time of the RUM request
64
51
  */
65
52
  processRumRequest(rumRequest, startTime, endTime) {
53
+ this.processStoredDiff();
66
54
  if (this.#ready) return; // Server time calculated from session entity
67
-
68
55
  const responseDateHeader = rumRequest.getResponseHeader('Date');
69
56
  if (!responseDateHeader) {
70
57
  throw new Error('Missing date header on rum response.');
71
58
  }
72
59
  const medianRumOffset = (endTime - startTime) / 2;
73
- const serverOffset = Math.floor(startTime + medianRumOffset);
60
+ const serverOffset = startTime + medianRumOffset;
74
61
 
75
62
  // Corrected page origin time
76
63
  this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
77
64
  this.#localTimeDiff = originTime - this.#correctedOriginTime;
78
- if (Number.isNaN(this.#correctedOriginTime)) {
65
+ if (isNaN(this.#correctedOriginTime)) {
79
66
  throw new Error('Date header invalid format.');
80
67
  }
81
- if (this.#session) this.#session.write({
68
+ this.#session?.write({
82
69
  serverTimeDiff: this.#localTimeDiff
83
70
  });
84
71
  this.#ready = true;
@@ -91,7 +78,7 @@ export class TimeKeeper {
91
78
  * @returns {number} Corrected unix/epoch timestamp
92
79
  */
93
80
  convertRelativeTimestamp(relativeTime) {
94
- return this.#correctedOriginTime + relativeTime;
81
+ return Math.floor(this.#correctedOriginTime + relativeTime);
95
82
  }
96
83
 
97
84
  /**
@@ -103,19 +90,11 @@ export class TimeKeeper {
103
90
  return Math.floor(timestamp - this.#localTimeDiff);
104
91
  }
105
92
 
106
- /**
107
- * Processes a session entity update payload to extract the server time calculated.
108
- * @param {import('../session/constants').SESSION_EVENT_TYPES | null} type
109
- * @param {Object} data
110
- */
111
- #processSessionUpdate(type, data) {
112
- if (typeof data?.serverTimeDiff !== 'number') return;
113
- if (!type && !this.#ready ||
114
- // This captures the initial read from the session entity when the timekeeper first initializes
115
- type === SESSION_EVENT_TYPES.CROSS_TAB // This captures any cross-tab write of the session entity
116
- ) {
117
- // This captures the initial read from the session entity when the timekeeper first initializes
118
- this.#localTimeDiff = data.serverTimeDiff;
93
+ /** Process the session entity and use the info to set the main time calculations if present */
94
+ processStoredDiff() {
95
+ const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff;
96
+ if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
97
+ this.#localTimeDiff = storedServerTimeDiff;
119
98
  this.#correctedOriginTime = originTime - this.#localTimeDiff;
120
99
  this.#ready = true;
121
100
  }
@@ -193,8 +193,7 @@ export class Aggregate extends AggregateBase {
193
193
  time
194
194
  };
195
195
 
196
- // sr, stn and spa aggregators listen to this event - stn sends the error in its payload,
197
- // and spa annotates the error with interaction info
196
+ // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
198
197
  const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
199
198
  handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
200
199
  // still send EE events for other features such as above, but stop this one from aggregating internal data
@@ -6,7 +6,6 @@
6
6
  import { handle } from '../../../common/event-emitter/handle';
7
7
  import { InstrumentBase } from '../../utils/instrument-base';
8
8
  import { FEATURE_NAME } from '../constants';
9
- import { FEATURE_NAMES } from '../../../loaders/features/features';
10
9
  import { globalScope } from '../../../common/constants/runtime';
11
10
  import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
12
11
  import { now } from '../../../common/timing/now';
@@ -24,7 +23,7 @@ export class Instrument extends InstrumentBase {
24
23
  } catch (e) {}
25
24
  this.ee.on('internal-error', error => {
26
25
  if (!this.abortHandler) return;
27
- handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
26
+ handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee);
28
27
  });
29
28
  this.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
30
29
  this.#replayRunning = isRunning;
@@ -33,11 +32,11 @@ export class Instrument extends InstrumentBase {
33
32
  if (!this.abortHandler) return;
34
33
  handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, {
35
34
  unhandledPromiseRejection: 1
36
- }, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
35
+ }, this.#replayRunning], undefined, this.featureName, this.ee);
37
36
  }, eventListenerOpts(false, this.removeOnAbort?.signal));
38
37
  globalScope.addEventListener('error', errorEvent => {
39
38
  if (!this.abortHandler) return;
40
- handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee);
39
+ handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, this.featureName, this.ee);
41
40
  }, eventListenerOpts(false, this.removeOnAbort?.signal));
42
41
  this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
43
42
  this.importAggregator();
@@ -24,7 +24,7 @@ export class Aggregate extends AggregateBase {
24
24
  this.timeToFirstByte = 0;
25
25
  this.firstByteToWindowLoad = 0; // our "frontend" duration
26
26
  this.firstByteToDomContent = 0; // our "dom processing" duration
27
-
27
+ this.timeKeeper = new TimeKeeper(this.agentIdentifier);
28
28
  if (isBrowserScope) {
29
29
  timeToFirstByte.subscribe(_ref => {
30
30
  let {
@@ -110,6 +110,9 @@ export class Aggregate extends AggregateBase {
110
110
  }
111
111
  queryParameters.fp = firstPaint.current.value;
112
112
  queryParameters.fcp = firstContentfulPaint.current.value;
113
+ if (this.timeKeeper?.ready) {
114
+ queryParameters.timestamp = this.timeKeeper.convertRelativeTimestamp(now());
115
+ }
113
116
  const rumStartTime = now();
114
117
  harvester.send({
115
118
  endpoint: 'rum',
@@ -134,10 +137,9 @@ export class Aggregate extends AggregateBase {
134
137
  return;
135
138
  }
136
139
  try {
137
- const timeKeeper = new TimeKeeper(this.agentIdentifier);
138
- timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
139
- if (!timeKeeper.ready) throw new Error('TimeKeeper not ready');
140
- agentRuntime.timeKeeper = timeKeeper;
140
+ this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
141
+ if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
142
+ agentRuntime.timeKeeper = this.timeKeeper;
141
143
  } catch (error) {
142
144
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, FEATURE_NAMES.metrics, this.ee);
143
145
  drain(this.agentIdentifier, FEATURE_NAMES.metrics, true);
@@ -95,7 +95,14 @@ export class Recorder {
95
95
  inlineStylesheet: inline_stylesheet,
96
96
  inlineImages: inline_images,
97
97
  collectFonts: collect_fonts,
98
- checkoutEveryNms: CHECKOUT_MS[this.parent.mode]
98
+ checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
99
+ /** Emits errors thrown by rrweb directly before bubbling them up to the window */
100
+ errorHandler: err => {
101
+ /** capture rrweb errors as "internal" errors only */
102
+ this.parent.ee.emit('internal-error', [err]);
103
+ /** returning true informs rrweb to swallow the error instead of throwing it to the window */
104
+ return true;
105
+ }
99
106
  });
100
107
  this.stopRecording = () => {
101
108
  this.recording = false;
@@ -131,6 +131,7 @@ export class Aggregate extends AggregateBase {
131
131
  earliestTimeStamp,
132
132
  latestTimeStamp
133
133
  } = this.traceStorage.takeSTNs();
134
+ if (!stns) return; // there are no trace nodes
134
135
  if (options.retry) {
135
136
  this.sentTrace = stns;
136
137
  }
@@ -272,7 +272,7 @@ export class TraceStorage {
272
272
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
273
273
  storeXhrAgg(type, name, params, metrics) {
274
274
  if (type !== 'xhr') return;
275
- this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname)));
275
+ this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
276
276
  }
277
277
  restoreNode(name, listOfSTNodes) {
278
278
  if (this.nodeCount >= MAX_NODES_PER_HARVEST) return;
@@ -1,10 +1,9 @@
1
- import { now } from '../../../common/timing/now';
2
1
  let nodesSeen = 0;
3
2
  export class BelNode {
4
3
  belType;
5
4
  /** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
6
5
  children = [];
7
- start = now();
6
+ start;
8
7
  end;
9
8
  callbackEnd = 0;
10
9
  callbackDuration = 0;
@@ -61,7 +61,7 @@ export class Aggregate extends AggregateBase {
61
61
  });
62
62
 
63
63
  // By default, a complete UI driven interaction requires event -> URL change -> DOM mod in that exact order.
64
- registerHandler('newUIEvent', event => this.startUIInteraction(event.type, event.timeStamp, event.target), this.featureName, this.ee);
64
+ registerHandler('newUIEvent', event => this.startUIInteraction(event.type, Math.floor(event.timeStamp), event.target), this.featureName, this.ee);
65
65
  registerHandler('newURL', (timestamp, url) => this.interactionInProgress?.updateHistory(timestamp, url), this.featureName, this.ee);
66
66
  registerHandler('newDom', timestamp => {
67
67
  this.interactionInProgress?.updateDom(timestamp);
@@ -2,6 +2,7 @@ import { getInfo } from '../../../common/config/config';
2
2
  import { globalScope, initialLocation } from '../../../common/constants/runtime';
3
3
  import { generateUuid } from '../../../common/ids/unique-id';
4
4
  import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
5
+ import { now } from '../../../common/timing/now';
5
6
  import { cleanURL } from '../../../common/url/clean-url';
6
7
  import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME } from '../constants';
7
8
  import { BelNode } from './bel-node';
@@ -39,11 +40,11 @@ export class Interaction extends BelNode {
39
40
  if (this.trigger === API_TRIGGER_NAME) this.createdByApi = true;
40
41
  }
41
42
  updateDom(timestamp) {
42
- this.domTimestamp = timestamp || performance.now(); // default timestamp should be precise for accurate isActiveDuring calculations
43
+ this.domTimestamp = timestamp || now(); // default timestamp should be precise for accurate isActiveDuring calculations
43
44
  }
44
45
  updateHistory(timestamp, newUrl) {
45
46
  this.newURL = newUrl || '' + globalScope?.location;
46
- this.historyTimestamp = timestamp || performance.now();
47
+ this.historyTimestamp = timestamp || now();
47
48
  }
48
49
  seenHistoryAndDomChange() {
49
50
  return this.historyTimestamp > 0 && this.domTimestamp > this.historyTimestamp; // URL must change before DOM does
@@ -113,9 +114,9 @@ export class Interaction extends BelNode {
113
114
  // IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
114
115
  const fields = [numeric(this.belType), 0,
115
116
  // this will be overwritten below with number of attached nodes
116
- numeric(Math.floor(this.start - firstStartTimeOfPayload)),
117
+ numeric(this.start - firstStartTimeOfPayload),
117
118
  // relative to first node
118
- numeric(Math.floor(this.end - this.start)),
119
+ numeric(this.end - this.start),
119
120
  // end -- relative to start
120
121
  numeric(this.callbackEnd),
121
122
  // cbEnd -- relative to start; not used by BrowserInteraction events
@@ -1,9 +1,3 @@
1
- /**
2
- * @file Contains constants about the environment the agent is running
3
- * within. These values are derived at the time the agent is first loaded.
4
- * @copyright 2023 New Relic Corporation. All rights reserved.
5
- * @license Apache-2.0
6
- */
7
1
  /**
8
2
  * Indicates if the agent is running within a normal browser window context.
9
3
  */
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/runtime.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,qCAEqB;AAErB;;GAEG;AACH,oCAeK;AAEL,oDAUI;AAEJ,oDAA6F;AAE7F,sCAA2F;AAE3F,qCAAyD;AAEzD,4BAA8E;AAE9E;;;;;;GAMG;AACH,iCAAwE;AAExE,+BAOI;AAEJ,2BAA2E;AAE3E,yCAAqE;AAErE;;;;GAIG;AACH,yBAFU,MAAM,CAEoD"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/runtime.js"],"names":[],"mappings":"AASA;;GAEG;AACH,qCAEqB;AAErB;;GAEG;AACH,oCAeK;AAEL,oDAUI;AAEJ,oDAA6F;AAE7F,sCAA2F;AAE3F,qCAAyD;AAEzD,4BAA8E;AAE9E;;;;;;GAMG;AACH,iCAAwE;AAExE,+BAOI;AAEJ,2BAA2E;AAE3E,yCAAqE;AAErE;;;;GAIG;AACH,yBAFU,MAAM,CAE4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":"AA8BA;IACE;;;;;OAKG;IACH,uBA0BC;IApBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAenC;;;;aA0EC;IAnEC,8BAA0B;IAC1B,+BAA4B;IAc1B,gCAOqC;IAUrC,4CAkBsC;IAexC,iCAAuB;IAKzB,wBAEC;IAED,sBAEC;IAED;;;OAGG;IACH,QAFa,MAAM,CA6BlB;IAED;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,MAAM,CAkBlB;IAED,gBAuBC;IAED;;OAEG;IACH,gBAIC;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAED,yDAUC;IAED,6DAIC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBAvSqB,gBAAgB;iCAGL,4BAA4B"}
1
+ {"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":"AA8BA;IACE;;;;;OAKG;IACH,uBA0BC;IApBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAenC;;;;aA4EC;IAnEC,8BAA0B;IAC1B,+BAA4B;IAc1B,gCAOqC;IAUrC,4CAkBsC;IAexC,iCAAuB;IAKzB,wBAEC;IAED,sBAEC;IAED;;;OAGG;IACH,QAFa,MAAM,CA6BlB;IAED;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,MAAM,CAkBlB;IAED,gBAuBC;IAED;;OAEG;IACH,gBAIC;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAED,yDAUC;IAED,6DAIC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBAzSqB,gBAAgB;iCAGL,4BAA4B"}
@@ -27,6 +27,8 @@ export class TimeKeeper {
27
27
  * @return {number} Corrected unix/epoch timestamp
28
28
  */
29
29
  correctAbsoluteTimestamp(timestamp: number): number;
30
+ /** Process the session entity and use the info to set the main time calculations if present */
31
+ processStoredDiff(): void;
30
32
  #private;
31
33
  }
32
34
  //# sourceMappingURL=time-keeper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"AAKA;;;;GAIG;AACH;IA2BE,kCAaC;IAED,oBAEC;IAED,kCAEC;IAED;;;;;OAKG;IACH,8BAJsB,cAAc,aACf,MAAM,WACR,MAAM,QAuBxB;IAED;;;;;OAKG;IACH,uCAHwB,MAAM,GACjB,MAAM,CAIlB;IAED;;;;OAIG;IACH,oCAHqB,MAAM,GACf,MAAM,CAIjB;;CAoBF"}
1
+ {"version":3,"file":"time-keeper.d.ts","sourceRoot":"","sources":["../../../../src/common/timing/time-keeper.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IA2BE,kCAGC;IAED,oBAEC;IAED,kCAEC;IAED;;;;;OAKG;IACH,8BAJsB,cAAc,aACf,MAAM,WACR,MAAM,QAuBxB;IAED;;;;;OAKG;IACH,uCAHwB,MAAM,GACjB,MAAM,CAIlB;IAED;;;;OAIG;IACH,oCAHqB,MAAM,GACf,MAAM,CAIjB;IAED,+FAA+F;IAC/F,0BAOC;;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,mCAA4B;IAC5B,qBAAwB;IA0B1B;;;MAwBC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,4FA6FC;IA4BD,yDA6BC;IAED,qFAOC;;CAwBF;wBAxSY,OAAO,0BAA0B,EAAE,SAAS;8BAN3B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,mDAgCC;IA7BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,mCAA4B;IAC5B,qBAAwB;IA0B1B;;;MAwBC;IAED,qCAWC;IAED,8BAEC;IAED,oEAMC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED,4FA4FC;IA4BD,yDA6BC;IAED,qFAOC;;CAwBF;wBAvSY,OAAO,0BAA0B,EAAE,SAAS;8BAN3B,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IAIjC,mEA6BC;IAxBG,2CAA0C;IAsB5C,yBAA+B;;CASlC;+BAlD8B,6BAA6B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IAIjC,mEA6BC;IAxBG,2CAA0C;IAsB5C,yBAA+B;;CASlC;+BAjD8B,6BAA6B"}
@@ -4,7 +4,9 @@ export class Aggregate extends AggregateBase {
4
4
  timeToFirstByte: number;
5
5
  firstByteToWindowLoad: number;
6
6
  firstByteToDomContent: number;
7
+ timeKeeper: TimeKeeper;
7
8
  sendRum(): void;
8
9
  }
9
10
  import { AggregateBase } from '../../utils/aggregate-base';
11
+ import { TimeKeeper } from '../../../common/timing/time-keeper';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAA2C;IAC3C,mDAoBC;IAjBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAiBhC,gBAmGC;CACF;8BAvI6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAA2C;IAC3C,mDAqBC;IAlBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,uBAAsD;IAiBxD,gBAsGC;CACF;8BA3I6B,4BAA4B;2BAS/B,oCAAoC"}
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAYA;IAUE,yBAkBC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBA+BC;IAED;;;;;OAKG;IACH,yCA0BC;IAED,0HAA0H;IAC1H,yCAqDC;IA3CG,8BAAoB;IA6CxB,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BApN8B,mBAAmB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAYA;IAUE,yBAkBC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAsCC;IAED;;;;;OAKG;IACH,yCA0BC;IAED,0HAA0H;IAC1H,yCAqDC;IA3CG,8BAAoB;IA6CxB,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BA3N8B,mBAAmB"}
@@ -4,7 +4,7 @@ export class Aggregate extends AggregateBase {
4
4
  agentRuntime: any;
5
5
  agentInfo: any;
6
6
  /** A buffer to hold on to harvested traces in the case that a retry must be made later */
7
- sentTrace: any[] | null | undefined;
7
+ sentTrace: any[] | null;
8
8
  harvestTimeSeconds: any;
9
9
  /** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
10
10
  entitled: any;
@@ -31,7 +31,7 @@ export class Aggregate extends AggregateBase {
31
31
  timestamp: any;
32
32
  attributes: string;
33
33
  };
34
- body: any[] | undefined;
34
+ body: any[];
35
35
  } | undefined;
36
36
  /** When the harvest scheduler finishes, this callback is executed. It's main purpose is to determine if the payload needs to be retried
37
37
  * and if so, it will take all data from the temporary buffer and place it back into the traceStorage module
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IAEjC,mDAmBC;IAjBC,kBAA+C;IAC/C,eAAyC;IAEzC,0FAA0F;IAC1F,oCAAqB;IACrB,wBAA0G;IAC1G,0GAA0G;IAC1G,cAAyB;IACzB,mIAAmI;IACnI,uBAA0B;IAC1B,0CAA0C;IAC1C,oBAAuB;IACvB,wIAAwI;IACxI,2BAA0C;IAM5C,gLAAgL;IAChL,mEA6DC;IA1CyD,UAA4D;IAGpH,iCAAuB;IAOvB,wCAKQ;IA6BV,kJAAkJ;IAClJ,wBAIC;IAED,iJAAiJ;IACjJ;;;;;;;;;;kBA4DC;IAED;;OAEG;IACH,qCAKC;IAED,8DAA8D;IAC9D,qBAUC;IAED,2DAA2D;IAC3D,cAKC;CACF;8BAtM6B,4BAA4B;6BAC7B,iBAAiB;iCAJb,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IAEjC,mDAmBC;IAjBC,kBAA+C;IAC/C,eAAyC;IAEzC,0FAA0F;IAC1F,wBAAqB;IACrB,wBAA0G;IAC1G,0GAA0G;IAC1G,cAAyB;IACzB,mIAAmI;IACnI,uBAA0B;IAC1B,0CAA0C;IAC1C,oBAAuB;IACvB,wIAAwI;IACxI,2BAA0C;IAM5C,gLAAgL;IAChL,mEA6DC;IA1CyD,UAA4D;IAGpH,iCAAuB;IAOvB,wCAKQ;IA6BV,kJAAkJ;IAClJ,wBAIC;IAED,iJAAiJ;IACjJ;;;;;;;;;;kBA6DC;IAED;;OAEG;IACH,qCAKC;IAED,8DAA8D;IAC9D,qBAUC;IAED,2DAA2D;IAC3D,cAKC;CACF;8BAvM6B,4BAA4B;6BAC7B,iBAAiB;iCAJb,2CAA2C"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AAyBA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,mBAAgB;IAChB,2BAA4B;IAG1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAiBC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;;;;;MAsBC;IAED,mEA6BC;IAED,oDAOC;IAED,oCAkBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAIC;IAED,iDAKC;;CACF"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AAyBA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,mBAAgB;IAChB,2BAA4B;IAG1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAiBC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;;;;;MAsBC;IAED,mEA6BC;IAED,oDAOC;IAED,oCAkBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAGC;IAED,iDAKC;;CACF"}
@@ -12,7 +12,6 @@ export class AjaxNode extends BelNode {
12
12
  traceId: any;
13
13
  spanTimestamp: any;
14
14
  gql: any;
15
- start: any;
16
15
  serialize(parentStartTimestamp: any): string;
17
16
  }
18
17
  import { BelNode } from './bel-node';
@@ -1 +1 @@
1
- {"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AAIA;IACE,kDAiBC;IAfC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IAExB,WAAgC;IAIlC,6CA+BC;CACF;wBAtDuB,YAAY"}
1
+ {"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AAIA;IACE,kDAiBC;IAfC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IAM1B,6CA+BC;CACF;wBAtDuB,YAAY"}
@@ -3,7 +3,7 @@ export class BelNode {
3
3
  belType: any;
4
4
  /** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
5
5
  children: any[];
6
- start: number;
6
+ start: any;
7
7
  end: any;
8
8
  callbackEnd: number;
9
9
  callbackDuration: number;
@@ -1 +1 @@
1
- {"version":3,"file":"bel-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/bel-node.js"],"names":[],"mappings":"AAIA;IAUE,kCAGC;IAZD,aAAO;IACP,6HAA6H;IAC7H,gBAAa;IACb,cAAa;IACb,SAAG;IACH,oBAAe;IACf,yBAAoB;IACpB,eAAoB;IAIlB,qBAAsC;IAGxC,2BAEC;IAED,+CAA+C;IAC/C,kBAAe;CAChB"}
1
+ {"version":3,"file":"bel-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/bel-node.js"],"names":[],"mappings":"AAEA;IAUE,kCAGC;IAZD,aAAO;IACP,6HAA6H;IAC7H,gBAAa;IACb,WAAK;IACL,SAAG;IACH,oBAAe;IACf,yBAAoB;IACpB,eAAoB;IAIlB,qBAAsC;IAGxC,2BAEC;IAED,+CAA+C;IAC/C,kBAAe;CAChB"}
@@ -23,7 +23,6 @@ export class Interaction extends BelNode {
23
23
  cancellationTimer: any;
24
24
  belType: number;
25
25
  trigger: any;
26
- start: any;
27
26
  oldRoute: any;
28
27
  eventSubscription: Map<string, never[]>;
29
28
  forceSave: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"AAQA;;IAEI;AACJ;IAoBE,+FAYC;IA/BD,WAAmB;IACnB,uBAAgC;IAChC,eAAmC;IACnC,eAAmC;IACnC,gBAAU;IACV,qBAAqB;IACrB,oBAAoB;IACpB,eAAS;IACT,aAAO;IACP,cAAQ;IACR,+EAA+E;IAC/E,eAA8B;IAC9B,qBAAgB;IAChB,yBAAoB;IACpB,sBAAoB;IACpB,6BAA2B;IAC3B,cAAW;IACX,uBAAiB;IAIf,gBAAoC;IACpC,aAAsB;IACtB,WAA6B;IAC7B,cAAiC;IACjC,wCAGE;IACF,mBAAyC;IAAxB,qBAAwB;IAI3C,gCAEC;IAED,iDAGC;IAED,mCAEC;IAED,8BAIC;IAED,kCAUC;IAwBD;;;;;OAKG;IACH,0BAHW,mBAAmB,WAM7B;IAGD,uBAAoB;IACpB,iCAA8B;IAC9B,sBAAmB;IAEnB,gDA2CC;;CACF;wBAxJuB,YAAY"}
1
+ {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"AASA;;IAEI;AACJ;IAoBE,+FAYC;IA/BD,WAAmB;IACnB,uBAAgC;IAChC,eAAmC;IACnC,eAAmC;IACnC,gBAAU;IACV,qBAAqB;IACrB,oBAAoB;IACpB,eAAS;IACT,aAAO;IACP,cAAQ;IACR,+EAA+E;IAC/E,eAA8B;IAC9B,qBAAgB;IAChB,yBAAoB;IACpB,sBAAoB;IACpB,6BAA2B;IAC3B,cAAW;IACX,uBAAiB;IAIf,gBAAoC;IACpC,aAAsB;IAEtB,cAAiC;IACjC,wCAGE;IACF,mBAAyC;IAAxB,qBAAwB;IAI3C,gCAEC;IAED,iDAGC;IAED,mCAEC;IAED,8BAIC;IAED,kCAUC;IAwBD;;;;;OAKG;IACH,0BAHW,mBAAmB,WAM7B;IAGD,uBAAoB;IACpB,iCAA8B;IAC9B,sBAAmB;IAEnB,gDA2CC;;CACF;wBAxJuB,YAAY"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.259.0",
3
+ "version": "1.260.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -214,6 +214,7 @@
214
214
  "eslint-plugin-sonarjs": "^0.23.0",
215
215
  "fastify": "^4.25.2",
216
216
  "fastify-plugin": "^4.5.1",
217
+ "form-data": "^4.0.0",
217
218
  "fs-extra": "^11.2.0",
218
219
  "function-bind": "^1.1.1",
219
220
  "glob": "^10.2.5",
@@ -5,6 +5,8 @@
5
5
  * @license Apache-2.0
6
6
  */
7
7
 
8
+ import { now } from '../timing/now'
9
+
8
10
  /**
9
11
  * Indicates if the agent is running within a normal browser window context.
10
12
  */
@@ -79,4 +81,4 @@ export const supportsSendBeacon = !!globalScope.navigator?.sendBeacon
79
81
  * according to the browser's local clock.
80
82
  * @type {number}
81
83
  */
82
- export const originTime = Math.floor(Date.now() - performance.now())
84
+ export const originTime = Date.now() - now()
@@ -64,8 +64,10 @@ export class SessionEntity {
64
64
  }
65
65
 
66
66
  setup ({ value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS }) {
67
+ /** Ensure that certain properties are preserved across a reset if already set */
68
+ const persistentAttributes = { serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff }
67
69
  this.state = {}
68
- this.sync(model)
70
+ this.sync({ ...model, ...persistentAttributes })
69
71
 
70
72
  // value is intended to act as the primary value of the k=v pair
71
73
  this.state.value = value
@@ -1,7 +1,5 @@
1
1
  import { originTime } from '../constants/runtime'
2
- import { ee as baseEE } from '../event-emitter/contextual-ee'
3
2
  import { getRuntime } from '../config/config'
4
- import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../session/constants'
5
3
 
6
4
  /**
7
5
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
@@ -37,17 +35,7 @@ export class TimeKeeper {
37
35
 
38
36
  constructor (agentIdentifier) {
39
37
  this.#session = getRuntime(agentIdentifier)?.session
40
-
41
- if (this.#session) {
42
- const ee = baseEE.get(agentIdentifier)
43
- ee.on(SESSION_EVENTS.UPDATE, this.#processSessionUpdate.bind(this))
44
- ee.on(SESSION_EVENTS.STARTED, () => {
45
- if (this.#ready) {
46
- this.#session.write({ serverTimeDiff: this.#localTimeDiff })
47
- }
48
- })
49
- this.#processSessionUpdate(null, this.#session.read())
50
- }
38
+ this.processStoredDiff()
51
39
  }
52
40
 
53
41
  get ready () {
@@ -65,25 +53,25 @@ export class TimeKeeper {
65
53
  * @param endTime {number} The end time of the RUM request
66
54
  */
67
55
  processRumRequest (rumRequest, startTime, endTime) {
56
+ this.processStoredDiff()
68
57
  if (this.#ready) return // Server time calculated from session entity
69
-
70
58
  const responseDateHeader = rumRequest.getResponseHeader('Date')
71
59
  if (!responseDateHeader) {
72
60
  throw new Error('Missing date header on rum response.')
73
61
  }
74
62
 
75
63
  const medianRumOffset = (endTime - startTime) / 2
76
- const serverOffset = Math.floor(startTime + medianRumOffset)
64
+ const serverOffset = startTime + medianRumOffset
77
65
 
78
66
  // Corrected page origin time
79
67
  this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset)
80
68
  this.#localTimeDiff = originTime - this.#correctedOriginTime
81
69
 
82
- if (Number.isNaN(this.#correctedOriginTime)) {
70
+ if (isNaN(this.#correctedOriginTime)) {
83
71
  throw new Error('Date header invalid format.')
84
72
  }
85
73
 
86
- if (this.#session) this.#session.write({ serverTimeDiff: this.#localTimeDiff })
74
+ this.#session?.write({ serverTimeDiff: this.#localTimeDiff })
87
75
  this.#ready = true
88
76
  }
89
77
 
@@ -94,7 +82,7 @@ export class TimeKeeper {
94
82
  * @returns {number} Corrected unix/epoch timestamp
95
83
  */
96
84
  convertRelativeTimestamp (relativeTime) {
97
- return this.#correctedOriginTime + relativeTime
85
+ return Math.floor(this.#correctedOriginTime + relativeTime)
98
86
  }
99
87
 
100
88
  /**
@@ -106,20 +94,11 @@ export class TimeKeeper {
106
94
  return Math.floor(timestamp - this.#localTimeDiff)
107
95
  }
108
96
 
109
- /**
110
- * Processes a session entity update payload to extract the server time calculated.
111
- * @param {import('../session/constants').SESSION_EVENT_TYPES | null} type
112
- * @param {Object} data
113
- */
114
- #processSessionUpdate (type, data) {
115
- if (typeof data?.serverTimeDiff !== 'number') return
116
-
117
- if (
118
- (!type && !this.#ready) || // This captures the initial read from the session entity when the timekeeper first initializes
119
- type === SESSION_EVENT_TYPES.CROSS_TAB // This captures any cross-tab write of the session entity
120
- ) {
121
- // This captures the initial read from the session entity when the timekeeper first initializes
122
- this.#localTimeDiff = data.serverTimeDiff
97
+ /** Process the session entity and use the info to set the main time calculations if present */
98
+ processStoredDiff () {
99
+ const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff
100
+ if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
101
+ this.#localTimeDiff = storedServerTimeDiff
123
102
  this.#correctedOriginTime = originTime - this.#localTimeDiff
124
103
  this.#ready = true
125
104
  }
@@ -201,8 +201,7 @@ export class Aggregate extends AggregateBase {
201
201
  var type = internal ? 'ierr' : 'err'
202
202
  var newMetrics = { time }
203
203
 
204
- // sr, stn and spa aggregators listen to this event - stn sends the error in its payload,
205
- // and spa annotates the error with interaction info
204
+ // Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
206
205
  const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes]
207
206
  handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee)
208
207
  // still send EE events for other features such as above, but stop this one from aggregating internal data
@@ -6,7 +6,6 @@
6
6
  import { handle } from '../../../common/event-emitter/handle'
7
7
  import { InstrumentBase } from '../../utils/instrument-base'
8
8
  import { FEATURE_NAME } from '../constants'
9
- import { FEATURE_NAMES } from '../../../loaders/features/features'
10
9
  import { globalScope } from '../../../common/constants/runtime'
11
10
  import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts'
12
11
  import { now } from '../../../common/timing/now'
@@ -28,7 +27,7 @@ export class Instrument extends InstrumentBase {
28
27
 
29
28
  this.ee.on('internal-error', (error) => {
30
29
  if (!this.abortHandler) return
31
- handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee)
30
+ handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee)
32
31
  })
33
32
 
34
33
  this.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, (isRunning) => {
@@ -37,12 +36,12 @@ export class Instrument extends InstrumentBase {
37
36
 
38
37
  globalScope.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
39
38
  if (!this.abortHandler) return
40
- handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, { unhandledPromiseRejection: 1 }, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee)
39
+ handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, { unhandledPromiseRejection: 1 }, this.#replayRunning], undefined, this.featureName, this.ee)
41
40
  }, eventListenerOpts(false, this.removeOnAbort?.signal))
42
41
 
43
42
  globalScope.addEventListener('error', (errorEvent) => {
44
43
  if (!this.abortHandler) return
45
- handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee)
44
+ handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, this.featureName, this.ee)
46
45
  }, eventListenerOpts(false, this.removeOnAbort?.signal))
47
46
 
48
47
  this.abortHandler = this.#abort // we also use this as a flag to denote that the feature is active or on and handling errors
@@ -26,6 +26,7 @@ export class Aggregate extends AggregateBase {
26
26
  this.timeToFirstByte = 0
27
27
  this.firstByteToWindowLoad = 0 // our "frontend" duration
28
28
  this.firstByteToDomContent = 0 // our "dom processing" duration
29
+ this.timeKeeper = new TimeKeeper(this.agentIdentifier)
29
30
 
30
31
  if (isBrowserScope) {
31
32
  timeToFirstByte.subscribe(({ value, attrs }) => {
@@ -102,6 +103,10 @@ export class Aggregate extends AggregateBase {
102
103
  queryParameters.fp = firstPaint.current.value
103
104
  queryParameters.fcp = firstContentfulPaint.current.value
104
105
 
106
+ if (this.timeKeeper?.ready) {
107
+ queryParameters.timestamp = this.timeKeeper.convertRelativeTimestamp(now())
108
+ }
109
+
105
110
  const rumStartTime = now()
106
111
  harvester.send({
107
112
  endpoint: 'rum',
@@ -117,11 +122,10 @@ export class Aggregate extends AggregateBase {
117
122
  }
118
123
 
119
124
  try {
120
- const timeKeeper = new TimeKeeper(this.agentIdentifier)
121
- timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime)
122
- if (!timeKeeper.ready) throw new Error('TimeKeeper not ready')
125
+ this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime)
126
+ if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready')
123
127
 
124
- agentRuntime.timeKeeper = timeKeeper
128
+ agentRuntime.timeKeeper = this.timeKeeper
125
129
  } catch (error) {
126
130
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, FEATURE_NAMES.metrics, this.ee)
127
131
  drain(this.agentIdentifier, FEATURE_NAMES.metrics, true)
@@ -86,7 +86,14 @@ export class Recorder {
86
86
  inlineStylesheet: inline_stylesheet,
87
87
  inlineImages: inline_images,
88
88
  collectFonts: collect_fonts,
89
- checkoutEveryNms: CHECKOUT_MS[this.parent.mode]
89
+ checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
90
+ /** Emits errors thrown by rrweb directly before bubbling them up to the window */
91
+ errorHandler: (err) => {
92
+ /** capture rrweb errors as "internal" errors only */
93
+ this.parent.ee.emit('internal-error', [err])
94
+ /** returning true informs rrweb to swallow the error instead of throwing it to the window */
95
+ return true
96
+ }
90
97
  })
91
98
 
92
99
  this.stopRecording = () => {
@@ -116,6 +116,7 @@ export class Aggregate extends AggregateBase {
116
116
 
117
117
  /** Get the ST nodes from the traceStorage buffer. This also returns helpful metadata about the payload. */
118
118
  const { stns, earliestTimeStamp, latestTimeStamp } = this.traceStorage.takeSTNs()
119
+ if (!stns) return // there are no trace nodes
119
120
  if (options.retry) {
120
121
  this.sentTrace = stns
121
122
  }
@@ -274,8 +274,7 @@ export class TraceStorage {
274
274
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
275
275
  storeXhrAgg (type, name, params, metrics) {
276
276
  if (type !== 'xhr') return
277
- this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname
278
- }`))
277
+ this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname}`, 'ajax'))
279
278
  }
280
279
 
281
280
  restoreNode (name, listOfSTNodes) {
@@ -1,12 +1,10 @@
1
- import { now } from '../../../common/timing/now'
2
-
3
1
  let nodesSeen = 0
4
2
 
5
3
  export class BelNode {
6
4
  belType
7
5
  /** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
8
6
  children = []
9
- start = now()
7
+ start
10
8
  end
11
9
  callbackEnd = 0
12
10
  callbackDuration = 0
@@ -55,7 +55,7 @@ export class Aggregate extends AggregateBase {
55
55
  })
56
56
 
57
57
  // By default, a complete UI driven interaction requires event -> URL change -> DOM mod in that exact order.
58
- registerHandler('newUIEvent', (event) => this.startUIInteraction(event.type, event.timeStamp, event.target), this.featureName, this.ee)
58
+ registerHandler('newUIEvent', (event) => this.startUIInteraction(event.type, Math.floor(event.timeStamp), event.target), this.featureName, this.ee)
59
59
  registerHandler('newURL', (timestamp, url) => this.interactionInProgress?.updateHistory(timestamp, url), this.featureName, this.ee)
60
60
  registerHandler('newDom', timestamp => {
61
61
  this.interactionInProgress?.updateDom(timestamp)
@@ -2,6 +2,7 @@ import { getInfo } from '../../../common/config/config'
2
2
  import { globalScope, initialLocation } from '../../../common/constants/runtime'
3
3
  import { generateUuid } from '../../../common/ids/unique-id'
4
4
  import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer'
5
+ import { now } from '../../../common/timing/now'
5
6
  import { cleanURL } from '../../../common/url/clean-url'
6
7
  import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME } from '../constants'
7
8
  import { BelNode } from './bel-node'
@@ -44,12 +45,12 @@ export class Interaction extends BelNode {
44
45
  }
45
46
 
46
47
  updateDom (timestamp) {
47
- this.domTimestamp = (timestamp || performance.now()) // default timestamp should be precise for accurate isActiveDuring calculations
48
+ this.domTimestamp = (timestamp || now()) // default timestamp should be precise for accurate isActiveDuring calculations
48
49
  }
49
50
 
50
51
  updateHistory (timestamp, newUrl) {
51
52
  this.newURL = newUrl || '' + globalScope?.location
52
- this.historyTimestamp = (timestamp || performance.now())
53
+ this.historyTimestamp = (timestamp || now())
53
54
  }
54
55
 
55
56
  seenHistoryAndDomChange () {
@@ -124,8 +125,8 @@ export class Interaction extends BelNode {
124
125
  const fields = [
125
126
  numeric(this.belType),
126
127
  0, // this will be overwritten below with number of attached nodes
127
- numeric(Math.floor(this.start - firstStartTimeOfPayload)), // relative to first node
128
- numeric(Math.floor(this.end - this.start)), // end -- relative to start
128
+ numeric(this.start - firstStartTimeOfPayload), // relative to first node
129
+ numeric(this.end - this.start), // end -- relative to start
129
130
  numeric(this.callbackEnd), // cbEnd -- relative to start; not used by BrowserInteraction events
130
131
  numeric(this.callbackDuration), // not relative
131
132
  addString(this.trigger),