@newrelic/browser-agent 1.245.0 → 1.246.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 (70) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/cdn/polyfills.js +2 -1
  3. package/dist/cjs/common/config/state/configurable.js +1 -1
  4. package/dist/cjs/common/config/state/init.js +1 -0
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/session/session-entity.js +3 -2
  8. package/dist/cjs/common/url/parse-url.js +20 -44
  9. package/dist/cjs/common/vitals/first-input-delay.js +1 -2
  10. package/dist/cjs/common/vitals/largest-contentful-paint.js +1 -2
  11. package/dist/cjs/common/vitals/vital-metric.js +2 -12
  12. package/dist/cjs/features/ajax/aggregate/index.js +1 -0
  13. package/dist/cjs/features/jserrors/aggregate/index.js +1 -1
  14. package/dist/cjs/features/page_view_event/aggregate/index.js +2 -0
  15. package/dist/cjs/features/page_view_timing/aggregate/index.js +10 -1
  16. package/dist/cjs/features/session_replay/aggregate/index.js +6 -6
  17. package/dist/cjs/features/session_trace/aggregate/index.js +14 -3
  18. package/dist/cjs/features/spa/aggregate/index.js +5 -3
  19. package/dist/cjs/features/utils/instrument-base.js +1 -1
  20. package/dist/esm/cdn/polyfills.js +2 -1
  21. package/dist/esm/common/config/state/configurable.js +1 -1
  22. package/dist/esm/common/config/state/init.js +1 -0
  23. package/dist/esm/common/constants/env.cdn.js +1 -1
  24. package/dist/esm/common/constants/env.npm.js +1 -1
  25. package/dist/esm/common/session/session-entity.js +3 -2
  26. package/dist/esm/common/url/parse-url.js +21 -45
  27. package/dist/esm/common/vitals/first-input-delay.js +1 -2
  28. package/dist/esm/common/vitals/largest-contentful-paint.js +1 -2
  29. package/dist/esm/common/vitals/vital-metric.js +1 -11
  30. package/dist/esm/features/ajax/aggregate/index.js +1 -0
  31. package/dist/esm/features/jserrors/aggregate/index.js +1 -1
  32. package/dist/esm/features/page_view_event/aggregate/index.js +2 -0
  33. package/dist/esm/features/page_view_timing/aggregate/index.js +9 -0
  34. package/dist/esm/features/session_replay/aggregate/index.js +6 -6
  35. package/dist/esm/features/session_trace/aggregate/index.js +14 -3
  36. package/dist/esm/features/spa/aggregate/index.js +5 -3
  37. package/dist/esm/features/utils/instrument-base.js +1 -1
  38. package/dist/types/common/config/state/configurable.d.ts.map +1 -1
  39. package/dist/types/common/config/state/init.d.ts.map +1 -1
  40. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  41. package/dist/types/common/url/parse-url.d.ts +12 -1
  42. package/dist/types/common/url/parse-url.d.ts.map +1 -1
  43. package/dist/types/common/vitals/vital-metric.d.ts +1 -2
  44. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
  45. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  46. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  47. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  48. package/dist/types/features/session_trace/aggregate/index.d.ts +1 -0
  49. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  50. package/dist/types/features/spa/aggregate/index.d.ts +1 -0
  51. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  52. package/dist/types/loaders/configure/public-path.npm.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/cdn/polyfills.js +1 -0
  55. package/src/common/config/state/configurable.js +2 -1
  56. package/src/common/config/state/init.js +1 -0
  57. package/src/common/session/session-entity.js +3 -2
  58. package/src/common/url/parse-url.js +21 -51
  59. package/src/common/vitals/first-input-delay.js +1 -2
  60. package/src/common/vitals/largest-contentful-paint.js +1 -2
  61. package/src/common/vitals/vital-metric.js +2 -12
  62. package/src/features/ajax/aggregate/index.js +2 -0
  63. package/src/features/jserrors/aggregate/index.js +1 -1
  64. package/src/features/page_view_event/aggregate/index.js +2 -0
  65. package/src/features/page_view_timing/aggregate/index.js +11 -0
  66. package/src/features/session_replay/aggregate/index.js +6 -6
  67. package/src/features/session_trace/aggregate/index.js +10 -2
  68. package/src/features/spa/aggregate/index.js +5 -3
  69. package/src/features/utils/instrument-base.js +1 -1
  70. package/src/loaders/configure/public-path.npm.js +0 -1
