@newrelic/browser-agent 1.247.0 → 1.248.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.248.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.247.0...v1.248.0) (2023-11-16)
7
+
8
+
9
+ ### Features
10
+
11
+ * Report enduser.id with Session Replay ([#815](https://github.com/newrelic/newrelic-browser-agent/issues/815)) ([8f5446d](https://github.com/newrelic/newrelic-browser-agent/commit/8f5446d1f7679f6a5ea0ba90eb082d1d4deb0d93))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * Fix issue with errors forcefully triggering session traces ([#819](https://github.com/newrelic/newrelic-browser-agent/issues/819)) ([3872c35](https://github.com/newrelic/newrelic-browser-agent/commit/3872c35a173f76644b663df5ca0474971451b7cf))
17
+
6
18
  ## [1.247.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.246.1...v1.247.0) (2023-11-14)
7
19
 
8
20
 
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.247.0";
15
+ const VERSION = "1.248.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.247.0";
15
+ const VERSION = "1.248.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -71,8 +71,10 @@ const MAX_PAYLOAD_SIZE = 1000000;
71
71
  /** Unloading caps around 64kb */
72
72
  exports.MAX_PAYLOAD_SIZE = MAX_PAYLOAD_SIZE;
73
73
  const IDEAL_PAYLOAD_SIZE = 64000;
74
- /** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
74
+ /** Reserved room for query param attrs */
75
75
  exports.IDEAL_PAYLOAD_SIZE = IDEAL_PAYLOAD_SIZE;
76
+ const QUERY_PARAM_PADDING = 5000;
77
+ /** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
76
78
  const CHECKOUT_MS = {
77
79
  [_sessionEntity.MODE.ERROR]: 15000,
78
80
  [_sessionEntity.MODE.FULL]: 300000,
@@ -298,6 +300,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
298
300
  getHarvestContents() {
299
301
  const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
300
302
  const info = (0, _config.getInfo)(this.agentIdentifier);
303
+ const endUserId = info.jsAttributes?.['enduser.id'];
301
304
  if (this.backloggedEvents.length) this.events = [...this.backloggedEvents, ...this.events];
302
305
 
303
306
  // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
@@ -330,6 +333,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
330
333
  app_id: info.applicationID,
331
334
  protocol_version: '0',
332
335
  attributes: (0, _encode.obj)({
336
+ // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
337
+ // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
333
338
  ...(this.shouldCompress && {
334
339
  content_encoding: 'gzip'
335
340
  }),
@@ -347,8 +352,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
347
352
  hasError: this.hasError,
348
353
  isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
349
354
  decompressedBytes: this.payloadBytesEstimation,
350
- 'rrweb.version': _env.RRWEB_VERSION
351
- }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
355
+ 'rrweb.version': _env.RRWEB_VERSION,
356
+ // customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
357
+ ...(endUserId && {
358
+ 'enduser.id': endUserId
359
+ })
360
+ // The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
361
+ }, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
352
362
  },
353
363
 
354
364
  body: this.events
@@ -470,8 +480,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
470
480
  /** Estimate the payload size */
471
481
  getPayloadSize() {
472
482
  let newBytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
473
- // the 1KB gives us some padding for the other metadata
474
- return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000;
483
+ // the query param padding constant gives us some padding for the other metadata to be safely injected
484
+ return this.estimateCompression(this.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING;
475
485
  }
476
486
 
477
487
  /**
@@ -102,6 +102,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
102
102
  * "external" input in this case means errors thrown on the page or session replay itself being triggered to run in full mode by the API, which updates the session entity.
103
103
  */
104
104
  const switchToFull = () => {
105
+ if (this.agentRuntime?.session?.state?.sessionReplayMode !== _sessionEntity.MODE.FULL) return;
105
106
  if (mostRecentModeKnown !== _sessionEntity.MODE.FULL) {
106
107
  const prevMode = mostRecentModeKnown;
107
108
  mostRecentModeKnown = _sessionEntity.MODE.FULL;
@@ -23,6 +23,8 @@ var _firstContentfulPaint = require("../../../common/vitals/first-contentful-pai
23
23
  var _firstPaint = require("../../../common/vitals/first-paint");
24
24
  var _bundleId = require("../../../common/ids/bundle-id");
25
25
  var _runtime = require("../../../common/constants/runtime");
26
+ var _handle = require("../../../common/event-emitter/handle");
27
+ var _constants2 = require("../../metrics/constants");
26
28
  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); }
27
29
  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; }
28
30
  /*
@@ -672,6 +674,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
672
674
  }
673
675
  baseEE.emit('interactionSaved', [interaction]);
674
676
  state.interactionsToHarvest.push(interaction);
677
+ let smCategory = 'RouteChange';
678
+ if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad';else if (interaction.root?.attrs?.trigger === 'api') smCategory = 'Custom';
679
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["Spa/Interaction/".concat(smCategory, "/Duration/Ms"), Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, _features.FEATURE_NAMES.metrics, baseEE);
675
680
  scheduler.scheduleHarvest(0);
676
681
  }
677
682
  function isEnabled() {
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.247.0";
9
+ export const VERSION = "1.248.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.247.0";
9
+ export const VERSION = "1.248.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -63,6 +63,8 @@ let recorder, gzipper, u8;
63
63
  export const MAX_PAYLOAD_SIZE = 1000000;
64
64
  /** Unloading caps around 64kb */
65
65
  export const IDEAL_PAYLOAD_SIZE = 64000;
66
+ /** Reserved room for query param attrs */
67
+ const QUERY_PARAM_PADDING = 5000;
66
68
  /** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
67
69
  const CHECKOUT_MS = {
68
70
  [MODE.ERROR]: 15000,
@@ -289,6 +291,7 @@ export class Aggregate extends AggregateBase {
289
291
  getHarvestContents() {
290
292
  const agentRuntime = getRuntime(this.agentIdentifier);
291
293
  const info = getInfo(this.agentIdentifier);
294
+ const endUserId = info.jsAttributes?.['enduser.id'];
292
295
  if (this.backloggedEvents.length) this.events = [...this.backloggedEvents, ...this.events];
293
296
 
294
297
  // do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
@@ -321,6 +324,8 @@ export class Aggregate extends AggregateBase {
321
324
  app_id: info.applicationID,
322
325
  protocol_version: '0',
323
326
  attributes: encodeObj({
327
+ // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
328
+ // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
324
329
  ...(this.shouldCompress && {
325
330
  content_encoding: 'gzip'
326
331
  }),
@@ -338,8 +343,13 @@ export class Aggregate extends AggregateBase {
338
343
  hasError: this.hasError,
339
344
  isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
340
345
  decompressedBytes: this.payloadBytesEstimation,
341
- 'rrweb.version': RRWEB_VERSION
342
- }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
346
+ 'rrweb.version': RRWEB_VERSION,
347
+ // customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
348
+ ...(endUserId && {
349
+ 'enduser.id': endUserId
350
+ })
351
+ // The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
352
+ }, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
343
353
  },
344
354
 
345
355
  body: this.events
@@ -461,8 +471,8 @@ export class Aggregate extends AggregateBase {
461
471
  /** Estimate the payload size */
462
472
  getPayloadSize() {
463
473
  let newBytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
464
- // the 1KB gives us some padding for the other metadata
465
- return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000;
474
+ // the query param padding constant gives us some padding for the other metadata to be safely injected
475
+ return this.estimateCompression(this.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING;
466
476
  }
467
477
 
468
478
  /**
@@ -95,6 +95,7 @@ export class Aggregate extends AggregateBase {
95
95
  * "external" input in this case means errors thrown on the page or session replay itself being triggered to run in full mode by the API, which updates the session entity.
96
96
  */
97
97
  const switchToFull = () => {
98
+ if (this.agentRuntime?.session?.state?.sessionReplayMode !== MODE.FULL) return;
98
99
  if (mostRecentModeKnown !== MODE.FULL) {
99
100
  const prevMode = mostRecentModeKnown;
100
101
  mostRecentModeKnown = MODE.FULL;
@@ -22,6 +22,8 @@ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-pa
22
22
  import { firstPaint } from '../../../common/vitals/first-paint';
23
23
  import { bundleId } from '../../../common/ids/bundle-id';
24
24
  import { loadedAsDeferredBrowserScript } from '../../../common/constants/runtime';
25
+ import { handle } from '../../../common/event-emitter/handle';
26
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
25
27
  const {
26
28
  FEATURE_NAME,
27
29
  INTERACTION_EVENTS,
@@ -663,6 +665,9 @@ export class Aggregate extends AggregateBase {
663
665
  }
664
666
  baseEE.emit('interactionSaved', [interaction]);
665
667
  state.interactionsToHarvest.push(interaction);
668
+ let smCategory = 'RouteChange';
669
+ if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad';else if (interaction.root?.attrs?.trigger === 'api') smCategory = 'Custom';
670
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ["Spa/Interaction/".concat(smCategory, "/Duration/Ms"), Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, FEATURE_NAMES.metrics, baseEE);
666
671
  scheduler.scheduleHarvest(0);
667
672
  }
668
673
  function isEnabled() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA6BA,mCAAmC;;;;;;;;;AAoCnC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAIvC;IACE,2BAAiC;IACjC,mDAuHC;IArHC,iHAAiH;IACjH,cAAgB;IAChB,mFAAmF;IACnF,wBAA0B;IAC1B,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;IAEzB,0BAA0B;IAC1B,kBAAqB;IAOrB,uIAAuI;IACvI,0BAAyE;IA0BvE,wCAKQ;IAuCZ,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CAuDhB;IAED;;;;;;;;;oBAkBC;IAED;;;;;;;;;MA0DC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBASC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCAsCC;IAED,0HAA0H;IAC1H,yBAGC;IAED,sBAGC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBASC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BApe6B,4BAA4B;iCALzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA6BA,mCAAmC;;;;;;;;;AAoCnC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAMvC;IACE,2BAAiC;IACjC,mDAuHC;IArHC,iHAAiH;IACjH,cAAgB;IAChB,mFAAmF;IACnF,wBAA0B;IAC1B,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;IAEzB,0BAA0B;IAC1B,kBAAqB;IAOrB,uIAAuI;IACvI,0BAAyE;IA0BvE,wCAKQ;IAuCZ,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CAuDhB;IAED;;;;;;;;;oBAkBC;IAED;;;;;;;;;MAgEC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBASC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCAsCC;IAED,0HAA0H;IAC1H,yBAGC;IAED,sBAGC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBASC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA5e6B,4BAA4B;iCALzB,2CAA2C"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEA0IC;IAxIC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IAwHxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD;;;;;;;YAkCM;0GAC8F;;YAE9F;0FAC8E;;YAE9E,4IAA4I;;;;;;MAMjJ;IAED,mEA6BC;;CACF;8BA1gB6B,4BAA4B;6BAH7B,2BAA2B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEA4IC;IA1IC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IA0HxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD;;;;;;;YAkCM;0GAC8F;;YAE9F;0FAC8E;;YAE9E,4IAA4I;;;;;;MAMjJ;IAED,mEA6BC;;CACF;8BA5gB6B,4BAA4B;6BAH7B,2BAA2B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA6BA;IACE,2BAAiC;IACjC,mDAgsBC;IA7rBC;;;;;;;;;;;;;;;;;MAkBC;IAED,uBAAsC;CA0qBzC;8BA7sB6B,4BAA4B;2BAJ/B,cAAc"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA+BA;IACE,2BAAiC;IACjC,mDAssBC;IAnsBC;;;;;;;;;;;;;;;;;MAkBC;IAED,uBAAsC;CAgrBzC;8BArtB6B,4BAA4B;2BAJ/B,cAAc"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.247.0",
3
+ "version": "1.248.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -67,6 +67,8 @@ let recorder, gzipper, u8
67
67
  export const MAX_PAYLOAD_SIZE = 1000000
68
68
  /** Unloading caps around 64kb */
69
69
  export const IDEAL_PAYLOAD_SIZE = 64000
70
+ /** Reserved room for query param attrs */
71
+ const QUERY_PARAM_PADDING = 5000
70
72
  /** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
71
73
  const CHECKOUT_MS = { [MODE.ERROR]: 15000, [MODE.FULL]: 300000, [MODE.OFF]: 0 }
72
74
 
@@ -292,6 +294,7 @@ export class Aggregate extends AggregateBase {
292
294
  getHarvestContents () {
293
295
  const agentRuntime = getRuntime(this.agentIdentifier)
294
296
  const info = getInfo(this.agentIdentifier)
297
+ const endUserId = info.jsAttributes?.['enduser.id']
295
298
 
296
299
  if (this.backloggedEvents.length) this.events = [...this.backloggedEvents, ...this.events]
297
300
 
@@ -327,6 +330,8 @@ export class Aggregate extends AggregateBase {
327
330
  app_id: info.applicationID,
328
331
  protocol_version: '0',
329
332
  attributes: encodeObj({
333
+ // this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
334
+ // if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
330
335
  ...(this.shouldCompress && { content_encoding: 'gzip' }),
331
336
  'replay.firstTimestamp': firstTimestamp,
332
337
  'replay.firstTimestampOffset': firstTimestamp - agentOffset,
@@ -342,8 +347,11 @@ export class Aggregate extends AggregateBase {
342
347
  hasError: this.hasError,
343
348
  isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
344
349
  decompressedBytes: this.payloadBytesEstimation,
345
- 'rrweb.version': RRWEB_VERSION
346
- }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
350
+ 'rrweb.version': RRWEB_VERSION,
351
+ // customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
352
+ ...(endUserId && { 'enduser.id': endUserId })
353
+ // The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
354
+ }, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
347
355
  },
348
356
  body: this.events
349
357
  }
@@ -459,8 +467,8 @@ export class Aggregate extends AggregateBase {
459
467
 
460
468
  /** Estimate the payload size */
461
469
  getPayloadSize (newBytes = 0) {
462
- // the 1KB gives us some padding for the other metadata
463
- return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000
470
+ // the query param padding constant gives us some padding for the other metadata to be safely injected
471
+ return this.estimateCompression(this.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING
464
472
  }
465
473
 
466
474
  /**
@@ -89,6 +89,8 @@ export class Aggregate extends AggregateBase {
89
89
  * "external" input in this case means errors thrown on the page or session replay itself being triggered to run in full mode by the API, which updates the session entity.
90
90
  */
91
91
  const switchToFull = () => {
92
+ if (this.agentRuntime?.session?.state?.sessionReplayMode !== MODE.FULL) return
93
+
92
94
  if (mostRecentModeKnown !== MODE.FULL) {
93
95
  const prevMode = mostRecentModeKnown
94
96
  mostRecentModeKnown = MODE.FULL
@@ -22,6 +22,8 @@ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-pa
22
22
  import { firstPaint } from '../../../common/vitals/first-paint'
23
23
  import { bundleId } from '../../../common/ids/bundle-id'
24
24
  import { loadedAsDeferredBrowserScript } from '../../../common/constants/runtime'
25
+ import { handle } from '../../../common/event-emitter/handle'
26
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
25
27
 
26
28
  const {
27
29
  FEATURE_NAME, INTERACTION_EVENTS, MAX_TIMER_BUDGET, FN_START, FN_END, CB_START, INTERACTION_API, REMAINING,
@@ -724,6 +726,12 @@ export class Aggregate extends AggregateBase {
724
726
  }
725
727
  baseEE.emit('interactionSaved', [interaction])
726
728
  state.interactionsToHarvest.push(interaction)
729
+
730
+ let smCategory = 'RouteChange'
731
+ if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad'
732
+ else if (interaction.root?.attrs?.trigger === 'api') smCategory = 'Custom'
733
+ handle(SUPPORTABILITY_METRIC_CHANNEL, [`Spa/Interaction/${smCategory}/Duration/Ms`, Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, FEATURE_NAMES.metrics, baseEE)
734
+
727
735
  scheduler.scheduleHarvest(0)
728
736
  }
729
737