@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
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
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.246.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.245.0...v1.246.0) (2023-10-23)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add network info to all page view timing events ([#768](https://github.com/newrelic/newrelic-browser-agent/issues/768)) ([757cf19](https://github.com/newrelic/newrelic-browser-agent/commit/757cf1953af471118d809414cd41297a87c89a34))
12
+ * Replace url parsing with URL class ([#781](https://github.com/newrelic/newrelic-browser-agent/issues/781)) ([4206263](https://github.com/newrelic/newrelic-browser-agent/commit/42062638850b4b410ac75eb008120ec4a82583c1))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * Add feature flag support for Browser Interactions ([#779](https://github.com/newrelic/newrelic-browser-agent/issues/779)) ([aa39c6c](https://github.com/newrelic/newrelic-browser-agent/commit/aa39c6cd2aeefaecd803aeb0736ad7aef8477bc4))
18
+ * Add first harvest of session flags to RUM and Trace ([#765](https://github.com/newrelic/newrelic-browser-agent/issues/765)) ([ab2e9dd](https://github.com/newrelic/newrelic-browser-agent/commit/ab2e9dd2252143635b67ea9da4e07867ec68cd0f))
19
+
6
20
  ## [1.245.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.244.0...v1.245.0) (2023-10-18)
7
21
 
8
22
 
@@ -17,4 +17,5 @@ require("core-js/stable/set");
17
17
  require("core-js/stable/weak-set");
18
18
  require("core-js/stable/object/get-own-property-descriptors");
19
19
  require("core-js/stable/url");
20
- require("core-js/stable/url-search-params");
20
+ require("core-js/stable/url-search-params");
21
+ require("core-js/stable/string/starts-with");
@@ -15,7 +15,7 @@ function getModeledObject(obj, model) {
15
15
  for (let key in target) {
16
16
  if (obj[key] !== undefined) {
17
17
  try {
18
- if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
18
+ if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]));else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
19
19
  } catch (e) {
20
20
  (0, _console.warn)('An error occurred while setting a property of a Configurable', e);
21
21
  }
@@ -37,6 +37,7 @@ const model = () => {
37
37
  };
38
38
 
39
39
  return {
40
+ feature_flags: [],
40
41
  proxy: {
41
42
  assets: undefined,
42
43
  // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
@@ -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.245.0";
15
+ const VERSION = "1.246.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.245.0";
15
+ const VERSION = "1.246.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -31,9 +31,10 @@ const model = {
31
31
  inactiveAt: 0,
32
32
  expiresAt: 0,
33
33
  updatedAt: Date.now(),
34
- sessionReplay: MODE.OFF,
34
+ sessionReplayMode: MODE.OFF,
35
35
  sessionReplaySentFirstChunk: false,
36
36
  sessionTraceMode: MODE.OFF,
37
+ traceHarvestStarted: false,
37
38
  custom: {}
38
39
  };
39
40
  const SESSION_EVENTS = {
@@ -152,7 +153,7 @@ class SessionEntity {
152
153
 
153
154
  // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
154
155
  // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
155
- if (this.isNew === undefined) this.isNew = !Object.keys(initialRead).length;
156
+ this.isNew = !Object.keys(initialRead).length;
156
157
  // 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.
157
158
  // we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
158
159
  if (this.isNew) this.write((0, _configurable.getModeledObject)(this.state, model), true);else this.sync(initialRead);
@@ -10,58 +10,34 @@ var _runtime = require("../constants/runtime");
10
10
  * SPDX-License-Identifier: Apache-2.0
11
11
  */
12
12
 
13
- var stringsToParsedUrls = {};
14
13
  function parseUrl(url) {
15
- if (url in stringsToParsedUrls) {
16
- return stringsToParsedUrls[url];
17
- }
18
-
19
14
  // Return if URL is a data URL, parseUrl assumes urls are http/https
20
15
  if ((url || '').indexOf('data:') === 0) {
21
16
  return {
22
17
  protocol: 'data'
23
18
  };
24
19
  }
25
- let urlEl;
26
- var location = _runtime.globalScope?.location;
27
- var ret = {};
28
20
  try {
29
- urlEl = new URL(url, location.href);
30
- } catch (err) {
31
- if (_runtime.isBrowserScope) {
32
- // Use an anchor dom element to resolve the url natively.
33
- urlEl = document.createElement('a');
34
- urlEl.href = url;
35
- } else {
36
- return ret;
21
+ const parsedUrl = new URL(url, location.href);
22
+ const returnVal = {
23
+ port: parsedUrl.port,
24
+ hostname: parsedUrl.hostname,
25
+ pathname: parsedUrl.pathname,
26
+ search: parsedUrl.search,
27
+ protocol: parsedUrl.protocol.slice(0, parsedUrl.protocol.indexOf(':')),
28
+ sameOrigin: parsedUrl.protocol === _runtime.globalScope?.location?.protocol && parsedUrl.host === _runtime.globalScope?.location?.host
29
+ };
30
+ if (!returnVal.port || returnVal.port === '') {
31
+ if (parsedUrl.protocol === 'http:') returnVal.port = '80';
32
+ if (parsedUrl.protocol === 'https:') returnVal.port = '443';
37
33
  }
34
+ if (!returnVal.pathname || returnVal.pathname === '') {
35
+ returnVal.pathname = '/';
36
+ } else if (!returnVal.pathname.startsWith('/')) {
37
+ returnVal.pathname = "/".concat(returnVal.pathname);
38
+ }
39
+ return returnVal;
40
+ } catch (err) {
41
+ return {};
38
42
  }
39
- ret.port = urlEl.port;
40
- ret.search = urlEl.search;
41
- var firstSplit = urlEl.href.split('://');
42
- if (!ret.port && firstSplit[1]) {
43
- ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1];
44
- }
45
- if (!ret.port || ret.port === '0') ret.port = firstSplit[0] === 'https' ? '443' : '80';
46
-
47
- // Host not provided in IE for relative urls
48
- ret.hostname = urlEl.hostname || location.hostname;
49
- ret.pathname = urlEl.pathname;
50
- ret.protocol = firstSplit[0];
51
-
52
- // Pathname sometimes doesn't have leading slash (IE 8 and 9)
53
- if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname;
54
-
55
- // urlEl.protocol is ':' in old ie when protocol is not specified
56
- var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol;
57
- var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port;
58
-
59
- // urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin
60
- ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain);
61
-
62
- // Only cache if url doesn't have a path
63
- if (ret.pathname === '/') {
64
- stringsToParsedUrls[url] = ret;
65
- }
66
- return ret;
67
43
  }
@@ -25,8 +25,7 @@ if (_runtime.isBrowserScope) {
25
25
  attrs: {
26
26
  type: entries[0].name,
27
27
  fid: Math.round(value)
28
- },
29
- shouldAddConnectionAttributes: true
28
+ }
30
29
  });
31
30
  });
32
31
  }
@@ -34,8 +34,7 @@ if (_runtime.isBrowserScope) {
34
34
  elTag: lcpEntry.element.tagName
35
35
  })
36
36
  }
37
- }),
38
- shouldAddConnectionAttributes: true
37
+ })
39
38
  });