@@ -100,6 +100,7 @@ export class Aggregate extends AggregateBase {
100
100
  }
101
101
  addTiming(name, value, attrs) {
102
102
  attrs = attrs || {};
103
+ addConnectionAttributes(attrs); // network conditions may differ from the actual for VitalMetrics when they were captured
103
104
 
104
105
  // If cls was set to another value by `onCLS`, then it's supported and is attached onto any timing but is omitted until such time.
105
106
  /*
@@ -170,4 +171,12 @@ export class Aggregate extends AggregateBase {
170
171
  }
171
172
  return payload;
172
173
  }
174
+ }
175
+ function addConnectionAttributes(obj) {
176
+ var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; // to date, both window & worker shares the same support for connection
177
+ if (!connection) return;
178
+ if (connection.type) obj['net-type'] = connection.type;
179
+ if (connection.effectiveType) obj['net-etype'] = connection.effectiveType;
180
+ if (connection.rtt) obj['net-rtt'] = connection.rtt;
181
+ if (connection.downlink) obj['net-dlink'] = connection.downlink;
173
182
  }
@@ -131,13 +131,13 @@ export class Aggregate extends AggregateBase {
131
131
  const {
132
132
  session
133
133
  } = getRuntime(this.agentIdentifier);
134
- this.mode = session.state.sessionReplay;
134
+ this.mode = session.state.sessionReplayMode;
135
135
  if (!this.initialized || this.mode === MODE.OFF) return;
136
136
  this.startRecording();
137
137
  });
138
138
  this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
139
139
  if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
140
- if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
140
+ if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
141
141
  this.mode = data.sessionReplay;
142
142
  });
143
143
 
@@ -163,7 +163,7 @@ export class Aggregate extends AggregateBase {
163
163
  this.startRecording();
164
164
  this.scheduler.startTimer(this.harvestTimeSeconds);
165
165
  this.syncWithSessionManager({
166
- sessionReplay: this.mode
166
+ sessionReplayMode: this.mode
167
167
  });
168
168
  }
169
169
  }
@@ -198,7 +198,7 @@ export class Aggregate extends AggregateBase {
198
198
  // session replays can continue if already in progress
199
199
  if (!session.isNew) {
200
200
  // inherit the mode of the existing session
201
- this.mode = session.state.sessionReplay;
201
+ this.mode = session.state.sessionReplayMode;
202
202
  } else {
203
203
  // The session is new... determine the mode the new session should start in
204
204
  if (fullSample) this.mode = MODE.FULL; // full mode has precedence over error mode
@@ -239,7 +239,7 @@ export class Aggregate extends AggregateBase {
239
239
  }
240
240
  this.startRecording();
241
241
  this.syncWithSessionManager({
242
- sessionReplay: this.mode
242
+ sessionReplayMode: this.mode
243
243
  });
244
244
  }
245
245
  prepareHarvest() {
@@ -448,7 +448,7 @@ export class Aggregate extends AggregateBase {
448
448
  this.mode = MODE.OFF;
449
449
  this.stopRecording();
450
450
  this.syncWithSessionManager({
451
- sessionReplay: this.mode
451
+ sessionReplayMode: this.mode
452
452
  });
453
453
  this.clearTimestamps();
454
454
  this.ee.emit('REPLAY_ABORTED');
@@ -141,7 +141,7 @@ export class Aggregate extends AggregateBase {
141
141
  });
142
142
  if (!sessionEntity.isNew) {
143
143
  // inherit the same mode as existing session's Trace
144
- if (sessionEntity.state.sessionReplay === MODE.OFF) this.isStandalone = true;
144
+ if (sessionEntity.state.sessionReplayMode === MODE.OFF) this.isStandalone = true;
145
145
  controlTraceOp(mostRecentModeKnown = sessionEntity.state.sessionTraceMode);
146
146
  } else {
147
147
  // for new sessions, see the truth table associated with NEWRELIC-8662 wrt the new Trace behavior under session management
@@ -503,6 +503,16 @@ export class Aggregate extends AggregateBase {
503
503
  }
504
504
  this.trace = {};
505
505
  this.nodeCount = 0;
506
+ let firstHarvestOfSession;
507
+ if (this.agentRuntime.session) {
508
+ const isFirstPayload = !this.agentRuntime.session.state.traceHarvestStarted;
509
+ firstHarvestOfSession = {
510
+ fsh: Number(isFirstPayload)
511
+ }; // converted to '0' | '1'
512
+ if (isFirstPayload) this.agentRuntime.session.write({
513
+ traceHarvestStarted: true
514
+ });
515
+ }
506
516
  return {
507
517
  qs: {
508
518
  st: this.agentRuntime.offset,
@@ -513,9 +523,10 @@ export class Aggregate extends AggregateBase {
513
523
  * so that blob parsing doesn't need to happen to support UI/API functions */
514
524
  fts: this.agentRuntime.offset + earliestTimeStamp,
515
525
  /** n === "nodeCount" in NR1, a count of nodes in the ST payload, so that blob parsing doesn't need to happen to support UI/API functions */
516
- n: stns.length // node count
526
+ n: stns.length,
527
+ // node count
528
+ ...firstHarvestOfSession
517
529
  },
