@newrelic/browser-agent 1.242.0 → 1.243.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 (111) hide show
  1. package/CHANGELOG.md +1472 -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/session/session-entity.js +20 -2
  5. package/dist/cjs/common/wrap/wrap-function.js +1 -1
  6. package/dist/cjs/features/ajax/aggregate/index.js +1 -1
  7. package/dist/cjs/features/session_replay/aggregate/index.js +89 -53
  8. package/dist/cjs/features/utils/feature-base.js +1 -2
  9. package/dist/cjs/loaders/api/api.js +2 -2
  10. package/dist/cjs/loaders/api/apiAsync.js +0 -37
  11. package/dist/cjs/loaders/configure/configure.js +1 -1
  12. package/dist/cjs/loaders/configure/public-path.js +6 -3
  13. package/dist/esm/common/constants/env.cdn.js +1 -1
  14. package/dist/esm/common/constants/env.npm.js +1 -1
  15. package/dist/esm/common/session/session-entity.js +18 -1
  16. package/dist/esm/common/wrap/wrap-function.js +1 -1
  17. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  18. package/dist/esm/features/session_replay/aggregate/index.js +88 -53
  19. package/dist/esm/features/utils/feature-base.js +1 -2
  20. package/dist/esm/loaders/api/api.js +2 -2
  21. package/dist/esm/loaders/api/apiAsync.js +1 -36
  22. package/dist/esm/loaders/configure/configure.js +1 -1
  23. package/dist/esm/loaders/configure/public-path.js +6 -3
  24. package/dist/types/common/session/session-entity.d.ts +5 -0
  25. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  26. package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
  27. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  28. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  29. package/dist/types/loaders/api/api.d.ts.map +1 -1
  30. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  31. package/dist/types/loaders/configure/public-path.d.ts +1 -1
  32. package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/src/common/session/session-entity.js +20 -1
  35. package/src/common/wrap/wrap-function.js +1 -1
  36. package/src/features/ajax/aggregate/index.js +2 -2
  37. package/src/features/session_replay/aggregate/index.js +86 -36
  38. package/src/features/utils/feature-base.js +1 -2
  39. package/src/loaders/api/api.js +1 -2
  40. package/src/loaders/api/apiAsync.js +1 -39
  41. package/src/loaders/configure/configure.js +1 -1
  42. package/src/loaders/configure/public-path.js +6 -3
  43. package/src/common/aggregate/aggregator.test.js +0 -107
  44. package/src/common/config/state/configurable.test.js +0 -73
  45. package/src/common/config/state/info.test.js +0 -31
  46. package/src/common/config/state/init.test.js +0 -68
  47. package/src/common/config/state/loader-config.test.js +0 -21
  48. package/src/common/config/state/runtime.test.js +0 -21
  49. package/src/common/constants/env.cdn.test.js +0 -7
  50. package/src/common/constants/env.npm.test.js +0 -7
  51. package/src/common/constants/env.test.js +0 -7
  52. package/src/common/constants/runtime.test.js +0 -176
  53. package/src/common/deny-list/deny-list.test.js +0 -104
  54. package/src/common/dom/query-selector.test.js +0 -24
  55. package/src/common/drain/drain.test.js +0 -74
  56. package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
  57. package/src/common/event-emitter/handle.test.js +0 -56
  58. package/src/common/event-emitter/register-handler.test.js +0 -61
  59. package/src/common/harvest/harvest-scheduler.test.js +0 -492
  60. package/src/common/harvest/harvest.test.js +0 -813
  61. package/src/common/ids/id.test.js +0 -92
  62. package/src/common/ids/unique-id.test.js +0 -58
  63. package/src/common/session/session-entity.component-test.js +0 -346
  64. package/src/common/storage/local-storage.test.js +0 -17
  65. package/src/common/timer/interaction-timer.component-test.js +0 -212
  66. package/src/common/timer/timer.test.js +0 -99
  67. package/src/common/timing/nav-timing.test.js +0 -161
  68. package/src/common/url/canonicalize-url.test.js +0 -45
  69. package/src/common/url/clean-url.test.js +0 -25
  70. package/src/common/url/encode.test.js +0 -81
  71. package/src/common/url/location.test.js +0 -15
  72. package/src/common/url/parse-url.test.js +0 -110
  73. package/src/common/url/protocol.test.js +0 -17
  74. package/src/common/util/console.test.js +0 -34
  75. package/src/common/util/data-size.test.js +0 -56
  76. package/src/common/util/feature-flags.test.js +0 -94
  77. package/src/common/util/get-or-set.test.js +0 -58
  78. package/src/common/util/invoke.test.js +0 -65
  79. package/src/common/util/map-own.test.js +0 -52
  80. package/src/common/util/obfuscate.component-test.js +0 -173
  81. package/src/common/util/stringify.test.js +0 -49
  82. package/src/common/util/submit-data.test.js +0 -183
  83. package/src/common/util/traverse.test.js +0 -50
  84. package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
  85. package/src/common/vitals/first-contentful-paint.test.js +0 -124
  86. package/src/common/vitals/first-input-delay.test.js +0 -88
  87. package/src/common/vitals/first-paint.test.js +0 -127
  88. package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
  89. package/src/common/vitals/largest-contentful-paint.test.js +0 -94
  90. package/src/common/vitals/long-task.test.js +0 -122
  91. package/src/common/vitals/time-to-first-byte.test.js +0 -147
  92. package/src/common/vitals/vital-metric.test.js +0 -171
  93. package/src/common/wrap/wrap-promise.component-test.js +0 -110
  94. package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
  95. package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
  96. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
  97. package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
  98. package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
  99. package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
  100. package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
  101. package/src/features/session_replay/aggregate/index.component-test.js +0 -317
  102. package/src/features/spa/aggregate/interaction-node.test.js +0 -17
  103. package/src/features/utils/agent-session.test.js +0 -194
  104. package/src/features/utils/aggregate-base.test.js +0 -123
  105. package/src/features/utils/feature-base.test.js +0 -45
  106. package/src/features/utils/handler-cache.test.js +0 -72
  107. package/src/features/utils/instrument-base.test.js +0 -216
  108. package/src/features/utils/lazy-feature-loader.test.js +0 -37
  109. package/src/loaders/api/api.component-test.js +0 -45
  110. package/src/loaders/api/api.test.js +0 -85
  111. package/src/loaders/api/apiAsync.test.js +0 -17