40
39
  });
41
40
  }
@@ -16,8 +16,7 @@ class VitalMetric {
16
16
  let {
17
17
  value,
18
18
  entries = [],
19
- attrs = {},
20
- shouldAddConnectionAttributes = false
19
+ attrs = {}
21
20
  } = _ref;
22
21
  if (value < 0) return;
23
22
  const state = {
@@ -26,7 +25,6 @@ class VitalMetric {
26
25
  entries,
27
26
  attrs
28
27
  };
29
- if (shouldAddConnectionAttributes) addConnectionAttributes(state.attrs);
30
28
  this.history.push(state);
31
29
  this.#subscribers.forEach(cb => {
32
30
  try {
@@ -60,12 +58,4 @@ class VitalMetric {
60
58
  };
61
59
  }
62
60
  }
63
- exports.VitalMetric = VitalMetric;
64
- function addConnectionAttributes(obj) {
65
- var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; // to date, both window & worker shares the same support for connection
66
- if (!connection) return;
67
- if (connection.type) obj['net-type'] = connection.type;
68
- if (connection.effectiveType) obj['net-etype'] = connection.effectiveType;
69
- if (connection.rtt) obj['net-rtt'] = connection.rtt;
70
- if (connection.downlink) obj['net-dlink'] = connection.downlink;
71
- }
61
+ exports.VitalMetric = VitalMetric;
@@ -122,6 +122,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
122
122
  body: this.body,
123
123
  query: this?.parsedOrigin?.search
124
124
  });