518
-
519
530
  body: {
520
531
  res: stns
521
532
  }
@@ -58,7 +58,9 @@ export class Aggregate extends AggregateBase {
58
58
  depth: 0,
59
59
  harvestTimeSeconds: getConfigurationValue(agentIdentifier, 'spa.harvestTimeSeconds') || 10,
60
60
  interactionsToHarvest: [],
61
- interactionsSent: []
61
+ interactionsSent: [],
62
+ // The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
63
+ disableSpaFix: (getConfigurationValue(agentIdentifier, 'feature_flags') || []).indexOf('disable-spa-fix') > -1
62
64
  };
63
65
  this.serializer = new Serializer(this);
64
66
  const {
@@ -278,7 +280,7 @@ export class Aggregate extends AggregateBase {
278
280
  // context is stored on the xhr and is shared with all callbacks associated
279
281
  // with the new xhr
280
282
  register('new-xhr', function () {
281
- if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
283
+ if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
282
284
  /*
283
285
  * The previous interaction was discarded before a route change. Restore the interaction
284
286
  * in case this XHR is associated with a route change.
@@ -369,7 +371,7 @@ export class Aggregate extends AggregateBase {
369
371
  }, this.featureName, jsonpEE);
370
372
  register(FETCH_START, function (fetchArguments, dtPayload) {
371
373
  if (fetchArguments) {
372
- if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
374
+ if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
373
375
  /*
374
376
  * The previous interaction was discarded before a route change. Restore the interaction
375
377
  * in case this XHR is associated with a route change.
@@ -134,7 +134,7 @@ export class InstrumentBase extends FeatureBase {
134
134
  if (featureName === FEATURE_NAMES.sessionReplay) {
135
135
  if (!originals.MO) return false; // Session Replay cannot work without Mutation Observer
136
136
  if (getConfigurationValue(this.agentIdentifier, 'session_trace.enabled') === false) return false; // Session Replay as of now is tightly coupled with Session Trace in the UI
137
- return !!session?.isNew || !!session?.state.sessionReplay; // Session Replay should only try to run if already running from a previous page, or at the beginning of a session
137
+ return !!session?.isNew || !!session?.state.sessionReplayMode; // Session Replay should only try to run if already running from a previous page, or at the beginning of a session
138
138
  }
139
139
 
140
140
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"configurable.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/configurable.js"],"names":[],"mappings":"AAEA,4DAwBC"}
1
+ {"version":3,"file":"configurable.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/configurable.js"],"names":[],"mappings":"AAEA,4DAyBC"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"AA0GA,+CAIC;AAED,0DAIC;AAED,+DAYC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"AA2GA,+CAIC;AAED,0DAIC;AAED,+DAYC"}
@@ -1 +1 @@
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
+ {"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AA6CA;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;IAOxC,2BAA6C;IAM7C,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;sBA9SqB,gBAAgB;iCAGL,4BAA4B"}
@@ -1,2 +1,13 @@
1
- export function parseUrl(url: any): any;
1
+ export function parseUrl(url: any): {
2
+ port: string;
3
+ hostname: string;
4
+ pathname: string;
5
+ search: string;
6
+ protocol: string;
7
+ sameOrigin: boolean;
8
+ } | {
9
+ protocol: string;
10
+ } | {
11
+ protocol?: undefined;
12
+ };
2
13
  //# sourceMappingURL=parse-url.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-url.d.ts","sourceRoot":"","sources":["../../../../src/common/url/parse-url.js"],"names":[],"mappings":"AASA,wCA8DC"}
1
+ {"version":3,"file":"parse-url.d.ts","sourceRoot":"","sources":["../../../../src/common/url/parse-url.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;EAkCC"}
@@ -4,11 +4,10 @@ export class VitalMetric {
4
4
  name: any;
5
5
  attrs: {};
6
6
  roundingMethod: any;
7
- update({ value, entries, attrs, shouldAddConnectionAttributes }: {
7
+ update({ value, entries, attrs }: {
8
8
  value: any;
9
9
  entries?: any[] | undefined;
10
10
  attrs?: {} | undefined;
11
- shouldAddConnectionAttributes?: boolean | undefined;
12
11
  }): void;
13
12
  get current(): any;
14
13
  get isValid(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"vital-metric.d.ts","sourceRoot":"","sources":["../../../../src/common/vitals/vital-metric.js"],"names":[],"mappings":"AAAA;IAIE,4CAIC;IAND,eAAY;IAGV,UAAgB;IAChB,UAAe;IACf,oBAAwF;IAG1F;;;;;aAiBC;IAED,mBAOC;IAED,uBAEC;IAED,uEAMC;;CACF"}
1
+ {"version":3,"file":"vital-metric.d.ts","sourceRoot":"","sources":["../../../../src/common/vitals/vital-metric.js"],"names":[],"mappings":"AAAA;IAIE,4CAIC;IAND,eAAY;IAGV,UAAgB;IAChB,UAAe;IACf,oBAAwF;IAG1F;;;;aAiBC;IAED,mBAOC;IAED,uBAEC;IAED,uEAMC;;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAiBA;IACE,2BAAiC;IACjC,mDAuOC;IAhNC,qGAAwB;IACxB;;;;4BAAoC;IACpC;;;mBAA2E;CA+M9E;8BA7O6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAiBA;IACE,2BAAiC;IACjC,mDAyOC;IAlNC,qGAAwB;IACxB;;;;4BAAoC;IACpC;;;mBAA2E;CAiN9E;8BA/O6B,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAA2C;IAC3C,mDAoBC;IAjBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAiBhC,gBA8EC;CACF;8BA5G6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAA2C;IAC3C,mDAoBC;IAjBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAiBhC,gBAgFC;CACF;8BA9G6B,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,mDAoCC;IAjCC,eAAiB;IACjB,mBAAqB;IACrB,4BAA+B;IAuB7B,4BAGQ;IAOZ;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAqBC;IAED,qCAKC;IAED,gDAWC;IAGD;;;;kBAWC;IAGD,8BAuBC;;CACF;8BAnK6B,4BAA4B;iCANzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,mDAoCC;IAjCC,eAAiB;IACjB,mBAAqB;IACrB,4BAA+B;IAuB7B,4BAGQ;IAOZ;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAsBC;IAED,qCAKC;IAED,gDAWC;IAGD;;;;kBAWC;IAGD,8BAuBC;;CACF;8BApK6B,4BAA4B;iCANzB,2CAA2C"}
@@ -37,6 +37,7 @@ export class Aggregate extends AggregateBase {
37
37
  body?: undefined;
38
38
  } | {
39
39
  qs: {
40
+ fsh?: number | undefined;
40
41
  st: any;
41
42
  /** hr === "hasReplay" in NR1, standalone is always checked and processed before harvesting
42
43
  * so a race condition between ST and SR states should not be a concern if implemented here */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEAyHC;IAvHC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IAuGxC,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;;;;;;YA2BM;0GAC8F;;YAE9F;0FAC8E;;YAE9E,4IAA4I;;;;;;MAKjJ;IAED,mEA6BC;;CACF;8BAjf6B,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,iEAyHC;IAvHC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IAuGxC,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;8BAzf6B,4BAA4B;6BAH7B,2BAA2B"}
@@ -17,6 +17,7 @@ export class Aggregate extends AggregateBase {
17
17
  harvestTimeSeconds: any;
18
18
  interactionsToHarvest: never[];
19
19
  interactionsSent: never[];
20
+ disableSpaFix: boolean;
20
21
  };
21
22
  serializer: Serializer;
22
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA4BA;IACE,2BAAiC;IACjC,mDA8rBC;IA3rBC;;;;;;;;;;;;;;;;MAgBC;IAED,uBAAsC;CA0qBzC;8BA1sB6B,4BAA4B;2BAJ/B,cAAc"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA4BA;IACE,2BAAiC;IACjC,mDAgsBC;IA7rBC;;;;;;;;;;;;;;;;;MAkBC;IAED,uBAAsC;CA0qBzC;8BA5sB6B,4BAA4B;2BAJ/B,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"public-path.npm.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.npm.js"],"names":[],"mappings":"AACO,2CAEN"}
1
+ {"version":3,"file":"public-path.npm.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.npm.js"],"names":[],"mappings":"AAAO,2CAEN"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.245.0",
3
+ "version": "1.246.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -21,3 +21,4 @@ import 'core-js/stable/weak-set'
21
21
  import 'core-js/stable/object/get-own-property-descriptors'
22
22
  import 'core-js/stable/url'
23
23
  import 'core-js/stable/url-search-params'
24
+ import 'core-js/stable/string/starts-with'
@@ -13,7 +13,8 @@ export function getModeledObject (obj, model) {
13
13
  for (let key in target) {
14
14
  if (obj[key] !== undefined) {
15
15
  try {
16
- if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key])
16
+ if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]))
17
+ else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key])
17
18
  else output[key] = obj[key]
18
19
  } catch (e) {
19
20
  warn('An error occurred while setting a property of a Configurable', e)
@@ -29,6 +29,7 @@ const model = () => {
29
29
  }
30
30
  }