@@ -12,7 +12,7 @@ exports.VERSION = exports.DIST_METHOD = exports.BUILD_ENV = void 0;
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.242.0";
15
+ const VERSION = "1.243.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.DIST_METHOD = exports.BUILD_ENV = void 0;
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.242.0";
15
+ const VERSION = "1.243.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.SessionEntity = exports.SESSION_EVENTS = exports.MODE = void 0;
6
+ exports.SessionEntity = exports.SESSION_EVENT_TYPES = exports.SESSION_EVENTS = exports.MODE = void 0;
7
7
  var _uniqueId = require("../ids/unique-id");
8
8
  var _console = require("../util/console");
9
9
  var _stringify = require("../util/stringify");
@@ -17,6 +17,7 @@ var _configurable = require("../config/state/configurable");
17
17
  var _handle = require("../event-emitter/handle");
18
18
  var _constants2 = require("../../features/metrics/constants");
19
19
  var _features = require("../../loaders/features/features");
20
+ var _eventListenerOpts = require("../event-listener/event-listener-opts");
20
21
  const MODE = {
21
22
  OFF: 0,
22
23
  FULL: 1,
@@ -31,15 +32,22 @@ const model = {
31
32
  expiresAt: 0,
32
33
  updatedAt: Date.now(),
33
34
  sessionReplay: MODE.OFF,
35
+ sessionReplaySentFirstChunk: false,
34
36
  sessionTraceMode: MODE.OFF,
35
37
  custom: {}
36
38
  };
37
39
  const SESSION_EVENTS = {
38
40
  PAUSE: 'session-pause',
39
41
  RESET: 'session-reset',
40
- RESUME: 'session-resume'
42
+ RESUME: 'session-resume',
43
+ UPDATE: 'session-update'
41
44
  };
42
45
  exports.SESSION_EVENTS = SESSION_EVENTS;
46
+ const SESSION_EVENT_TYPES = {
47
+ SAME_TAB: 'same-tab',
48
+ CROSS_TAB: 'cross-tab'
49
+ };
50
+ exports.SESSION_EVENT_TYPES = SESSION_EVENT_TYPES;
43
51
  class SessionEntity {
44
52
  /**
45
53
  * Create a self-managing Session Entity. This entity is scoped to the agent identifier which triggered it, allowing for multiple simultaneous session objects to exist.
@@ -65,6 +73,15 @@ class SessionEntity {
65
73
  this.ee = _contextualEe.ee.get(agentIdentifier);
66
74
  (0, _wrap.wrapEvents)(this.ee);
67
75
  this.setup(opts);
76
+ if (_runtime.isBrowserScope) {
77
+ (0, _eventListenerOpts.windowAddEventListener)('storage', event => {
78
+ if (event.key === this.lookupKey) {
79
+ const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue;
80
+ this.sync(obj);
81
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state]);
82
+ }
83
+ });
84
+ }
68
85
  }
69
86
  setup(_ref) {
70
87
  let {
@@ -198,6 +215,7 @@ class SessionEntity {
198
215
  //
199
216
  // TODO - compression would need happen here if we decide to do it
200
217
  this.storage.set(this.lookupKey, (0, _stringify.stringify)(this.state));
218
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state]);
201
219
  return data;
202
220
  } catch (e) {
203
221
  // storage is inaccessible
@@ -218,5 +218,5 @@ function copy(from, to, emitter) {
218
218
  * @returns {boolean} Whether the passed function is ineligible to be wrapped.
219
219
  */
220
220
  function notWrappable(fn) {
221
- return !(fn && fn instanceof Function && fn.apply && !fn[flag]);
221
+ return !(fn && typeof fn === 'function' && fn.apply && !fn[flag]);
222
222
  }
@@ -83,7 +83,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
83
83
  } else {
84
84
  hash = (0, _stringify.stringify)([params.status, params.host, params.pathname]);
85
85
  }
86
- (0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, ee);
87
86
 
88
87
  // store as metric
89
88
  aggregator.store('xhr', hash, params, metrics);
@@ -97,6 +96,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
97
96
  }
