@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
@@ -15,18 +15,43 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
15
15
  import { FEATURE_NAME } from '../constants';
16
16
  import { stringify } from '../../../common/util/stringify';
17
17
  import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
18
- import { SESSION_EVENTS, MODE } from '../../../common/session/session-entity';
18
+ import { SESSION_EVENTS, MODE, SESSION_EVENT_TYPES } from '../../../common/session/session-entity';
19
19
  import { AggregateBase } from '../../utils/aggregate-base';
20
20
  import { sharedChannel } from '../../../common/constants/shared-channel';
21
21
  import { obj as encodeObj } from '../../../common/url/encode';
22
22
  import { warn } from '../../../common/util/console';
23
23
  import { globalScope } from '../../../common/constants/runtime';
24
24
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
25
- import { FEATURE_NAMES } from '../../../loaders/features/features';
26
25
 
27
26
  // would be better to get this dynamically in some way
28
27
  export const RRWEB_VERSION = '2.0.0-alpha.8';
29
28
  export const AVG_COMPRESSION = 0.12;
29
+ export const RRWEB_EVENT_TYPES = {
30
+ DomContentLoaded: 0,
31
+ Load: 1,
32
+ FullSnapshot: 2,
33
+ IncrementalSnapshot: 3,
34
+ Meta: 4,
35
+ Custom: 5
36
+ };
37
+ const ABORT_REASONS = {
38
+ RESET: {
39
+ message: 'Session was reset',
40
+ sm: 'Reset'
41
+ },
42
+ IMPORT: {
43
+ message: 'Recorder failed to import',
44
+ sm: 'Import'
45
+ },
46
+ TOO_MANY: {
47
+ message: '429: Too Many Requests',
48
+ sm: 'Too-Many'
49
+ },
50
+ TOO_BIG: {
51
+ message: 'Payload was too large',
52
+ sm: 'Too-Big'
53
+ }
54
+ };
30
55
  let recorder, gzipper, u8;
31
56
 
32
57
  /** Vortex caps payload sizes at 1MB */