125
+ if (event.gql) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', (0, _stringify.stringify)(event.gql).length], undefined, _features.FEATURE_NAMES.metrics, ee);
125
126
 
126
127
  // if the ajax happened inside an interaction, hold it until the interaction finishes
127
128
  if (this.spaNode) {
@@ -187,7 +187,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
187
187
  params.pageview = 1;
188
188
  this.pageviewReported[bucketHash] = true;
189
189
  }
190
- if (agentRuntime?.session?.state?.sessionReplay) params.hasReplay = true;
190
+ if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true;
191
191
  params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
192
192
  var type = internal ? 'ierr' : 'err';
193
193
  var newMetrics = {
@@ -84,6 +84,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
84
84
  ua: info.userAttributes,
85
85
  at: info.atts
86
86
  };
87
+ if (agentRuntime.session) queryParameters.fsh = Number(agentRuntime.session.isNew); // "first session harvest" aka RUM request or PageView event of a session
88
+
87
89
  let body;
88
90
  if (typeof info.jsAttributes === 'object' && Object.keys(info.jsAttributes).length > 0) {
89
91
  body = {
@@ -106,6 +106,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
106
106
  }
107
107
  addTiming(name, value, attrs) {
108
108
  attrs = attrs || {};
109
+ addConnectionAttributes(attrs); // network conditions may differ from the actual for VitalMetrics when they were captured
109
110
 
110
111
  // 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.
111
112
  /*
@@ -177,4 +178,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
177
178
  return payload;
178
179
  }
179
180
  }
180
- exports.Aggregate = Aggregate;
181
+ exports.Aggregate = Aggregate;
182
+ function addConnectionAttributes(obj) {
183
+ var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; // to date, both window & worker shares the same support for connection
184
+ if (!connection) return;
185
+ if (connection.type) obj['net-type'] = connection.type;
186
+ if (connection.effectiveType) obj['net-etype'] = connection.effectiveType;
187
+ if (connection.rtt) obj['net-rtt'] = connection.rtt;
188
+ if (connection.downlink) obj['net-dlink'] = connection.downlink;
189
+ }
@@ -140,13 +140,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
140
140
  const {
141
141
  session
142
142
  } = (0, _config.getRuntime)(this.agentIdentifier);
143
- this.mode = session.state.sessionReplay;
143
+ this.mode = session.state.sessionReplayMode;
144
144
  if (!this.initialized || this.mode === _sessionEntity.MODE.OFF) return;
145
145
  this.startRecording();
146
146
  });
147
147
  this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (type, data) => {
148
148
  if (!this.initialized || this.blocked || type !== _sessionEntity.SESSION_EVENT_TYPES.CROSS_TAB) return;
149
- if (this.mode !== _sessionEntity.MODE.OFF && data.sessionReplay === _sessionEntity.MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
149
+ if (this.mode !== _sessionEntity.MODE.OFF && data.sessionReplayMode === _sessionEntity.MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
150
150
  this.mode = data.sessionReplay;
151
151
  });
152
152
 
@@ -172,7 +172,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
172
172
  this.startRecording();
173
173
  this.scheduler.startTimer(this.harvestTimeSeconds);
174
174
  this.syncWithSessionManager({
175
- sessionReplay: this.mode
175
+ sessionReplayMode: this.mode
176
176
  });
177
177
  }
178
178
  }
@@ -207,7 +207,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
207
207
  // session replays can continue if already in progress
208
208
  if (!session.isNew) {
209
209
  // inherit the mode of the existing session
210
- this.mode = session.state.sessionReplay;
210
+ this.mode = session.state.sessionReplayMode;
211
211
  } else {
212
212
  // The session is new... determine the mode the new session should start in
213
213
  if (fullSample) this.mode = _sessionEntity.MODE.FULL; // full mode has precedence over error mode
@@ -248,7 +248,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
248
248
  }
249
249
  this.startRecording();
250
250
  this.syncWithSessionManager({
251
- sessionReplay: this.mode
251
+ sessionReplayMode: this.mode
252
252
  });
253
253
  }
254
254
  prepareHarvest() {
@@ -457,7 +457,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
457
457
  this.mode = _sessionEntity.MODE.OFF;
458
458
  this.stopRecording();
459
459
  this.syncWithSessionManager({
460
- sessionReplay: this.mode
460
+ sessionReplayMode: this.mode
461
461
  });
462
462
  this.clearTimestamps();
463
463
  this.ee.emit('REPLAY_ABORTED');
@@ -148,7 +148,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
148
148
  });
149
149
  if (!sessionEntity.isNew) {
150
150
  // inherit the same mode as existing session's Trace
151
- if (sessionEntity.state.sessionReplay === _sessionEntity.MODE.OFF) this.isStandalone = true;
151
+ if (sessionEntity.state.sessionReplayMode === _sessionEntity.MODE.OFF) this.isStandalone = true;
152
152
  controlTraceOp(mostRecentModeKnown = sessionEntity.state.sessionTraceMode);
153
153
  } else {
154
154
  // for new sessions, see the truth table associated with NEWRELIC-8662 wrt the new Trace behavior under session management
@@ -510,6 +510,16 @@ class Aggregate extends _aggregateBase.AggregateBase {
510
510
  }
511
511
  this.trace = {};
512
512
  this.nodeCount = 0;
513
+ let firstHarvestOfSession;
514
+ if (this.agentRuntime.session) {
515
+ const isFirstPayload = !this.agentRuntime.session.state.traceHarvestStarted;
516
+ firstHarvestOfSession = {
517
+ fsh: Number(isFirstPayload)
518
+ }; // converted to '0' | '1'
519
+ if (isFirstPayload) this.agentRuntime.session.write({
520
+ traceHarvestStarted: true
521
+ });
522
+ }
513
523
  return {
514
524
  qs: {
515
525
  st: this.agentRuntime.offset,
@@ -520,9 +530,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
520
530
  * so that blob parsing doesn't need to happen to support UI/API functions */
521
531
  fts: this.agentRuntime.offset + earliestTimeStamp,
522
532
  /** 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 */
523
- n: stns.length // node count
533
+ n: stns.length,
534
+ // node count
535
+ ...firstHarvestOfSession
524
536
  },
525
-
526
537
  body: {
527
538
  res: stns
528
539
  }
@@ -67,7 +67,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
67
67
  depth: 0,
68
68
  harvestTimeSeconds: (0, _config.getConfigurationValue)(agentIdentifier, 'spa.harvestTimeSeconds') || 10,
69
69
  interactionsToHarvest: [],
70
- interactionsSent: []
70
+ interactionsSent: [],
71
+ // The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
72
+ disableSpaFix: ((0, _config.getConfigurationValue)(agentIdentifier, 'feature_flags') || []).indexOf('disable-spa-fix') > -1
71
73
  };