98
97
  return;
99
98
  }
99
+ (0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, ee);
100
100
  var xhrContext = this;
101
101
  var event = {
102
102
  method: params.method,
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.RRWEB_VERSION = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.Aggregate = exports.AVG_COMPRESSION = void 0;
6
+ exports.RRWEB_VERSION = exports.RRWEB_EVENT_TYPES = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.Aggregate = exports.AVG_COMPRESSION = void 0;
7
7
  var _registerHandler = require("../../../common/event-emitter/register-handler");
8
8
  var _harvestScheduler = require("../../../common/harvest/harvest-scheduler");
9
9
  var _constants = require("../constants");
@@ -16,6 +16,7 @@ var _encode = require("../../../common/url/encode");
16
16
  var _console = require("../../../common/util/console");
17
17
  var _runtime = require("../../../common/constants/runtime");
18
18
  var _constants2 = require("../../metrics/constants");
19
+ var _handle = require("../../../common/event-emitter/handle");
19
20
  var _features = require("../../../loaders/features/features");
20
21
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
21
22
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /*
@@ -33,6 +34,37 @@ const RRWEB_VERSION = '2.0.0-alpha.8';
33
34
  exports.RRWEB_VERSION = RRWEB_VERSION;
34
35
  const AVG_COMPRESSION = 0.12;
35
36
  exports.AVG_COMPRESSION = AVG_COMPRESSION;
37
+ const RRWEB_EVENT_TYPES = {
38
+ DomContentLoaded: 0,
39
+ Load: 1,
40
+ FullSnapshot: 2,
41
+ IncrementalSnapshot: 3,
42
+ Meta: 4,
43
+ Custom: 5
44
+ };
45
+ exports.RRWEB_EVENT_TYPES = RRWEB_EVENT_TYPES;
46
+ const ABORT_REASONS = {
47
+ RESET: {
48
+ message: 'Session was reset',
49
+ sm: 'Reset'
50
+ },
51
+ IMPORT: {
52
+ message: 'Recorder failed to import',
53
+ sm: 'Import'
54
+ },
55
+ TOO_MANY: {
56
+ message: '429: Too Many Requests',
57
+ sm: 'Too-Many'
58
+ },
59
+ TOO_BIG: {
60
+ message: 'Payload was too large',
61
+ sm: 'Too-Big'
62
+ },
63
+ CROSS_TAB: {
64
+ message: 'Session Entity was set to OFF on another tab',
65
+ sm: 'Cross-Tab'
66
+ }
67
+ };
36
68
  let recorder, gzipper, u8;
37
69
 
38
70
  /** Vortex caps payload sizes at 1MB */
@@ -68,8 +100,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
68
100
  /** can shut off efforts to compress the data */
69
101
  this.shouldCompress = true;
70
102
 
71
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
72
- this.isFirstChunk = false;
73
103
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
74
104
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
75
105
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -83,16 +113,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
83
113
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
84
114
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
85
115
  */
86
- this.timestamp = {
87
- event: {
88
- first: undefined,
89
- last: undefined
90
- },
91
- cycle: {
92
- first: undefined,
93
- last: undefined
94
- }
95
- };
116
+ this.cycleTimestamp = undefined;
96
117
 
97
118
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
98
119
  this.payloadBytesEstimation = 0;
@@ -106,7 +127,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
106
127
  if (shouldSetup) {
107
128
  // 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.
108
129
  this.ee.on(_sessionEntity.SESSION_EVENTS.RESET, () => {
109
- this.abort('Session Reset');
130
+ this.abort(ABORT_REASONS.RESET);
110
131
  });
111
132
 
112
133
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -115,9 +136,19 @@ class Aggregate extends _aggregateBase.AggregateBase {
115
136
  });
116
137
  // The SessionEntity class can emit a message indicating the session was resumed (visibility change). This feature must start running again (if already running) if that occurs.
117
138
  this.ee.on(_sessionEntity.SESSION_EVENTS.RESUME, () => {
139
+ // if the mode changed on a different tab, it needs to update this instance to match
140
+ const {
141
+ session
142
+ } = (0, _config.getRuntime)(this.agentIdentifier);
143
+ this.mode = session.state.sessionReplay;
118
144
  if (!this.initialized || this.mode === _sessionEntity.MODE.OFF) return;
119
145
  this.startRecording();
120
146
  });
147
+ this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (type, data) => {
148
+ if (!this.initialized || this.blocked || type !== _sessionEntity.SESSION_EVENT_TYPES.CROSS_TAB) return;
149
+ if (this.mode !== _sessionEntity.MODE.OFF && data.sessionReplay === _sessionEntity.MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
150
+ this.mode = data.sessionReplay;
151
+ });
121
152
 
122
153
  // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
123
154
  this.scheduler = new _harvestScheduler.HarvestScheduler('browser/blobs', {
@@ -133,7 +164,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
133
164
  this.hasError = true;
134
165
  this.errorNoticed = true;
135
166
  // run once
136
- if (this.mode === _sessionEntity.MODE.ERROR) {
167
+ if (this.mode === _sessionEntity.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
137
168
  this.mode = _sessionEntity.MODE.FULL;
138
169
  // if the error was noticed AFTER the recorder was already imported....
139
170
  if (recorder && this.initialized) {
@@ -193,7 +224,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
193
224
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
194
225
  recorder = (await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'rrweb')))).record;
195
226
  } catch (err) {
196
- return this.abort('Recorder failed to import');
227
+ return this.abort(ABORT_REASONS.IMPORT);
197
228
  }
198
229
 
199
230
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -216,7 +247,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
216
247
  this.shouldCompress = false;
217
248
  }
218
249
  this.startRecording();
219
- this.isFirstChunk = !!session.isNew;
220
250
  this.syncWithSessionManager({
221
251
  sessionReplay: this.mode
222
252
  });
@@ -231,14 +261,39 @@ class Aggregate extends _aggregateBase.AggregateBase {
231
261
  this.scheduler.opts.gzip = false;
232
262
  }
233
263
  // TODO -- Gracefully handle the buffer for retries.
264
+ const {
265
+ session
266
+ } = (0, _config.getRuntime)(this.agentIdentifier);
267
+ if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
268
+ sessionReplaySentFirstChunk: true
269
+ });
234
270
  this.clearBuffer();
