@newrelic/browser-agent 1.242.0 → 1.243.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +1465 -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 +84 -54
  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 +83 -54
  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 +81 -37
  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.0";
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.0";
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,7 +16,6 @@ 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 _features = require("../../../loaders/features/features");
20
19
  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
20
  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; } /*
22
21
  * Copyright 2023 New Relic Corporation. All rights reserved.
@@ -33,6 +32,33 @@ const RRWEB_VERSION = '2.0.0-alpha.8';
33
32
  exports.RRWEB_VERSION = RRWEB_VERSION;
34
33
  const AVG_COMPRESSION = 0.12;
35
34
  exports.AVG_COMPRESSION = AVG_COMPRESSION;
35
+ const RRWEB_EVENT_TYPES = {
36
+ DomContentLoaded: 0,
37
+ Load: 1,
38
+ FullSnapshot: 2,
39
+ IncrementalSnapshot: 3,
40
+ Meta: 4,
41
+ Custom: 5
42
+ };
43
+ exports.RRWEB_EVENT_TYPES = RRWEB_EVENT_TYPES;
44
+ const ABORT_REASONS = {
45
+ RESET: {
46
+ message: 'Session was reset',
47
+ sm: 'Reset'
48
+ },
49
+ IMPORT: {
50
+ message: 'Recorder failed to import',
51
+ sm: 'Import'
52
+ },
53
+ TOO_MANY: {
54
+ message: '429: Too Many Requests',
55
+ sm: 'Too-Many'
56
+ },
57
+ TOO_BIG: {
58
+ message: 'Payload was too large',
59
+ sm: 'Too-Big'
60
+ }
61
+ };
36
62
  let recorder, gzipper, u8;
37
63
 
38
64
  /** Vortex caps payload sizes at 1MB */
@@ -68,8 +94,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
68
94
  /** can shut off efforts to compress the data */
69
95
  this.shouldCompress = true;
70
96
 
71
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
72
- this.isFirstChunk = false;
73
97
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
74
98
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
75
99
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -83,16 +107,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
83
107
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
84
108
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
85
109
  */
86
- this.timestamp = {
87
- event: {
88
- first: undefined,
89
- last: undefined
90
- },
91
- cycle: {
92
- first: undefined,
93
- last: undefined
94
- }
95
- };
110
+ this.cycleTimestamp = undefined;
96
111
 
97
112
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
98
113
  this.payloadBytesEstimation = 0;
@@ -106,7 +121,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
106
121
  if (shouldSetup) {
107
122
  // 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
123
  this.ee.on(_sessionEntity.SESSION_EVENTS.RESET, () => {
109
- this.abort('Session Reset');
124
+ this.abort(ABORT_REASONS.RESET);
110
125
  });
111
126
 
112
127
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -115,9 +130,19 @@ class Aggregate extends _aggregateBase.AggregateBase {
115
130
  });
116
131
  // 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
132
  this.ee.on(_sessionEntity.SESSION_EVENTS.RESUME, () => {
133
+ // if the mode changed on a different tab, it needs to update this instance to match
134
+ const {
135
+ session
136
+ } = (0, _config.getRuntime)(this.agentIdentifier);
137
+ this.mode = session.state.sessionReplay;
118
138
  if (!this.initialized || this.mode === _sessionEntity.MODE.OFF) return;
119
139
  this.startRecording();
120
140
  });
141
+ this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (type, data) => {
142
+ if (!this.initialized || this.blocked || type !== _sessionEntity.SESSION_EVENT_TYPES.CROSS_TAB) return;
143
+ if (this.mode !== _sessionEntity.MODE.OFF && data.sessionReplay === _sessionEntity.MODE.OFF) this.abort('Session Entity was set to OFF on another tab');
144
+ this.mode = data.sessionReplay;
145
+ });
121
146
 
122
147
  // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
123
148
  this.scheduler = new _harvestScheduler.HarvestScheduler('browser/blobs', {
@@ -133,7 +158,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
133
158
  this.hasError = true;
134
159
  this.errorNoticed = true;
135
160
  // run once
136
- if (this.mode === _sessionEntity.MODE.ERROR) {
161
+ if (this.mode === _sessionEntity.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
137
162
  this.mode = _sessionEntity.MODE.FULL;
138
163
  // if the error was noticed AFTER the recorder was already imported....
139
164
  if (recorder && this.initialized) {
@@ -193,7 +218,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
193
218
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
194
219
  recorder = (await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'rrweb')))).record;
195
220
  } catch (err) {
196
- return this.abort('Recorder failed to import');
221
+ return this.abort(ABORT_REASONS.IMPORT);
197
222
  }
198
223
 
199
224
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -216,7 +241,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
216
241
  this.shouldCompress = false;
217
242
  }
218
243
  this.startRecording();
219
- this.isFirstChunk = !!session.isNew;
220
244
  this.syncWithSessionManager({
221
245
  sessionReplay: this.mode
222
246
  });
@@ -231,14 +255,39 @@ class Aggregate extends _aggregateBase.AggregateBase {
231
255
  this.scheduler.opts.gzip = false;
232
256
  }
233
257
  // TODO -- Gracefully handle the buffer for retries.
258
+ const {
259
+ session
260
+ } = (0, _config.getRuntime)(this.agentIdentifier);
261
+ if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
262
+ sessionReplaySentFirstChunk: true
263
+ });
234
264
  this.clearBuffer();
