@newrelic/browser-agent 1.244.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 (104) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/cdn/polyfills.js +5 -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 +21 -44
  9. package/dist/cjs/common/util/type-check.js +14 -0
  10. package/dist/cjs/common/vitals/first-input-delay.js +1 -2
  11. package/dist/cjs/common/vitals/largest-contentful-paint.js +1 -2
  12. package/dist/cjs/common/vitals/vital-metric.js +2 -12
  13. package/dist/cjs/features/ajax/aggregate/gql.js +94 -0
  14. package/dist/cjs/features/ajax/aggregate/index.js +13 -1
  15. package/dist/cjs/features/ajax/instrument/index.js +2 -0
  16. package/dist/cjs/features/jserrors/aggregate/index.js +1 -1
  17. package/dist/cjs/features/page_view_event/aggregate/index.js +2 -0
  18. package/dist/cjs/features/page_view_timing/aggregate/index.js +10 -1
  19. package/dist/cjs/features/session_replay/aggregate/index.js +6 -6
  20. package/dist/cjs/features/session_trace/aggregate/index.js +14 -3
  21. package/dist/cjs/features/spa/aggregate/index.js +5 -3
  22. package/dist/cjs/features/spa/aggregate/serializer.js +7 -0
  23. package/dist/cjs/features/utils/instrument-base.js +1 -1
  24. package/dist/cjs/index.js +0 -7
  25. package/dist/cjs/loaders/api/api.js +2 -2
  26. package/dist/esm/cdn/polyfills.js +5 -1
  27. package/dist/esm/common/config/state/configurable.js +1 -1
  28. package/dist/esm/common/config/state/init.js +1 -0
  29. package/dist/esm/common/constants/env.cdn.js +1 -1
  30. package/dist/esm/common/constants/env.npm.js +1 -1
  31. package/dist/esm/common/session/session-entity.js +3 -2
  32. package/dist/esm/common/url/parse-url.js +22 -45
  33. package/dist/esm/common/util/type-check.js +8 -0
  34. package/dist/esm/common/vitals/first-input-delay.js +1 -2
  35. package/dist/esm/common/vitals/largest-contentful-paint.js +1 -2
  36. package/dist/esm/common/vitals/vital-metric.js +1 -11
  37. package/dist/esm/features/ajax/aggregate/gql.js +89 -0
  38. package/dist/esm/features/ajax/aggregate/index.js +13 -1
  39. package/dist/esm/features/ajax/instrument/index.js +2 -0
  40. package/dist/esm/features/jserrors/aggregate/index.js +1 -1
  41. package/dist/esm/features/page_view_event/aggregate/index.js +2 -0
  42. package/dist/esm/features/page_view_timing/aggregate/index.js +9 -0
  43. package/dist/esm/features/session_replay/aggregate/index.js +6 -6
  44. package/dist/esm/features/session_trace/aggregate/index.js +14 -3
  45. package/dist/esm/features/spa/aggregate/index.js +5 -3
  46. package/dist/esm/features/spa/aggregate/serializer.js +7 -0
  47. package/dist/esm/features/utils/instrument-base.js +1 -1
  48. package/dist/esm/index.js +0 -1
  49. package/dist/esm/loaders/api/api.js +2 -2
  50. package/dist/types/common/config/state/configurable.d.ts.map +1 -1
  51. package/dist/types/common/config/state/init.d.ts.map +1 -1
  52. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  53. package/dist/types/common/url/parse-url.d.ts +12 -1
  54. package/dist/types/common/url/parse-url.d.ts.map +1 -1
  55. package/dist/types/common/util/type-check.d.ts +7 -0
  56. package/dist/types/common/util/type-check.d.ts.map +1 -0
  57. package/dist/types/common/vitals/vital-metric.d.ts +1 -2
  58. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
  59. package/dist/types/features/ajax/aggregate/gql.d.ts +29 -0
  60. package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -0
  61. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  62. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  63. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  64. package/dist/types/features/session_trace/aggregate/index.d.ts +1 -0
  65. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  66. package/dist/types/features/spa/aggregate/index.d.ts +1 -0
  67. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  68. package/dist/types/features/spa/aggregate/serializer.d.ts.map +1 -1
  69. package/dist/types/index.d.ts +0 -1
  70. package/dist/types/loaders/configure/public-path.npm.d.ts.map +1 -1
  71. package/package.json +1 -9
  72. package/src/cdn/polyfills.js +4 -0
  73. package/src/common/config/state/configurable.js +2 -1
  74. package/src/common/config/state/init.js +1 -0
  75. package/src/common/session/session-entity.js +3 -2
  76. package/src/common/url/parse-url.js +22 -50
  77. package/src/common/util/type-check.js +8 -0
  78. package/src/common/vitals/first-input-delay.js +1 -2
  79. package/src/common/vitals/largest-contentful-paint.js +1 -2
  80. package/src/common/vitals/vital-metric.js +2 -12
  81. package/src/features/ajax/aggregate/gql.js +95 -0
  82. package/src/features/ajax/aggregate/index.js +11 -1
  83. package/src/features/ajax/instrument/index.js +3 -0
  84. package/src/features/jserrors/aggregate/index.js +1 -1
  85. package/src/features/page_view_event/aggregate/index.js +2 -0
  86. package/src/features/page_view_timing/aggregate/index.js +11 -0
  87. package/src/features/session_replay/aggregate/index.js +6 -6
  88. package/src/features/session_trace/aggregate/index.js +10 -2
  89. package/src/features/spa/aggregate/index.js +5 -3
  90. package/src/features/spa/aggregate/serializer.js +7 -1
  91. package/src/features/utils/instrument-base.js +1 -1
  92. package/src/index.js +0 -1
  93. package/src/loaders/api/api.js +2 -2
  94. package/src/loaders/configure/public-path.npm.js +0 -1
  95. package/dist/cjs/cdn/worker.js +0 -16
  96. package/dist/cjs/loaders/worker-agent.js +0 -24
  97. package/dist/esm/cdn/worker.js +0 -14
  98. package/dist/esm/loaders/worker-agent.js +0 -18
  99. package/dist/types/cdn/worker.d.ts +0 -2
  100. package/dist/types/cdn/worker.d.ts.map +0 -1
  101. package/dist/types/loaders/worker-agent.d.ts +0 -8
  102. package/dist/types/loaders/worker-agent.d.ts.map +0 -1
  103. package/src/cdn/worker.js +0 -21
  104. package/src/loaders/worker-agent.js +0 -24