235
271
  return [payload];
236
272
  }
237
273
  getHarvestContents() {
238
274
  const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
239
275
  const info = (0, _config.getInfo)(this.agentIdentifier);
240
- const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first;
241
- const lastTimestamp = this.timestamp.event.last || this.timestamp.cycle.last;
276
+
277
+ // do not let the last node be a meta node, since this NEEDS to precede a snapshot
278
+ // we will manually inject it later if we find a payload that is missing a meta node
279
+ const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
280
+ if (payloadEndsWithMeta) {
281
+ this.lastMeta = this.events[this.events.length - 1];
282
+ this.events = this.events.slice(0, this.events.length - 1);
283
+ this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
284
+ }
285
+
286
+ // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
287
+ // we will manually inject it if this happens
288
+ const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
289
+ if (payloadStartsWithFullSnapshot) {
290
+ this.hasMeta = true;
291
+ this.events.unshift(this.lastMeta);
292
+ }
293
+ const firstEventTimestamp = this.events[0]?.timestamp;
294
+ const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
295
+ const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
296
+ const lastTimestamp = lastEventTimestamp || (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
242
297
  return {
243
298
  qs: {
244
299
  browser_monitoring_key: info.licenseKey,
@@ -252,12 +307,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
252
307
  'replay.firstTimestamp': firstTimestamp,
253
308
  'replay.lastTimestamp': lastTimestamp,
254
309
  'replay.durationMs': lastTimestamp - firstTimestamp,
310
+ 'replay.nodes': this.events.length,
255
311
  agentVersion: agentRuntime.version,
256
312
  session: agentRuntime.session.state.value,
257
313
  hasMeta: this.hasMeta,
258
314
  hasSnapshot: this.hasSnapshot,
259
315
  hasError: this.hasError,
260
- isFirstChunk: this.isFirstChunk,
316
+ isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
261
317
  decompressedBytes: this.payloadBytesEstimation,
262
318
  'nr.rrweb.version': RRWEB_VERSION
263
319
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
@@ -269,7 +325,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
269
325
  onHarvestFinished(result) {
270
326
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
271
327
  if (result.status === 429) {
272
- this.abort('429: Too many requests');
328
+ this.abort(ABORT_REASONS.TOO_MANY);
273
329
  }
274
330
  if (this.blocked) this.scheduler.stopTimer(true);
275
331
  }
@@ -277,7 +333,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
277
333
  /** Clears the buffer (this.events), and resets all payload metadata properties */
278
334
  clearBuffer() {
279
335
  this.events = [];
280
- this.isFirstChunk = false;
281
336
  this.hasSnapshot = false;
282
337
  this.hasMeta = false;
283
338
  this.hasError = false;
@@ -289,7 +344,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
289
344
  startRecording() {
290
345
  if (!recorder) {
291
346
  (0, _console.warn)('Recording library was never imported');
292
- return this.abort('Recorder was never imported');
347
+ return this.abort(ABORT_REASONS.IMPORT);
293
348
  }
294
349
  this.clearTimestamps();
295
350
  // set the fallbacks as early as possible
@@ -325,7 +380,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
325
380
 
326
381
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
327
382
  store(event, isCheckout) {
328
- this.setTimestamps(event);
383
+ this.setTimestamps();
329
384
  if (this.blocked) return;
330
385
  const eventBytes = (0, _stringify.stringify)(event).length;
331
386
  /** The estimated size of the payload after compression */
@@ -333,8 +388,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
333
388
  // Vortex will block payloads at a certain size, we might as well not send.
334
389
  if (payloadSize > MAX_PAYLOAD_SIZE) {
335
390
  this.clearBuffer();
336
- this.ee.emit(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Too-Big/Seen'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
337
- return this.abort('Payload too big');
391
+ return this.abort(ABORT_REASONS.TOO_BIG);
338
392
  }
339
393
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
340
394
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -345,19 +399,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
345
399
  }
346
400
 
347
401
  // meta event
348
- if (event.type === 4) {
402
+ if (event.type === RRWEB_EVENT_TYPES.Meta) {
349
403
  this.hasMeta = true;
350
404
  this.lastMeta = event;
351
405
  }
352
406
  // snapshot event
353
- if (event.type === 2) {
407
+ if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
354
408
  this.hasSnapshot = true;
355
- // small chance that the meta event got separated from its matching snapshot across payload harvests
356
- // it needs to precede the snapshot, so shove it in first.
357
- if (!this.hasMeta) {
358
- this.events.push(this.lastMeta);
359
- this.hasMeta = true;
360
- }
361
409
  }
362
410
  this.events.push(event);
363
411
  this.payloadBytesEstimation += eventBytes;
@@ -375,26 +423,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
375
423
  if (!recorder) return;
376
424
  recorder.takeFullSnapshot();
377
425
  }
378
- setTimestamps(event) {
426
+ setTimestamps() {
379
427
  // fallbacks if timestamps cannot be derived from rrweb events
380
- this.timestamp.cycle.last = (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
381
- if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
382
- // timestamps based on rrweb events
383
- if (!event || !event.timestamp) return;
384
- if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
385
- this.timestamp.event.last = event.timestamp;
428
+ if (!this.cycleTimestamp) this.cycleTimestamp = (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
386
429
  }
387
430
  clearTimestamps() {
388
- this.timestamp = {
389
- event: {
390
- first: undefined,
391
- last: undefined
392
- },
393
- cycle: {
394
- first: undefined,
395
- last: undefined
396
- }
397
- };
431
+ this.cycleTimestamp = undefined;
398
432
  }
399
433
 
400
434
  /** Estimate the payload size */
@@ -405,8 +439,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
405
439
  }
406
440
 
407
441
  /** Abort the feature, once aborted it will not resume */
408
- abort(reason) {
409
- (0, _console.warn)("SR aborted -- ".concat(reason));
442
+ abort() {
443
+ let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
444
+ (0, _console.warn)("SR aborted -- ".concat(reason.message));
445
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(reason.sm)], undefined, _features.FEATURE_NAMES.metrics, this.ee);
410
446
  this.blocked = true;
411
447
  this.mode = _sessionEntity.MODE.OFF;
412
448
  this.stopRecording();
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.FeatureBase = void 0;
7
- var _config = require("../../common/config/config");
8
7
  var _contextualEe = require("../../common/event-emitter/contextual-ee");
9
8
  class FeatureBase {
10
9
  constructor(agentIdentifier, aggregator, featureName) {
@@ -13,7 +12,7 @@ class FeatureBase {
13
12
  /** @type {Aggregator} */
14
13
  this.aggregator = aggregator;
15
14
  /** @type {ContextualEE} */
16
- this.ee = _contextualEe.ee.get(agentIdentifier, (0, _config.getRuntime)(this.agentIdentifier).isolatedBacklog);
15
+ this.ee = _contextualEe.ee.get(agentIdentifier);
17
16
  /** @type {string} */
18
17
  this.featureName = featureName;
19
18
  /**
@@ -26,7 +26,7 @@ const CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stored unde
26
26
  exports.CUSTOM_ATTR_GROUP = CUSTOM_ATTR_GROUP;
27
27
  function setTopLevelCallers() {
28
28
  const nr = (0, _nreum.gosCDN)();
29
- const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
29
+ const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
30
30
  funcs.forEach(f => {
31
31
  nr[f] = function () {
32
32
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
@@ -53,7 +53,7 @@ function setAPI(agentIdentifier, forceDrain) {
53
53
  const apiInterface = {};
54
54
  var instanceEE = _contextualEe.ee.get(agentIdentifier);
55
55
  var tracerEE = instanceEE.get('tracer');
56
- var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease'];
56
+ var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
57
57
  var prefix = 'api-';
58
58
  var spaPrefix = prefix + 'ixn-';
59
59
 
@@ -10,19 +10,13 @@ var _contextualEe = require("../../common/event-emitter/contextual-ee");
10
10
  var _handle = require("../../common/event-emitter/handle");
11
11
  var _registerHandler = require("../../common/event-emitter/register-handler");
12
12
  var _invoke = require("../../common/util/invoke");
13
- var submitData = _interopRequireWildcard(require("../../common/util/submit-data"));
14
- var _runtime = require("../../common/constants/runtime");
15
13
  var _constants = require("../../features/metrics/constants");
16
- function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
17
- function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
18
14
  function setAPI(agentIdentifier) {
19
15
  var instanceEE = _contextualEe.ee.get(agentIdentifier);
20
- var cycle = 0;
21
16
  var api = {
22
17
  finished: (0, _invoke.single)(finished),
23
18
  setErrorHandler,
24
19
  addToTrace,
25
- inlineHit,
26
20
  addRelease
27
21
  };
28
22
 
@@ -58,37 +52,6 @@ function setAPI(agentIdentifier) {
58
52
  };
59
53
  (0, _handle.handle)('bstApi', [report], undefined, _features.FEATURE_NAMES.sessionTrace, instanceEE);
60
54
  }
61
-
62
- // NREUM.inlineHit(requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime)
63
- //
64
- // requestName - the 'web page' name or service name
65
- // queueTime - the amount of time spent in the app tier queue
66
- // appTime - the amount of time spent in the application code
67
- // totalBackEndTime - the total roundtrip time of the remote service call
68
- // domTime - the time spent processing the result of the service call (or user defined)
69
- // frontEndTime - the time spent rendering the result of the service call (or user defined)
70
- function inlineHit(t, requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime) {
71
- if (!_runtime.isBrowserScope) return;
72
- requestName = window.encodeURIComponent(requestName);
73
- cycle += 1;
74
- const agentInfo = (0, _config.getInfo)(agentIdentifier);
75
- if (!agentInfo.beacon) return;
76
- const agentInit = (0, _config.getConfiguration)(agentIdentifier);
77
- const scheme = agentInit.ssl === false ? 'http' : 'https';
78
- const beacon = agentInit.proxy.beacon || agentInfo.beacon;
79
- let url = "".concat(scheme, "://").concat(beacon, "/1/").concat(agentInfo.licenseKey);
80
- url += '?a=' + agentInfo.applicationID + '&';
81
- url += 't=' + requestName + '&';
82
- url += 'qt=' + ~~queueTime + '&';
83
- url += 'ap=' + ~~appTime + '&';
84
- url += 'be=' + ~~totalBackEndTime + '&';
85
- url += 'dc=' + ~~domTime + '&';
86
- url += 'fe=' + ~~frontEndTime + '&';
87
- url += 'c=' + cycle;
88
- submitData.xhr({
89
- url
90
- });
91
- }
92
55
  function setErrorHandler(t, handler) {
93
56
  (0, _config.getRuntime)(agentIdentifier).onerror = handler;
94
57
  }
@@ -47,7 +47,7 @@ function configure(agentIdentifier) {
47
47
  if (!alreadySetOnce) {
48
48
  alreadySetOnce = true;
49
49
  if (updatedInit.proxy.assets) {
50
- (0, _publicPath.redefinePublicPath)(updatedInit.proxy.assets + '/'); // much like the info.beacon & init.proxy.beacon, this input should not end in a slash, but one is needed for webpack concat
50
+ (0, _publicPath.redefinePublicPath)(updatedInit.proxy.assets);
51
51
  internalTrafficList.push(updatedInit.proxy.assets);
52
52
  }
53
53
  if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon);
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.redefinePublicPath = void 0;
7
7
  // Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
8
8
 
9
- const redefinePublicPath = url => {
10
- // There's no URL validation here, so caller should check arg if need be.
11
- __webpack_public_path__ = url; // eslint-disable-line
9
+ const redefinePublicPath = urlString => {
10
+ const isOrigin = urlString.startsWith('http');
11
+ // Input is not expected to end in a slash, but webpack concats as-is, so one is inserted.
12
+ urlString += '/';
13
+ // If there's no existing HTTP scheme, the secure protocol is prepended by default.
14
+ __webpack_public_path__ = isOrigin ? urlString : 'https://' + urlString; // eslint-disable-line
12
15
  };
13
16
  exports.redefinePublicPath = redefinePublicPath;
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.242.0";
9
+ export const VERSION = "1.243.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.242.0";
9
+ export const VERSION = "1.243.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -11,6 +11,7 @@ import { getModeledObject } from '../config/state/configurable';
11
11
  import { handle } from '../event-emitter/handle';
12
12
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
13
13
  import { FEATURE_NAMES } from '../../loaders/features/features';
14
+ import { windowAddEventListener } from '../event-listener/event-listener-opts';
14
15
  export const MODE = {
15
16
  OFF: 0,
16
17
  FULL: 1,
@@ -24,13 +25,19 @@ const model = {
24
25
  expiresAt: 0,
25
26
  updatedAt: Date.now(),
26
27
  sessionReplay: MODE.OFF,
28
+ sessionReplaySentFirstChunk: false,
27
29
  sessionTraceMode: MODE.OFF,
28
30
  custom: {}
29
31
  };
30
32
  export const SESSION_EVENTS = {
31
33
  PAUSE: 'session-pause',
32
34
  RESET: 'session-reset',
33
- RESUME: 'session-resume'
35
+ RESUME: 'session-resume',
36
+ UPDATE: 'session-update'
37
+ };
38
+ export const SESSION_EVENT_TYPES = {
39
+ SAME_TAB: 'same-tab',
40
+ CROSS_TAB: 'cross-tab'
34
41
  };
35
42
  export class SessionEntity {
36
43
  /**
@@ -57,6 +64,15 @@ export class SessionEntity {
57
64
  this.ee = ee.get(agentIdentifier);
58
65
  wrapEvents(this.ee);
59
66
  this.setup(opts);
67
+ if (isBrowserScope) {
68
+ windowAddEventListener('storage', event => {
69
+ if (event.key === this.lookupKey) {
70
+ const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue;
71
+ this.sync(obj);
72
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state]);
73
+ }
74
+ });
75
+ }
60
76
  }
61
77
  setup(_ref) {
62
78
  let {
@@ -190,6 +206,7 @@ export class SessionEntity {
190
206
  //
191
207
  // TODO - compression would need happen here if we decide to do it
192
208
  this.storage.set(this.lookupKey, stringify(this.state));
209
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state]);
193
210
  return data;
194
211
  } catch (e) {
195
212
  // storage is inaccessible
@@ -210,5 +210,5 @@ function copy(from, to, emitter) {
210
210
  * @returns {boolean} Whether the passed function is ineligible to be wrapped.
211
211
  */
212
212
  function notWrappable(fn) {
213
- return !(fn && fn instanceof Function && fn.apply && !fn[flag]);
213
+ return !(fn && typeof fn === 'function' && fn.apply && !fn[flag]);
214
214
  }
@@ -76,7 +76,6 @@ export class Aggregate extends AggregateBase {
76
76
  } else {
77
77
  hash = stringify([params.status, params.host, params.pathname]);
78
78
  }
79
- handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee);
80
79
 
81
80
  // store as metric
82
81
  aggregator.store('xhr', hash, params, metrics);
@@ -90,6 +89,7 @@ export class Aggregate extends AggregateBase {
90
89
  }
91
90
  return;
92
91
  }
92
+ handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee);
93
93
  var xhrContext = this;
94
94
  var event = {
95
95
  method: params.method,