31
31
  return {
32
+ feature_flags: [],
32
33
  proxy: {
33
34
  assets: undefined, // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
34
35
  beacon: undefined // likewise for the url to which we send analytics
@@ -25,9 +25,10 @@ const model = {
25
25
  inactiveAt: 0,
26
26
  expiresAt: 0,
27
27
  updatedAt: Date.now(),
28
- sessionReplay: MODE.OFF,
28
+ sessionReplayMode: MODE.OFF,
29
29
  sessionReplaySentFirstChunk: false,
30
30
  sessionTraceMode: MODE.OFF,
31
+ traceHarvestStarted: false,
31
32
  custom: {}
32
33
  }
33
34
  export const SESSION_EVENTS = {
@@ -140,7 +141,7 @@ export class SessionEntity {
140
141
 
141
142
  // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
142
143
  // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
143
- if (this.isNew === undefined) this.isNew = !Object.keys(initialRead).length
144
+ this.isNew = !Object.keys(initialRead).length
144
145
  // if its a "new" session, we write to storage API with the default values. These values may change over the lifespan of the agent run.
145
146
  // we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
146
147
  if (this.isNew) this.write(getModeledObject(this.state, model), true)
@@ -3,15 +3,9 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
- import { globalScope, isBrowserScope } from '../constants/runtime'
7
-
8
- var stringsToParsedUrls = {}
6
+ import { globalScope } from '../constants/runtime'
9
7
 
10
8
  export function parseUrl (url) {
11
- if (url in stringsToParsedUrls) {
12
- return stringsToParsedUrls[url]
13
- }
14
-
15
9
  // Return if URL is a data URL, parseUrl assumes urls are http/https
16
10
  if ((url || '').indexOf('data:') === 0) {
17
11
  return {
@@ -19,54 +13,30 @@ export function parseUrl (url) {
19
13
  }
20
14
  }
21
15
 
22
- let urlEl
23
- var location = globalScope?.location
24
- var ret = {}
25
-
26
16
  try {
27
- urlEl = new URL(url, location.href)
28
- } catch (err) {
29
- if (isBrowserScope) {
30
- // Use an anchor dom element to resolve the url natively.
31
- urlEl = document.createElement('a')
32
- urlEl.href = url
33
- } else {
34
- return ret
17
+ const parsedUrl = new URL(url, location.href)
18
+ const returnVal = {
19
+ port: parsedUrl.port,
20
+ hostname: parsedUrl.hostname,
21
+ pathname: parsedUrl.pathname,
22
+ search: parsedUrl.search,
23
+ protocol: parsedUrl.protocol.slice(0, parsedUrl.protocol.indexOf(':')),
24
+ sameOrigin: parsedUrl.protocol === globalScope?.location?.protocol && parsedUrl.host === globalScope?.location?.host
35
25
  }
36
- }
37
-
38
- ret.port = urlEl.port
39
-
40
- ret.search = urlEl.search
41
26
 
42
- var firstSplit = urlEl.href.split('://')
43
-
44
- if (!ret.port && firstSplit[1]) {
45
- ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1]
46
- }
47
- if (!ret.port || ret.port === '0') ret.port = (firstSplit[0] === 'https' ? '443' : '80')
48
-
49
- // Host not provided in IE for relative urls
50
- ret.hostname = (urlEl.hostname || location.hostname)
51
-
52
- ret.pathname = urlEl.pathname
53
-
54
- ret.protocol = firstSplit[0]
55
-
56
- // Pathname sometimes doesn't have leading slash (IE 8 and 9)
57
- if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname
58
-
59
- // urlEl.protocol is ':' in old ie when protocol is not specified
60
- var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol
61
- var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port
27
+ if (!returnVal.port || returnVal.port === '') {
28
+ if (parsedUrl.protocol === 'http:') returnVal.port = '80'
29
+ if (parsedUrl.protocol === 'https:') returnVal.port = '443'
30
+ }
62
31
 
63
- // urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin
64
- ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain)
32
+ if (!returnVal.pathname || returnVal.pathname === '') {
33
+ returnVal.pathname = '/'
34
+ } else if (!returnVal.pathname.startsWith('/')) {
35
+ returnVal.pathname = `/${returnVal.pathname}`
36
+ }
65
37
 
66
- // Only cache if url doesn't have a path
67
- if (ret.pathname === '/') {
68
- stringsToParsedUrls[url] = ret
38
+ return returnVal
39
+ } catch (err) {
40
+ return {}
69
41
  }
70
-
71
- return ret
72
42
  }
@@ -13,8 +13,7 @@ if (isBrowserScope) {
13
13
  firstInputDelay.update({
14
14
  value: entries[0].startTime,
15
15
  entries,
16
- attrs: { type: entries[0].name, fid: Math.round(value) },
17
- shouldAddConnectionAttributes: true
16
+ attrs: { type: entries[0].name, fid: Math.round(value) }
18
17
  })
19
18
  })
20
19
  }
@@ -22,8 +22,7 @@ if (isBrowserScope) {
22
22
  ...(!!lcpEntry.url && { elUrl: cleanURL(lcpEntry.url) }),
23
23
  ...(!!lcpEntry.element?.tagName && { elTag: lcpEntry.element.tagName })
24
24
  }
25
- }),
26
- shouldAddConnectionAttributes: true
25
+ })
27
26
  })