@@ -60,8 +85,6 @@ export class Aggregate extends AggregateBase {
60
85
  /** can shut off efforts to compress the data */
61
86
  this.shouldCompress = true;
62
87
 
63
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
64
- this.isFirstChunk = false;
65
88
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
66
89
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
67
90
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -75,16 +98,7 @@ export class Aggregate extends AggregateBase {
75
98
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
76
99
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
77
100
  */
78
- this.timestamp = {
79
- event: {
80
- first: undefined,
81
- last: undefined
82
- },
83
- cycle: {
84
- first: undefined,
85
- last: undefined
86
- }
87
- };
101
+ this.cycleTimestamp = undefined;
88
102
 
89
103
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
90
104
  this.payloadBytesEstimation = 0;
@@ -98,7 +112,7 @@ export class Aggregate extends AggregateBase {
98
112
  if (shouldSetup) {
99
113
  // 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.
100
114
  this.ee.on(SESSION_EVENTS.RESET, () => {
101
- this.abort('Session Reset');
115
+ this.abort(ABORT_REASONS.RESET);
102
116
  });
103
117
 
104
118
  // The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
@@ -107,9 +121,19 @@ export class Aggregate extends AggregateBase {
107
121
  });
108
122
  // 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.
109
123
  this.ee.on(SESSION_EVENTS.RESUME, () => {
124
+ // if the mode changed on a different tab, it needs to update this instance to match
125
+ const {
126
+ session
127
+ } = getRuntime(this.agentIdentifier);
128
+ this.mode = session.state.sessionReplay;
110
129
  if (!this.initialized || this.mode === MODE.OFF) return;
111
130
  this.startRecording();
112
131
  });
132
+ this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
133
+ if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
134
+ if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort('Session Entity was set to OFF on another tab');
135
+ this.mode = data.sessionReplay;
136
+ });
113
137
 
114
138
  // Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
115
139
  this.scheduler = new HarvestScheduler('browser/blobs', {
@@ -125,7 +149,7 @@ export class Aggregate extends AggregateBase {
125
149
  this.hasError = true;
126
150
  this.errorNoticed = true;
127
151
  // run once
128
- if (this.mode === MODE.ERROR) {
152
+ if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
129
153
  this.mode = MODE.FULL;
130
154
  // if the error was noticed AFTER the recorder was already imported....
131
155
  if (recorder && this.initialized) {
@@ -185,7 +209,7 @@ export class Aggregate extends AggregateBase {
185
209
  // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
186
210
  recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
187
211
  } catch (err) {
188
- return this.abort('Recorder failed to import');
212
+ return this.abort(ABORT_REASONS.IMPORT);
189
213
  }
190
214
 
191
215
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -208,7 +232,6 @@ export class Aggregate extends AggregateBase {
208
232
  this.shouldCompress = false;
209
233
  }
210
234
  this.startRecording();
211
- this.isFirstChunk = !!session.isNew;
212
235
  this.syncWithSessionManager({
213
236
  sessionReplay: this.mode
214
237
  });
@@ -223,14 +246,39 @@ export class Aggregate extends AggregateBase {
223
246
  this.scheduler.opts.gzip = false;
224
247
  }
225
248
  // TODO -- Gracefully handle the buffer for retries.
249
+ const {
250
+ session
251
+ } = getRuntime(this.agentIdentifier);
252
+ if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
253
+ sessionReplaySentFirstChunk: true
254
+ });
226
255
  this.clearBuffer();
227
256
  return [payload];
228
257
  }
229
258
  getHarvestContents() {
230
259
  const agentRuntime = getRuntime(this.agentIdentifier);
231
260
  const info = getInfo(this.agentIdentifier);
232
- const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first;
233
- const lastTimestamp = this.timestamp.event.last || this.timestamp.cycle.last;
261
+
262
+ // do not let the last node be a meta node, since this NEEDS to precede a snapshot
263
+ // we will manually inject it later if we find a payload that is missing a meta node
264
+ const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
265
+ if (payloadEndsWithMeta) {
266
+ this.lastMeta = this.events[this.events.length - 1];
267
+ this.events = this.events.slice(0, this.events.length - 1);
268
+ this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
269
+ }
270
+
271
+ // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
272
+ // we will manually inject it if this happens
273
+ const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
274
+ if (payloadStartsWithFullSnapshot) {
275
+ this.hasMeta = true;
276
+ this.events.unshift(this.lastMeta);
277
+ }
278
+ const firstEventTimestamp = this.events[0]?.timestamp;
279
+ const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
280
+ const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
281
+ const lastTimestamp = lastEventTimestamp || getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
234
282
  return {
235
283
  qs: {
236
284
  browser_monitoring_key: info.licenseKey,
@@ -244,12 +292,13 @@ export class Aggregate extends AggregateBase {
244
292
  'replay.firstTimestamp': firstTimestamp,
245
293
  'replay.lastTimestamp': lastTimestamp,
246
294
  'replay.durationMs': lastTimestamp - firstTimestamp,
295
+ 'replay.nodes': this.events.length,
247
296
  agentVersion: agentRuntime.version,
248
297
  session: agentRuntime.session.state.value,
249
298
  hasMeta: this.hasMeta,
250
299
  hasSnapshot: this.hasSnapshot,
251
300
  hasError: this.hasError,
252
- isFirstChunk: this.isFirstChunk,
301
+ isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
253
302
  decompressedBytes: this.payloadBytesEstimation,
254
303
  'nr.rrweb.version': RRWEB_VERSION
255
304
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
@@ -261,7 +310,7 @@ export class Aggregate extends AggregateBase {
261
310
  onHarvestFinished(result) {
262
311
  // The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
263
312
  if (result.status === 429) {
264
- this.abort('429: Too many requests');
313
+ this.abort(ABORT_REASONS.TOO_MANY);
265
314
  }
266
315
  if (this.blocked) this.scheduler.stopTimer(true);
267
316
  }
@@ -269,7 +318,6 @@ export class Aggregate extends AggregateBase {
269
318
  /** Clears the buffer (this.events), and resets all payload metadata properties */
270
319
  clearBuffer() {
271
320
  this.events = [];
272
- this.isFirstChunk = false;
273
321
  this.hasSnapshot = false;
274
322
  this.hasMeta = false;
275
323
  this.hasError = false;
@@ -281,7 +329,7 @@ export class Aggregate extends AggregateBase {
281
329
  startRecording() {
282
330
  if (!recorder) {
283
331
  warn('Recording library was never imported');
284
- return this.abort('Recorder was never imported');
332
+ return this.abort(ABORT_REASONS.IMPORT);
285
333
  }
286
334
  this.clearTimestamps();
287
335
  // set the fallbacks as early as possible
@@ -317,7 +365,7 @@ export class Aggregate extends AggregateBase {
317
365
 
318
366
  /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
319
367
  store(event, isCheckout) {
320
- this.setTimestamps(event);
368
+ this.setTimestamps();
321
369
  if (this.blocked) return;
322
370
  const eventBytes = stringify(event).length;
323
371
  /** The estimated size of the payload after compression */
@@ -325,8 +373,7 @@ export class Aggregate extends AggregateBase {
325
373
  // Vortex will block payloads at a certain size, we might as well not send.
326
374
  if (payloadSize > MAX_PAYLOAD_SIZE) {
327
375
  this.clearBuffer();
328
- this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Too-Big/Seen'], undefined, FEATURE_NAMES.metrics, this.ee);
329
- return this.abort('Payload too big');
376
+ return this.abort(ABORT_REASONS.TOO_BIG);
330
377
  }
331
378
  // Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
332
379
  // to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
@@ -337,19 +384,13 @@ export class Aggregate extends AggregateBase {
337
384
  }
338
385
 
339
386
  // meta event
340
- if (event.type === 4) {
387
+ if (event.type === RRWEB_EVENT_TYPES.Meta) {
341
388
  this.hasMeta = true;
342
389
  this.lastMeta = event;
343
390
  }
344
391
  // snapshot event
345
- if (event.type === 2) {
392
+ if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
346
393
  this.hasSnapshot = true;
347
- // small chance that the meta event got separated from its matching snapshot across payload harvests
348
- // it needs to precede the snapshot, so shove it in first.
349
- if (!this.hasMeta) {
350
- this.events.push(this.lastMeta);
351
- this.hasMeta = true;
352
- }
353
394
  }
354
395
  this.events.push(event);
355
396
  this.payloadBytesEstimation += eventBytes;
@@ -367,26 +408,12 @@ export class Aggregate extends AggregateBase {
367
408
  if (!recorder) return;
368
409
  recorder.takeFullSnapshot();
369
410
  }
370
- setTimestamps(event) {
411
+ setTimestamps() {
371
412
  // fallbacks if timestamps cannot be derived from rrweb events
372
- this.timestamp.cycle.last = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
373
- if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
374
- // timestamps based on rrweb events
375
- if (!event || !event.timestamp) return;
376
- if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
377
- this.timestamp.event.last = event.timestamp;
413
+ if (!this.cycleTimestamp) this.cycleTimestamp = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
378
414
  }
379
415
  clearTimestamps() {
380
- this.timestamp = {
381
- event: {
382
- first: undefined,
383
- last: undefined
384
- },
385
- cycle: {
386
- first: undefined,
387
- last: undefined
388
- }
389
- };
416
+ this.cycleTimestamp = undefined;
390
417
  }
391
418
 
392
419
  /** Estimate the payload size */
@@ -397,8 +424,10 @@ export class Aggregate extends AggregateBase {
397
424
  }
398
425
 
399
426
  /** Abort the feature, once aborted it will not resume */
400
- abort(reason) {
401
- warn("SR aborted -- ".concat(reason));
427
+ abort() {
428
+ let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
429
+ warn("SR aborted -- ".concat(reason.message));
430
+ this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(ABORT_REASONS[reason.sm])]);
402
431
  this.blocked = true;
403
432
  this.mode = MODE.OFF;
404
433
  this.stopRecording();
@@ -1,4 +1,3 @@
1
- import { getRuntime } from '../../common/config/config';
2
1
  import { ee } from '../../common/event-emitter/contextual-ee';
3
2
  export class FeatureBase {
4
3
  constructor(agentIdentifier, aggregator, featureName) {
@@ -7,7 +6,7 @@ export class FeatureBase {
7
6
  /** @type {Aggregator} */
8
7
  this.aggregator = aggregator;
9
8
  /** @type {ContextualEE} */
10
- this.ee = ee.get(agentIdentifier, getRuntime(this.agentIdentifier).isolatedBacklog);
9
+ this.ee = ee.get(agentIdentifier);
11
10
  /** @type {string} */
12
11
  this.featureName = featureName;
13
12
  /**
@@ -17,7 +17,7 @@ export const CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stor
17
17
 
18
18
  export function setTopLevelCallers() {
19
19
  const nr = gosCDN();
20
- const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
20
+ const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
21
21
  funcs.forEach(f => {
22
22
  nr[f] = function () {
23
23
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
@@ -44,7 +44,7 @@ export function setAPI(agentIdentifier, forceDrain) {
44
44
  const apiInterface = {};
45
45
  var instanceEE = ee.get(agentIdentifier);
46
46
  var tracerEE = instanceEE.get('tracer');
47
- var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'inlineHit', 'addRelease'];
47
+ var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
48
48
  var prefix = 'api-';
49
49
  var spaPrefix = prefix + 'ixn-';
50
50
 
@@ -1,20 +1,16 @@
1
1
  import { FEATURE_NAMES } from '../features/features';
2
- import { getConfiguration, getInfo, getRuntime } from '../../common/config/config';
2
+ import { getRuntime } from '../../common/config/config';
3
3
  import { ee } from '../../common/event-emitter/contextual-ee';
4
4
  import { handle } from '../../common/event-emitter/handle';
5
5
  import { registerHandler } from '../../common/event-emitter/register-handler';
6
6
  import { single } from '../../common/util/invoke';
7
- import * as submitData from '../../common/util/submit-data';
8
- import { isBrowserScope } from '../../common/constants/runtime';
9
7
  import { CUSTOM_METRIC_CHANNEL } from '../../features/metrics/constants';
10
8
  export function setAPI(agentIdentifier) {
11
9
  var instanceEE = ee.get(agentIdentifier);
12
- var cycle = 0;
13
10
  var api = {
14
11
  finished: single(finished),
15
12
  setErrorHandler,
16
13
  addToTrace,
17
- inlineHit,
18
14
  addRelease
19
15
  };
20
16
 
@@ -50,37 +46,6 @@ export function setAPI(agentIdentifier) {
50
46
  };
51
47
  handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, instanceEE);
52
48
  }
53
-
54
- // NREUM.inlineHit(requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime)
55
- //
56
- // requestName - the 'web page' name or service name
57
- // queueTime - the amount of time spent in the app tier queue
58
- // appTime - the amount of time spent in the application code
59
- // totalBackEndTime - the total roundtrip time of the remote service call
60
- // domTime - the time spent processing the result of the service call (or user defined)
61
- // frontEndTime - the time spent rendering the result of the service call (or user defined)
62
- function inlineHit(t, requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime) {
63
- if (!isBrowserScope) return;
64
- requestName = window.encodeURIComponent(requestName);
65
- cycle += 1;
66
- const agentInfo = getInfo(agentIdentifier);
67
- if (!agentInfo.beacon) return;
68
- const agentInit = getConfiguration(agentIdentifier);
69
- const scheme = agentInit.ssl === false ? 'http' : 'https';
70
- const beacon = agentInit.proxy.beacon || agentInfo.beacon;
71
- let url = "".concat(scheme, "://").concat(beacon, "/1/").concat(agentInfo.licenseKey);
72
- url += '?a=' + agentInfo.applicationID + '&';
73
- url += 't=' + requestName + '&';
74
- url += 'qt=' + ~~queueTime + '&';
75
- url += 'ap=' + ~~appTime + '&';
76
- url += 'be=' + ~~totalBackEndTime + '&';
77
- url += 'dc=' + ~~domTime + '&';
78
- url += 'fe=' + ~~frontEndTime + '&';
79
- url += 'c=' + cycle;
80
- submitData.xhr({
81
- url
82
- });
83
- }
84
49
  function setErrorHandler(t, handler) {
85
50
  getRuntime(agentIdentifier).onerror = handler;
86
51
  }
@@ -41,7 +41,7 @@ export function configure(agentIdentifier) {
41
41
  if (!alreadySetOnce) {
42
42
  alreadySetOnce = true;
43
43
  if (updatedInit.proxy.assets) {
44
- 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
44
+ redefinePublicPath(updatedInit.proxy.assets);
45
45
  internalTrafficList.push(updatedInit.proxy.assets);
46
46
  }
47
47
  if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon);
@@ -1,6 +1,9 @@
1
1
  // Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
2
2
 
3
- export const redefinePublicPath = url => {
4
- // There's no URL validation here, so caller should check arg if need be.
5
- __webpack_public_path__ = url; // eslint-disable-line
3
+ export const redefinePublicPath = urlString => {
4
+ const isOrigin = urlString.startsWith('http');
5
+ // Input is not expected to end in a slash, but webpack concats as-is, so one is inserted.
6
+ urlString += '/';
7
+ // If there's no existing HTTP scheme, the secure protocol is prepended by default.
8
+ __webpack_public_path__ = isOrigin ? urlString : 'https://' + urlString; // eslint-disable-line
6
9
  };
@@ -7,6 +7,11 @@ export namespace SESSION_EVENTS {
7
7
  const PAUSE: string;
8
8
  const RESET: string;
9
9
  const RESUME: string;
10
+ const UPDATE: string;
11
+ }
12
+ export namespace SESSION_EVENT_TYPES {
13
+ const SAME_TAB: string;
14
+ const CROSS_TAB: string;
10
15
  }
11
16
  export class SessionEntity {
12
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":";;;;;;;;;;AAoCA;IACE;;;;;OAKG;IACH,uBAgBC;IAVC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAKnC;;;;aAsEC;IA/DC,8BAA0B;IAC1B,+BAA4B;IAc1B,gCAOqC;IAUrC,4CAiBsC;IAOV,2BAA6C;IAM3E,iCAAuB;IAIzB,wBAEC;IAED,sBAEC;IAED;;;OAGG;IACH,QAFa,MAAM,CA6BlB;IAED;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,MAAM,CAiBlB;IAED,gBAuBC;IAED;;OAEG;IACH,gBAIC;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAED,yDAYC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBA1RqB,gBAAgB;iCAGL,4BAA4B"}
1
+ {"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AA4CA;IACE;;;;;OAKG;IACH,uBA0BC;IApBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAenC;;;;aAsEC;IA/DC,8BAA0B;IAC1B,+BAA4B;IAc1B,gCAOqC;IAUrC,4CAiBsC;IAOV,2BAA6C;IAM3E,iCAAuB;IAIzB,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,yDAYC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBA7SqB,gBAAgB;iCAGL,4BAA4B"}
@@ -1,5 +1,13 @@
1
1
  export const RRWEB_VERSION: "2.0.0-alpha.8";
2
2
  export const AVG_COMPRESSION: 0.12;
3
+ export namespace RRWEB_EVENT_TYPES {
4
+ const DomContentLoaded: number;
5
+ const Load: number;
6
+ const FullSnapshot: number;
7
+ const IncrementalSnapshot: number;
8
+ const Meta: number;
9
+ const Custom: number;
10
+ }
3
11
  /** Vortex caps payload sizes at 1MB */
4
12
  export const MAX_PAYLOAD_SIZE: 1000000;
5
13
  /** Unloading caps around 64kb */
@@ -21,8 +29,6 @@ export class Aggregate extends AggregateBase {
21
29
  recording: boolean;
22
30
  /** can shut off efforts to compress the data */
23
31
  shouldCompress: boolean;
24
- /** Payload metadata -- Should indicate that the payload being sent is the first of a session */
25
- isFirstChunk: boolean;
26
32
  /** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
27
33
  * -- When the recording library begins recording, it starts by taking a DOM snapshot
28
34
  * -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
@@ -35,16 +41,7 @@ export class Aggregate extends AggregateBase {
35
41
  /** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
36
42
  * cycle timestamps are used as fallbacks if event timestamps cannot be used
37
43
  */
38
- timestamp: {
39
- event: {
40
- first: undefined;
41
- last: undefined;
42
- };
43
- cycle: {
44
- first: undefined;
45
- last: undefined;
46
- };
47
- };
44
+ cycleTimestamp: any;
48
45
  /** A value which increments with every new mutation node reported. Resets after a harvest is sent */
49
46
  payloadBytesEstimation: number;
50
47
  /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
@@ -89,12 +86,12 @@ export class Aggregate extends AggregateBase {
89
86
  store(event: any, isCheckout: any): void;
90
87
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
91
88
  takeFullSnapshot(): void;
92
- setTimestamps(event: any): void;
89
+ setTimestamps(): void;
93
90
  clearTimestamps(): void;
94
91
  /** Estimate the payload size */
95
92
  getPayloadSize(newBytes?: number): any;
96
93
  /** Abort the feature, once aborted it will not resume */
97
- abort(reason: any): void;
94
+ abort(reason?: {}): void;
98
95
  /** Extensive research has yielded about an 88% compression factor on these payloads.
99
96
  * This is an estimation using that factor as to not cause performance issues while evaluating
100
97
  * https://staging.onenr.io/037jbJWxbjy
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA2BA,4CAA4C;AAE5C,mCAAmC;AAInC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAIvC;IACE,2BAAiC;IACjC,mDAmGC;IAjGC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,mEAAmE;IACnE,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAGpB,iEAAiE;IACjE,mBAAsB;IACtB,gDAAgD;IAChD,wBAA0B;IAE1B,gGAAgG;IAChG,sBAAyB;IACzB;;;MAGE;IACF,qBAAwB;IACxB,4IAA4I;IAC5I,iBAAoB;IACpB,+HAA+H;IAC/H,kBAAqB;IAErB;;OAEG;IACH;;;;;;;;;MAA+G;IAE/G,qGAAqG;IACrG,+BAA+B;IAE/B,kIAAkI;IAClI,cAAyB;IAOzB,uIAAuI;IACvI,0BAAyE;IAiBvE,wCAKQ;IA+BZ;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CAyDhB;IAED;;;;;;;;;oBAaC;IAED;;;;;;;;;MA4BC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAQC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCA6CC;IAED,0HAA0H;IAC1H,yBAGC;IAED,gCAQC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED,yDAAyD;IACzD,yBAQC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA/X6B,4BAA4B;iCALzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA0BA,4CAA4C;AAE5C,mCAAmC;;;;;;;;;AAgCnC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAIvC;IACE,2BAAiC;IACjC,mDA2GC;IAzGC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,mEAAmE;IACnE,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAGpB,iEAAiE;IACjE,mBAAsB;IACtB,gDAAgD;IAChD,wBAA0B;IAE1B;;;MAGE;IACF,qBAAwB;IACxB,4IAA4I;IAC5I,iBAAoB;IACpB,+HAA+H;IAC/H,kBAAqB;IAErB;;OAEG;IACH,oBAA+B;IAE/B,qGAAqG;IACrG,+BAA+B;IAE/B,kIAAkI;IAClI,cAAyB;IAOzB,uIAAuI;IACvI,0BAAyE;IA0BvE,wCAKQ;IAgCZ;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CAuDhB;IAED;;;;;;;;;oBAeC;IAED;;;;;;;;;MAiDC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAOC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCAsCC;IAED,0HAA0H;IAC1H,yBAGC;IAED,sBAGC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED,yDAAyD;IACzD,yBASC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA3a6B,4BAA4B;iCALzB,2CAA2C"}
@@ -1 +1 @@
1
- {"version":3,"file":"feature-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/feature-base.js"],"names":[],"mappings":"AAGA;IACE,qEAeC;IAdC,qBAAqB;IACrB,iBADW,MAAM,CACqB;IACtC,yBAAyB;IACzB,uBAA4B;IAC5B,2BAA2B;IAC3B,iBAAmF;IACnF,qBAAqB;IACrB,aADW,MAAM,CACa;IAC9B;;;;OAIG;IACH,SAFU,OAAO,CAEG;CAEvB"}
1
+ {"version":3,"file":"feature-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/feature-base.js"],"names":[],"mappings":"AAEA;IACE,qEAeC;IAdC,qBAAqB;IACrB,iBADW,MAAM,CACqB;IACtC,yBAAyB;IACzB,uBAA4B;IAC5B,2BAA2B;IAC3B,iBAAiC;IACjC,qBAAqB;IACrB,aADW,MAAM,CACa;IAC9B;;;;OAIG;IACH,SAFU,OAAO,CAEG;CAEvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IA0DE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;EA0FvB;AA3LD,0CAA0C"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IAyDE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;EA0FvB;AA1LD,0CAA0C"}
@@ -1 +1 @@
1
- {"version":3,"file":"apiAsync.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/apiAsync.js"],"names":[],"mappings":"AAUA,mDAkFC"}
1
+ {"version":3,"file":"apiAsync.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/apiAsync.js"],"names":[],"mappings":"AAQA,mDA8CC"}
@@ -1,2 +1,2 @@
1
- export function redefinePublicPath(url: any): void;
1
+ export function redefinePublicPath(urlString: any): void;
2
2
  //# sourceMappingURL=public-path.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-path.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.js"],"names":[],"mappings":"AAEO,mDAGN"}
1
+ {"version":3,"file":"public-path.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.js"],"names":[],"mappings":"AAEO,yDAMN"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.242.0",
3
+ "version": "1.243.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -266,6 +266,6 @@
266
266
  "src",
267
267
  "LICENSE",
268
268
  "README.md",
269
- "types.ts"
269
+ "CHANGELOG.md"
270
270
  ]
271
271
  }
@@ -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
 
15
16
  export const MODE = {
16
17
  OFF: 0,
@@ -25,13 +26,20 @@ const model = {
25
26
  expiresAt: 0,
26
27
  updatedAt: Date.now(),
27
28
  sessionReplay: MODE.OFF,
29
+ sessionReplaySentFirstChunk: false,
28
30
  sessionTraceMode: MODE.OFF,
29
31
  custom: {}
30
32
  }
31
33
  export const SESSION_EVENTS = {
32
34
  PAUSE: 'session-pause',
33
35
  RESET: 'session-reset',
34
- RESUME: 'session-resume'
36
+ RESUME: 'session-resume',
37
+ UPDATE: 'session-update'
38
+ }
39
+
40
+ export const SESSION_EVENT_TYPES = {
41
+ SAME_TAB: 'same-tab',
42
+ CROSS_TAB: 'cross-tab'
35
43
  }
36
44
 
37
45
  export class SessionEntity {
@@ -57,6 +65,16 @@ export class SessionEntity {
57
65
  this.ee = ee.get(agentIdentifier)
58
66
  wrapEvents(this.ee)
59
67
  this.setup(opts)
68
+
69
+ if (isBrowserScope) {
70
+ windowAddEventListener('storage', (event) => {
71
+ if (event.key === this.lookupKey) {
72
+ const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue
73
+ this.sync(obj)
74
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state])
75
+ }
76
+ })
77
+ }
60
78
  }
61
79
 
62
80
  setup ({ value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS }) {
@@ -189,6 +207,7 @@ export class SessionEntity {
189
207
  //
190
208
  // TODO - compression would need happen here if we decide to do it
191
209
  this.storage.set(this.lookupKey, stringify(this.state))
210
+ this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state])
192
211
  return data
193
212
  } catch (e) {
194
213
  // storage is inaccessible
@@ -215,5 +215,5 @@ function copy (from, to, emitter) {
215
215
  * @returns {boolean} Whether the passed function is ineligible to be wrapped.
216
216
  */
217
217
  function notWrappable (fn) {
218
- return !(fn && fn instanceof Function && fn.apply && !fn[flag])
218
+ return !(fn && typeof fn === 'function' && fn.apply && !fn[flag])
219
219
  }
@@ -81,8 +81,6 @@ export class Aggregate extends AggregateBase {
81
81
  hash = stringify([params.status, params.host, params.pathname])
82
82
  }
83
83
 
84
- handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee)
85
-
86
84
  // store as metric
87
85
  aggregator.store('xhr', hash, params, metrics)
88
86
 
@@ -98,6 +96,8 @@ export class Aggregate extends AggregateBase {
98
96
  return
99
97
  }
100
98
 
99
+ handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee)
100
+
101
101
  var xhrContext = this
102
102
 
103
103
  var event = {