package/CHANGELOG.md CHANGED
@@ -3,6 +3,29 @@
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
+
20
+ ## [1.245.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.244.0...v1.245.0) (2023-10-18)
21
+
22
+
23
+ ### Features
24
+
25
+ * Allow boolean values in setCustomAttribute ([#776](https://github.com/newrelic/newrelic-browser-agent/issues/776)) ([d44f033](https://github.com/newrelic/newrelic-browser-agent/commit/d44f03384655f47c5f8a63db02f7eaac58585a86))
26
+ * Detect GraphQL operation names and types in AJAX calls ([#764](https://github.com/newrelic/newrelic-browser-agent/issues/764)) ([8587afc](https://github.com/newrelic/newrelic-browser-agent/commit/8587afc9dbc18a52048f467c77e5ededc225eb2a))
27
+ * Removing worker build ([#762](https://github.com/newrelic/newrelic-browser-agent/issues/762)) ([15f801b](https://github.com/newrelic/newrelic-browser-agent/commit/15f801b1a48c6e60f8f50f349aa382c77a073480))
28
+
6
29
  ## [1.244.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.243.1...v1.244.0) (2023-10-10)
7
30
 
8
31
 
@@ -10,8 +10,12 @@ require("core-js/stable/array/some");
10
10
  require("core-js/stable/object/assign");
11
11
  require("core-js/stable/object/entries");
12
12
  require("core-js/stable/object/values");
13
+ require("core-js/stable/object/from-entries");
13
14
  require("core-js/stable/map");
14
15
  require("core-js/stable/reflect");
15
16
  require("core-js/stable/set");
16
17
  require("core-js/stable/weak-set");
17
- require("core-js/stable/object/get-own-property-descriptors");
18
+ require("core-js/stable/object/get-own-property-descriptors");
19
+ require("core-js/stable/url");
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.244.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.244.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,57 +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
- if (_runtime.isBrowserScope) {
29
- // Use an anchor dom element to resolve the url natively.
30
- urlEl = document.createElement('a');
31
- urlEl.href = url;
32
- } else {
33
- try {
34
- urlEl = new URL(url, location.href);
35
- } catch (err) {
36
- return ret;
20
+ try {
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
- var firstSplit = urlEl.href.split('://');
41
- if (!ret.port && firstSplit[1]) {
42
- ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1];
43
- }
44
- if (!ret.port || ret.port === '0') ret.port = firstSplit[0] === 'https' ? '443' : '80';
45
-
46
- // Host not provided in IE for relative urls
47
- ret.hostname = urlEl.hostname || location.hostname;
48
- ret.pathname = urlEl.pathname;
49
- ret.protocol = firstSplit[0];
50
-
51
- // Pathname sometimes doesn't have leading slash (IE 8 and 9)
52
- if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname;
53
-
54
- // urlEl.protocol is ':' in old ie when protocol is not specified
55
- var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol;
56
- var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port;
57
-
58
- // urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin
59
- ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain);
60
-
61
- // Only cache if url doesn't have a path
62
- if (ret.pathname === '/') {
63
- stringsToParsedUrls[url] = ret;
64
- }
65
- return ret;
66
43
  }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isPureObject = isPureObject;
7
+ /**
8
+ * Tests a passed object to see if it is a pure object or not. All non-primatives in JS
9
+ * are technically objects and would pass a `typeof` check.
10
+ * @param {*} obj Input object to be tested
11
+ **/
12
+ function isPureObject(obj) {
13
+ return obj?.constructor === {}.constructor;
14
+ }
@@ -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;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.parseGQL = parseGQL;
7
+ var _typeCheck = require("../../../common/util/type-check");
8
+ /**
9
+ * @typedef {object} GQLMetadata
10
+ * @property {string} operationName Name of the operation
11
+ * @property {string} operationType Type of the operation
12
+ * @property {string} operationFramework Framework responsible for the operation
13
+ */
14
+
15
+ /**
16
+ * Parses and returns the graphql metadata from a network request. If the network
17
+ * request is not a graphql call, undefined will be returned.
18
+ * @param {object|string} body Ajax request body
19
+ * @param {string} query Ajax request query param string
20
+ * @returns {GQLMetadata | undefined}
21
+ */
22
+ function parseGQL() {
23
+ let {
24
+ body,
25
+ query
26
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
27
+ if (!body && !query) return;
28
+ try {
29
+ const gqlBody = parseBatchGQL(parseGQLContents(body));
30
+ if (gqlBody) return gqlBody;
31
+ const gqlQuery = parseSingleGQL(parseGQLQueryString(query));
32
+ if (gqlQuery) return gqlQuery;
33
+ } catch (err) {
34
+ // parsing failed, return undefined
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @param {string|Object} gql The GraphQL object body sent to a GQL server
40
+ * @returns {GQLMetadata}
41
+ */
42
+ function parseSingleGQL(contents) {
43
+ if (typeof contents !== 'object' || !contents.query || typeof contents.query !== 'string') return;
44
+
45
+ /** parses gql query string and returns [fullmatch, type match, name match] */
46
+ const matches = contents.query.trim().match(/^(query|mutation|subscription)\s?(\w*)/);
47
+ const operationType = matches?.[1];
48
+ if (!operationType) return;
49
+ const operationName = contents.operationName || matches?.[2] || 'Anonymous';
50
+ return {
51
+ operationName,
52
+ // the operation name of the indiv query
53
+ operationType,
54
+ // query, mutation, or subscription,
55
+ operationFramework: 'GraphQL'
56
+ };
57
+ }
58
+ function parseBatchGQL(contents) {
59
+ if (!contents) return;
60
+ if (!Array.isArray(contents)) contents = [contents];
61
+ const opNames = [];
62
+ const opTypes = [];
63
+ for (let content of contents) {
64
+ const operation = parseSingleGQL(content);
65
+ if (!operation) continue;
66
+ opNames.push(operation.operationName);
67
+ opTypes.push(operation.operationType);
68
+ }
69
+ if (!opTypes.length) return;
70
+ return {
71
+ operationName: opNames.join(','),
72
+ // the operation name of the indiv query -- joined by ',' for batched results
73
+ operationType: opTypes.join(','),
74
+ // query, mutation, or subscription -- joined by ',' for batched results
75
+ operationFramework: 'GraphQL'
76
+ };
77
+ }
78
+ function parseGQLContents(gqlContents) {
79
+ let contents;
80
+ if (!gqlContents || typeof gqlContents !== 'string' && typeof gqlContents !== 'object') return;else if (typeof gqlContents === 'string') contents = JSON.parse(gqlContents);else contents = gqlContents;
81
+ if (!(0, _typeCheck.isPureObject)(contents) && !Array.isArray(contents)) return;
82
+ let isValid = false;
83
+ if (Array.isArray(contents)) isValid = contents.some(x => validateGQLObject(x));else isValid = validateGQLObject(contents);
84
+ if (!isValid) return;
85
+ return contents;
86
+ }
87
+ function parseGQLQueryString(gqlQueryString) {
88
+ if (!gqlQueryString || typeof gqlQueryString !== 'string') return;
89
+ const params = new URLSearchParams(gqlQueryString);
90
+ return parseGQLContents(Object.fromEntries(params));
91
+ }
92
+ function validateGQLObject(obj) {
93
+ return !(typeof obj !== 'object' || !obj.query || typeof obj.query !== 'string');
94
+ }
@@ -15,6 +15,7 @@ var _constants = require("../constants");
15
15
  var _features = require("../../../loaders/features/features");
16
16
  var _constants2 = require("../../metrics/constants");
17
17
  var _aggregateBase = require("../../utils/aggregate-base");
18
+ var _gql = require("./gql");
18
19
  /*
19
20
  * Copyright 2020 New Relic Corporation. All rights reserved.
20
21
  * SPDX-License-Identifier: Apache-2.0
@@ -116,6 +117,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
116
117
  event.spanTimestamp = xhrContext.dt.timestamp;
117
118
  }
118
119
 
120
+ // parsed from the AJAX body, looking for operationName param & parsing query for operationType
121
+ event.gql = params.gql = (0, _gql.parseGQL)({
122
+ body: this.body,
123
+ query: this?.parsedOrigin?.search
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);
126
+
119
127
  // if the ajax happened inside an interaction, hold it until the interaction finishes
120
128
  if (this.spaNode) {
121
129
  var interactionId = this.spaNode.interaction.id;
@@ -205,7 +213,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
205
213
  var insert = '2,';
206
214
 
207
215
  // add custom attributes
208
- var attrParts = (0, _belSerializer.addCustomAttributes)((0, _config.getInfo)(agentIdentifier).jsAttributes || {}, this.addString);
216
+ // gql decorators are added as custom attributes to alleviate need for new BEL schema
217
+ var attrParts = (0, _belSerializer.addCustomAttributes)({
218
+ ...((0, _config.getInfo)(agentIdentifier).jsAttributes || {}),
219
+ ...(event.gql || {})
220
+ }, this.addString);
209
221
  fields.unshift((0, _belSerializer.numeric)(attrParts.length));
210
222
  insert += fields.join(',');
211
223
  if (attrParts && attrParts.length > 0) {
@@ -161,6 +161,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
161
161
  if (size) metrics.txSize = size;
162
162
  }
163
163
  this.startTime = (0, _now.now)();
164
+ this.body = data;
164
165
  this.listener = function (evt) {
165
166
  try {
166
167
  if (evt.type === 'abort' && !context.loadCaptureCalled) {
@@ -308,6 +309,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
308
309
  addUrl(this, url);
309
310
  var method = ('' + (target && target instanceof origRequest && target.method || opts.method || 'GET')).toUpperCase();
310
311
  this.params.method = method;
312
+ this.body = opts.body;
311
313
  this.txSize = (0, _dataSize.dataSize)(opts.body) || 0;
312
314
  }
313
315
 
@@ -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.
@@ -91,6 +91,13 @@ class Serializer extends _sharedContext.SharedContext {
91
91
  break;
92
92
  case 2:
93
93
  fields.push(addString(params.method), (0, _belSerializer.numeric)(params.status), addString(params.host), addString(params.pathname), (0, _belSerializer.numeric)(metrics.txSize), (0, _belSerializer.numeric)(metrics.rxSize), attrs.isFetch ? 1 : attrs.isJSONP ? 2 : '', addString(node.id), (0, _belSerializer.nullable)(node.dt && node.dt.spanId, addString, true) + (0, _belSerializer.nullable)(node.dt && node.dt.traceId, addString, true) + (0, _belSerializer.nullable)(node.dt && node.dt.timestamp, _belSerializer.numeric, false));
94
+
95
+ // add params.gql here
96
+ if (Object.keys(params?.gql || {}).length) {
97
+ var ajaxAttrParts = (0, _belSerializer.addCustomAttributes)(params.gql, addString);
98
+ children = children.concat(ajaxAttrParts);
99
+ attrCount = ajaxAttrParts.length;
100
+ }
94
101
  break;
95
102
  case 4:
96
103
  var tracedTime = attrs.tracedTime;
@@ -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;
package/dist/cjs/index.js CHANGED
@@ -69,15 +69,8 @@ Object.defineProperty(exports, "Spa", {
69
69
  return _spa.Spa;
70
70
  }
71
71
  });
72
- Object.defineProperty(exports, "WorkerAgent", {
73
- enumerable: true,
74
- get: function () {
75
- return _workerAgent.WorkerAgent;
76
- }
77
- });
78
72
  var _agent = require("./loaders/agent");
79
73
  var _browserAgent = require("./loaders/browser-agent");
80
- var _workerAgent = require("./loaders/worker-agent");
81
74
  var _microAgent = require("./loaders/micro-agent");
82
75
  var _ajax = require("./features/ajax");
83
76
  var _jserrors = require("./features/jserrors");
@@ -99,8 +99,8 @@ function setAPI(agentIdentifier, forceDrain) {
99
99
  (0, _console.warn)("Failed to execute setCustomAttribute.\nName must be a string type, but a type of <".concat(typeof name, "> was provided."));
100
100
  return;
101
101
  }
102
- if (!(['string', 'number'].includes(typeof value) || value === null)) {
103
- (0, _console.warn)("Failed to execute setCustomAttribute.\nNon-null value must be a string or number type, but a type of <".concat(typeof value, "> was provided."));
102
+ if (!(['string', 'number', 'boolean'].includes(typeof value) || value === null)) {
103
+ (0, _console.warn)("Failed to execute setCustomAttribute.\nNon-null value must be a string, number or boolean type, but a type of <".concat(typeof value, "> was provided."));
104
104
  return;
105
105
  }
106
106
  return appendJsAttribute(name, value, 'setCustomAttribute', persistAttribute);
@@ -13,8 +13,12 @@ import 'core-js/stable/array/some';
13
13
  import 'core-js/stable/object/assign';
14
14
  import 'core-js/stable/object/entries';
15
15
  import 'core-js/stable/object/values';
16
+ import 'core-js/stable/object/from-entries';
16
17
  import 'core-js/stable/map';
17
18
  import 'core-js/stable/reflect';
18
19
  import 'core-js/stable/set';
19
20
  import 'core-js/stable/weak-set';
20
- import 'core-js/stable/object/get-own-property-descriptors';
21
+ import 'core-js/stable/object/get-own-property-descriptors';
22
+ import 'core-js/stable/url';
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
  }