235
265
  return [payload];
236
266
  }
237
267
  getHarvestContents() {
238
268
  const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
239
269
  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;
270
+
271
+ // do not let the last node be a meta node, since this NEEDS to precede a snapshot
272
+ // we will manually inject it later if we find a payload that is missing a meta node
273
+ const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
274
+ if (payloadEndsWithMeta) {
275
+ this.lastMeta = this.events[this.events.length - 1];
276
+ this.events = this.events.slice(0, this.events.length - 1);
277
+ this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
278
+ }
279
+
280
+ // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
281
+ // we will manually inject it if this happens
282
+ const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
283
+ if (payloadStartsWithFullSnapshot) {
284
+ this.hasMeta = true;
285
+ this.events.unshift(this.lastMeta);
286
+ }
287
+ const firstEventTimestamp = this.events[0]?.timestamp;
288
+ const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
289
+ const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
290
+ const lastTimestamp = lastEventTimestamp || (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
242
291
  return {
243
292
  qs: {
244
293
  browser_monitoring_key: info.licenseKey,
@@ -252,12 +301,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
252
301
  'replay.firstTimestamp': firstTimestamp,
253
302
  'replay.lastTimestamp': lastTimestamp,
254
303
  'replay.durationMs': lastTimestamp - firstTimestamp,
304
+ 'replay.nodes': this.events.length,
255
305
  agentVersion: agentRuntime.version,
256
306
  session: agentRuntime.session.state.value,
257
307
  hasMeta: this.hasMeta,
258
308
  hasSnapshot: this.hasSnapshot,
259
309
  hasError: this.hasError,
260
- isFirstChunk: this.isFirstChunk,
310
+ isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
261
311
  decompressedBytes: this.payloadBytesEstimation,
262
312
  'nr.rrweb.version': RRWEB_VERSION
263
313
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
@@ -269,7 +319,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
269
319
  onHarvestFinished(result) {
270
320
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
271
321
  if (result.status === 429) {
272
- this.abort('429: Too many requests');
322
+ this.abort(ABORT_REASONS.TOO_MANY);
273
323
  }
274
324
  if (this.blocked) this.scheduler.stopTimer(true);
275
325
  }
@@ -277,7 +327,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
277
327
  /** Clears the buffer (this.events), and resets all payload metadata properties */
278
328
  clearBuffer() {
279
329
  this.events = [];
280
- this.isFirstChunk = false;
281
330
  this.hasSnapshot = false;
282
331
  this.hasMeta = false;
283
332
  this.hasError = false;
@@ -289,7 +338,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
289
338
  startRecording() {
290
339
  if (!recorder) {
291
340
  (0, _console.warn)('Recording library was never imported');
292
- return this.abort('Recorder was never imported');
341
+ return this.abort(ABORT_REASONS.IMPORT);
293
342
  }
294
343
  this.clearTimestamps();
295
344
  // set the fallbacks as early as possible
@@ -325,7 +374,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
325
374
 
326
375
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
327
376
  store(event, isCheckout) {
328
- this.setTimestamps(event);
377
+ this.setTimestamps();
329
378
  if (this.blocked) return;
330
379
  const eventBytes = (0, _stringify.stringify)(event).length;
331
380
  /** The estimated size of the payload after compression */
@@ -333,8 +382,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
333
382
  // Vortex will block payloads at a certain size, we might as well not send.
334
383
  if (payloadSize > MAX_PAYLOAD_SIZE) {
335
384
  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');
385
+ return this.abort(ABORT_REASONS.TOO_BIG);
338
386
  }
339
387
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
340
388
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -345,19 +393,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
345
393
  }
346
394
 
347
395
  // meta event
348
- if (event.type === 4) {
396
+ if (event.type === RRWEB_EVENT_TYPES.Meta) {
349
397
  this.hasMeta = true;
350
398
  this.lastMeta = event;
351
399
  }
352
400
  // snapshot event
353
- if (event.type === 2) {
401
+ if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
354
402
  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
403
  }
362
404
  this.events.push(event);
363
405
  this.payloadBytesEstimation += eventBytes;
@@ -375,26 +417,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
375
417
  if (!recorder) return;
376
418
  recorder.takeFullSnapshot();
377
419
  }
378
- setTimestamps(event) {
420
+ setTimestamps() {
379
421
  // 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;
422
+ if (!this.cycleTimestamp) this.cycleTimestamp = (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
386
423
  }
387
424
  clearTimestamps() {
388
- this.timestamp = {
389
- event: {
390
- first: undefined,
391
- last: undefined
392
- },
393
- cycle: {
394
- first: undefined,
395
- last: undefined
396
- }
397
- };
425
+ this.cycleTimestamp = undefined;
398
426
  }
399
427
 
400
428
  /** Estimate the payload size */
@@ -405,8 +433,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
405
433
  }
406
434
 
407
435
  /** Abort the feature, once aborted it will not resume */
408
- abort(reason) {
409
- (0, _console.warn)("SR aborted -- ".concat(reason));
436
+ abort() {
437
+ let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
438
+ (0, _console.warn)("SR aborted -- ".concat(reason.message));
439
+ this.ee.emit(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(ABORT_REASONS[reason.sm])]);
410
440
  this.blocked = true;
411
441
  this.mode = _sessionEntity.MODE.OFF;
412
442
  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.0";
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.0";
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,