72
74
  this.serializer = new _serializer.Serializer(this);
73
75
  const {
@@ -287,7 +289,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
287
289
  // context is stored on the xhr and is shared with all callbacks associated
288
290
  // with the new xhr
289
291
  (0, _registerHandler.registerHandler)('new-xhr', function () {
290
- if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
292
+ if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
291
293
  /*
292
294
  * The previous interaction was discarded before a route change. Restore the interaction
293
295
  * in case this XHR is associated with a route change.
@@ -378,7 +380,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
378
380
  }, this.featureName, jsonpEE);
379
381
  (0, _registerHandler.registerHandler)(FETCH_START, function (fetchArguments, dtPayload) {
380
382
  if (fetchArguments) {
381
- if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
383
+ if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
382
384
  /*
383
385
  * The previous interaction was discarded before a route change. Restore the interaction
384
386
  * in case this XHR is associated with a route change.
@@ -139,7 +139,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
139
139
  if (featureName === _features.FEATURE_NAMES.sessionReplay) {
140
140
  if (!_config.originals.MO) return false; // Session Replay cannot work without Mutation Observer
141
141
  if ((0, _config.getConfigurationValue)(this.agentIdentifier, 'session_trace.enabled') === false) return false; // Session Replay as of now is tightly coupled with Session Trace in the UI
142
- 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
142
+ 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
143
143
  }
144
144
 
145
145
  return true;
@@ -20,4 +20,5 @@ import 'core-js/stable/set';
20
20
  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
- import 'core-js/stable/url-search-params';
23
+ import 'core-js/stable/url-search-params';
24
+ import 'core-js/stable/string/starts-with';
@@ -9,7 +9,7 @@ export function getModeledObject(obj, model) {
9
9
  for (let key in target) {
10
10
  if (obj[key] !== undefined) {
11
11
  try {
12
- if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
12
+ if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]));else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
13
13
  } catch (e) {
14
14
  warn('An error occurred while setting a property of a Configurable', e);
15
15
  }
@@ -29,6 +29,7 @@ const model = () => {
29
29
  };
30
30
 
31
31
  return {
32
+ feature_flags: [],
32
33
  proxy: {
33
34
  assets: undefined,
34
35
  // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.245.0";
9
+ export const VERSION = "1.246.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.245.0";
9
+ export const VERSION = "1.246.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -24,9 +24,10 @@ const model = {
24
24
  inactiveAt: 0,
25
25
  expiresAt: 0,
26
26
  updatedAt: Date.now(),
27
- sessionReplay: MODE.OFF,
27
+ sessionReplayMode: MODE.OFF,
28
28
  sessionReplaySentFirstChunk: false,
29
29
  sessionTraceMode: MODE.OFF,
30
+ traceHarvestStarted: false,
30
31
  custom: {}
31
32
  };
32
33
  export const SESSION_EVENTS = {
@@ -143,7 +144,7 @@ export class SessionEntity {
143
144
 
144
145
  // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
145
146
  // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
146
- if (this.isNew === undefined) this.isNew = !Object.keys(initialRead).length;
147
+ this.isNew = !Object.keys(initialRead).length;
147
148
  // 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.
148
149
  // we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
149
150
  if (this.isNew) this.write(getModeledObject(this.state, model), true);else this.sync(initialRead);
@@ -3,59 +3,35 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
- import { globalScope, isBrowserScope } from '../constants/runtime';
7
- var stringsToParsedUrls = {};
6
+ import { globalScope } from '../constants/runtime';
8
7
  export function parseUrl(url) {
9
- if (url in stringsToParsedUrls) {
10
- return stringsToParsedUrls[url];
11
- }
12
-
13
8
  // Return if URL is a data URL, parseUrl assumes urls are http/https
14
9
  if ((url || '').indexOf('data:') === 0) {
15
10
  return {
16
11
  protocol: 'data'
17
12
  };
18
13
  }
19
- let urlEl;
20
- var location = globalScope?.location;
21
- var ret = {};
22
14
  try {
23
- urlEl = new URL(url, location.href);
24
- } catch (err) {
25
- if (isBrowserScope) {
26
- // Use an anchor dom element to resolve the url natively.
27
- urlEl = document.createElement('a');
28
- urlEl.href = url;
29
- } else {
30
- return ret;
15
+ const parsedUrl = new URL(url, location.href);
16
+ const returnVal = {
17
+ port: parsedUrl.port,
18
+ hostname: parsedUrl.hostname,
19
+ pathname: parsedUrl.pathname,
20
+ search: parsedUrl.search,
21
+ protocol: parsedUrl.protocol.slice(0, parsedUrl.protocol.indexOf(':')),
22
+ sameOrigin: parsedUrl.protocol === globalScope?.location?.protocol && parsedUrl.host === globalScope?.location?.host
23
+ };
24
+ if (!returnVal.port || returnVal.port === '') {
25
+ if (parsedUrl.protocol === 'http:') returnVal.port = '80';
26
+ if (parsedUrl.protocol === 'https:') returnVal.port = '443';
31
27
  }
28
+ if (!returnVal.pathname || returnVal.pathname === '') {
29
+ returnVal.pathname = '/';
30
+ } else if (!returnVal.pathname.startsWith('/')) {
31
+ returnVal.pathname = "/".concat(returnVal.pathname);
32
+ }
33
+ return returnVal;
34
+ } catch (err) {
35
+ return {};
32
36
  }
33
- ret.port = urlEl.port;
34
- ret.search = urlEl.search;
35
- var firstSplit = urlEl.href.split('://');
36
- if (!ret.port && firstSplit[1]) {
37
- ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1];
38
- }
39
- if (!ret.port || ret.port === '0') ret.port = firstSplit[0] === 'https' ? '443' : '80';
40
-
41
- // Host not provided in IE for relative urls
42
- ret.hostname = urlEl.hostname || location.hostname;
43
- ret.pathname = urlEl.pathname;
44
- ret.protocol = firstSplit[0];
45
-
46
- // Pathname sometimes doesn't have leading slash (IE 8 and 9)
47
- if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname;
48
-
49
- // urlEl.protocol is ':' in old ie when protocol is not specified
50
- var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol;
51
- var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port;
52
-
53
- // urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin
54
- ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain);
55
-
56
- // Only cache if url doesn't have a path
57
- if (ret.pathname === '/') {
58
- stringsToParsedUrls[url] = ret;
59
- }
60
- return ret;
61
37
  }
@@ -18,8 +18,7 @@ if (isBrowserScope) {
18
18
  attrs: {
19
19
  type: entries[0].name,
20
20
  fid: Math.round(value)
21
- },
22
- shouldAddConnectionAttributes: true
21
+ }
23
22
  });
24
23
  });
25
24
  }
@@ -27,8 +27,7 @@ if (isBrowserScope) {
27
27
  elTag: lcpEntry.element.tagName
28
28
  })
29
29
  }
30
- }),
31
- shouldAddConnectionAttributes: true
30
+ })
32
31
  });
33
32
  });
34
33
  }
@@ -10,8 +10,7 @@ export class VitalMetric {
10
10
  let {
11
11
  value,
12
12
  entries = [],
13
- attrs = {},
14
- shouldAddConnectionAttributes = false
13
+ attrs = {}
15
14
  } = _ref;
16
15
  if (value < 0) return;
17
16
  const state = {
@@ -20,7 +19,6 @@ export class VitalMetric {
20
19
  entries,
21
20
  attrs
22
21
  };
23
- if (shouldAddConnectionAttributes) addConnectionAttributes(state.attrs);
24
22
  this.history.push(state);
25
23
  this.#subscribers.forEach(cb => {
26
24
  try {
@@ -53,12 +51,4 @@ export class VitalMetric {
53
51
  this.#subscribers.delete(callback);
54
52
  };
55
53
  }
56
- }
57
- function addConnectionAttributes(obj) {
58
- var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; // to date, both window & worker shares the same support for connection
59
- if (!connection) return;
60
- if (connection.type) obj['net-type'] = connection.type;
61
- if (connection.effectiveType) obj['net-etype'] = connection.effectiveType;
62
- if (connection.rtt) obj['net-rtt'] = connection.rtt;
63
- if (connection.downlink) obj['net-dlink'] = connection.downlink;
64
54
  }
@@ -115,6 +115,7 @@ export class Aggregate extends AggregateBase {
115
115
  body: this.body,
116
116
  query: this?.parsedOrigin?.search
117
117
  });
118
+ if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, ee);
118
119
 
119
120
  // if the ajax happened inside an interaction, hold it until the interaction finishes
120
121
  if (this.spaNode) {
@@ -182,7 +182,7 @@ export class Aggregate extends AggregateBase {
182
182
  params.pageview = 1;
183
183
  this.pageviewReported[bucketHash] = true;
184
184
  }
185
- if (agentRuntime?.session?.state?.sessionReplay) params.hasReplay = true;
185
+ if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true;
186
186
  params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
187
187
  var type = internal ? 'ierr' : 'err';
188
188
  var newMetrics = {
@@ -76,6 +76,8 @@ export class Aggregate extends AggregateBase {
76
76
  ua: info.userAttributes,
77
77
  at: info.atts
78
78
  };
79
+ if (agentRuntime.session) queryParameters.fsh = Number(agentRuntime.session.isNew); // "first session harvest" aka RUM request or PageView event of a session
80
+
79
81
  let body;
80
82
  if (typeof info.jsAttributes === 'object' && Object.keys(info.jsAttributes).length > 0) {
81
83
  body = {