28
27
  })
29
28
  }
@@ -8,7 +8,7 @@ export class VitalMetric {
8
8
  this.roundingMethod = typeof roundingMethod === 'function' ? roundingMethod : Math.floor
9
9
  }
10
10
 
11
- update ({ value, entries = [], attrs = {}, shouldAddConnectionAttributes = false }) {
11
+ update ({ value, entries = [], attrs = {} }) {
12
12
  if (value < 0) return
13
13
  const state = {
14
14
  value: this.roundingMethod(value),
@@ -16,7 +16,7 @@ export class VitalMetric {
16
16
  entries,
17
17
  attrs
18
18
  }
19
- if (shouldAddConnectionAttributes) addConnectionAttributes(state.attrs)
19
+
20
20
  this.history.push(state)
21
21
  this.#subscribers.forEach(cb => {
22
22
  try {
@@ -48,13 +48,3 @@ export class VitalMetric {
48
48
  return () => { this.#subscribers.delete(callback) }
49
49
  }
50
50
  }
51
-
52
- function addConnectionAttributes (obj) {
53
- var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection // to date, both window & worker shares the same support for connection
54
- if (!connection) return
55
-
56
- if (connection.type) obj['net-type'] = connection.type
57
- if (connection.effectiveType) obj['net-etype'] = connection.effectiveType
58
- if (connection.rtt) obj['net-rtt'] = connection.rtt
59
- if (connection.downlink) obj['net-dlink'] = connection.downlink
60
- }
@@ -126,6 +126,8 @@ export class Aggregate extends AggregateBase {
126
126
  query: this?.parsedOrigin?.search
127
127
  })
128
128
 
129
+ if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, ee)
130
+
129
131
  // if the ajax happened inside an interaction, hold it until the interaction finishes
130
132
  if (this.spaNode) {
131
133
  var interactionId = this.spaNode.interaction.id
@@ -191,7 +191,7 @@ export class Aggregate extends AggregateBase {
191
191
  this.pageviewReported[bucketHash] = true
192
192
  }
193
193
 
194
- if (agentRuntime?.session?.state?.sessionReplay) params.hasReplay = true
194
+ if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true
195
195
  params.firstOccurrenceTimestamp = this.observedAt[bucketHash]
196
196
 
197
197
  var type = internal ? 'ierr' : 'err'
@@ -69,6 +69,8 @@ export class Aggregate extends AggregateBase {
69
69
  at: info.atts
70
70
  }
71
71
 
72
+ if (agentRuntime.session) queryParameters.fsh = Number(agentRuntime.session.isNew) // "first session harvest" aka RUM request or PageView event of a session
73
+
72
74
  let body
73
75
  if (typeof info.jsAttributes === 'object' && Object.keys(info.jsAttributes).length > 0) {
74
76
  body = { ja: info.jsAttributes }
@@ -94,6 +94,7 @@ export class Aggregate extends AggregateBase {
94
94
 
95
95
  addTiming (name, value, attrs) {
96
96
  attrs = attrs || {}
97
+ addConnectionAttributes(attrs) // network conditions may differ from the actual for VitalMetrics when they were captured
97
98
 
98
99
  // If cls was set to another value by `onCLS`, then it's supported and is attached onto any timing but is omitted until such time.
99
100
  /*
@@ -175,3 +176,13 @@ export class Aggregate extends AggregateBase {
175
176
  return payload
176
177
  }
177
178
  }
179
+
180
+ function addConnectionAttributes (obj) {
181
+ var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection // to date, both window & worker shares the same support for connection
182
+ if (!connection) return
183
+
184
+ if (connection.type) obj['net-type'] = connection.type
185
+ if (connection.effectiveType) obj['net-etype'] = connection.effectiveType
186
+ if (connection.rtt) obj['net-rtt'] = connection.rtt
187
+ if (connection.downlink) obj['net-dlink'] = connection.downlink
188
+ }
@@ -133,14 +133,14 @@ export class Aggregate extends AggregateBase {
133
133
  this.ee.on(SESSION_EVENTS.RESUME, () => {
134
134
  // if the mode changed on a different tab, it needs to update this instance to match
135
135
  const { session } = getRuntime(this.agentIdentifier)
136
- this.mode = session.state.sessionReplay
136
+ this.mode = session.state.sessionReplayMode
137
137
  if (!this.initialized || this.mode === MODE.OFF) return
138
138
  this.startRecording()
139
139
  })
140
140
 
141
141
  this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
142
142
  if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return
143
- if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB)
143
+ if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB)
144
144
  this.mode = data.sessionReplay
145
145
  })
146
146
 
@@ -167,7 +167,7 @@ export class Aggregate extends AggregateBase {
167
167
 
168
168
  this.scheduler.startTimer(this.harvestTimeSeconds)
169
169
 
170
- this.syncWithSessionManager({ sessionReplay: this.mode })
170
+ this.syncWithSessionManager({ sessionReplayMode: this.mode })
171
171
  }
172
172
  }
173
173
  }, this.featureName, this.ee)
@@ -201,7 +201,7 @@ export class Aggregate extends AggregateBase {
201
201
  // session replay samples can only be decided on the first load of a session
202
202
  // session replays can continue if already in progress
203
203
  if (!session.isNew) { // inherit the mode of the existing session
204
- this.mode = session.state.sessionReplay
204
+ this.mode = session.state.sessionReplayMode
205
205
  } else {
206
206
  // The session is new... determine the mode the new session should start in
207
207
  if (fullSample) this.mode = MODE.FULL // full mode has precedence over error mode
@@ -241,7 +241,7 @@ export class Aggregate extends AggregateBase {
241
241
  }
242
242
  this.startRecording()
243
243
 
244
- this.syncWithSessionManager({ sessionReplay: this.mode })
244
+ this.syncWithSessionManager({ sessionReplayMode: this.mode })
245
245
  }
246
246
 
247
247
  prepareHarvest () {
@@ -439,7 +439,7 @@ export class Aggregate extends AggregateBase {
439
439
  this.blocked = true
440
440
  this.mode = MODE.OFF
441
441
  this.stopRecording()
442
- this.syncWithSessionManager({ sessionReplay: this.mode })
442
+ this.syncWithSessionManager({ sessionReplayMode: this.mode })
443
443
  this.clearTimestamps()
444
444
  this.ee.emit('REPLAY_ABORTED')
445
445
  }
@@ -125,7 +125,7 @@ export class Aggregate extends AggregateBase {
125
125
  this.ee.on(SESSION_EVENTS.PAUSE, () => { mostRecentModeKnown = sessionEntity.state.sessionTraceMode })
126
126
 
127
127
  if (!sessionEntity.isNew) { // inherit the same mode as existing session's Trace
128
- if (sessionEntity.state.sessionReplay === MODE.OFF) this.isStandalone = true
128
+ if (sessionEntity.state.sessionReplayMode === MODE.OFF) this.isStandalone = true
129
129
  controlTraceOp(mostRecentModeKnown = sessionEntity.state.sessionTraceMode)
130
130
  } else { // for new sessions, see the truth table associated with NEWRELIC-8662 wrt the new Trace behavior under session management
131
131
  const replayMode = await getSessionReplayMode(agentIdentifier)
@@ -462,6 +462,13 @@ export class Aggregate extends AggregateBase {
462
462
  this.trace = {}
463
463
  this.nodeCount = 0
464
464
 
465
+ let firstHarvestOfSession
466
+ if (this.agentRuntime.session) {
467
+ const isFirstPayload = !this.agentRuntime.session.state.traceHarvestStarted
468
+ firstHarvestOfSession = { fsh: Number(isFirstPayload) } // converted to '0' | '1'
469
+ if (isFirstPayload) this.agentRuntime.session.write({ traceHarvestStarted: true })
470
+ }
471
+
465
472
  return {
466
473
  qs: {
467
474
  st: this.agentRuntime.offset,
@@ -472,7 +479,8 @@ export class Aggregate extends AggregateBase {
472
479
  * so that blob parsing doesn't need to happen to support UI/API functions */
473
480
  fts: this.agentRuntime.offset + earliestTimeStamp,
474
481
  /** n === "nodeCount" in NR1, a count of nodes in the ST payload, so that blob parsing doesn't need to happen to support UI/API functions */
475
- n: stns.length // node count
482
+ n: stns.length, // node count
483
+ ...firstHarvestOfSession
476
484
  },
477
485
  body: { res: stns }
478
486
  }
@@ -46,7 +46,9 @@ export class Aggregate extends AggregateBase {
46
46
  depth: 0,
47
47
  harvestTimeSeconds: getConfigurationValue(agentIdentifier, 'spa.harvestTimeSeconds') || 10,
48
48
  interactionsToHarvest: [],
49
- interactionsSent: []
49
+ interactionsSent: [],
50
+ // The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
51
+ disableSpaFix: (getConfigurationValue(agentIdentifier, 'feature_flags') || []).indexOf('disable-spa-fix') > -1
50
52
  }
51
53
 
52
54
  this.serializer = new Serializer(this)
@@ -279,7 +281,7 @@ export class Aggregate extends AggregateBase {
279
281
  // context is stored on the xhr and is shared with all callbacks associated
280
282
  // with the new xhr
281
283
  register('new-xhr', function () {
282
- if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
284
+ if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
283
285
  /*
284
286
  * The previous interaction was discarded before a route change. Restore the interaction
285
287
  * in case this XHR is associated with a route change.
@@ -382,7 +384,7 @@ export class Aggregate extends AggregateBase {
382
384
 
383
385
  register(FETCH_START, function (fetchArguments, dtPayload) {
384
386
  if (fetchArguments) {
385
- if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
387
+ if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
386
388
  /*
387
389
  * The previous interaction was discarded before a route change. Restore the interaction
388
390
  * in case this XHR is associated with a route change.