@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
@@ -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.244.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.244.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,58 +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
- if (isBrowserScope) {
23
- // Use an anchor dom element to resolve the url natively.
24
- urlEl = document.createElement('a');
25
- urlEl.href = url;
26
- } else {
27
- try {
28
- urlEl = new URL(url, location.href);
29
- } catch (err) {
30
- return ret;
14
+ try {
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
- var firstSplit = urlEl.href.split('://');
35
- if (!ret.port && firstSplit[1]) {
36
- ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1];
37
- }
38
- if (!ret.port || ret.port === '0') ret.port = firstSplit[0] === 'https' ? '443' : '80';
39
-
40
- // Host not provided in IE for relative urls
41
- ret.hostname = urlEl.hostname || location.hostname;
42
- ret.pathname = urlEl.pathname;
43
- ret.protocol = firstSplit[0];
44
-
45
- // Pathname sometimes doesn't have leading slash (IE 8 and 9)
46
- if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname;
47
-
48
- // urlEl.protocol is ':' in old ie when protocol is not specified
49
- var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol;
50
- var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port;
51
-
52
- // urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin
53
- ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain);
54
-
55
- // Only cache if url doesn't have a path
56
- if (ret.pathname === '/') {
57
- stringsToParsedUrls[url] = ret;
58
- }
59
- return ret;
60
37
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tests a passed object to see if it is a pure object or not. All non-primatives in JS
3
+ * are technically objects and would pass a `typeof` check.
4
+ * @param {*} obj Input object to be tested
5
+ **/
6
+ export function isPureObject(obj) {
7
+ return obj?.constructor === {}.constructor;
8
+ }
@@ -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
  }
@@ -0,0 +1,89 @@
1
+ import { isPureObject } from '../../../common/util/type-check';
2
+
3
+ /**
4
+ * @typedef {object} GQLMetadata
5
+ * @property {string} operationName Name of the operation
6
+ * @property {string} operationType Type of the operation
7
+ * @property {string} operationFramework Framework responsible for the operation
8
+ */
9
+
10
+ /**
11
+ * Parses and returns the graphql metadata from a network request. If the network
12
+ * request is not a graphql call, undefined will be returned.
13
+ * @param {object|string} body Ajax request body
14
+ * @param {string} query Ajax request query param string
15
+ * @returns {GQLMetadata | undefined}
16
+ */
17
+ export function parseGQL() {
18
+ let {
19
+ body,
20
+ query
21
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
22
+ if (!body && !query) return;
23
+ try {
24
+ const gqlBody = parseBatchGQL(parseGQLContents(body));
25
+ if (gqlBody) return gqlBody;
26
+ const gqlQuery = parseSingleGQL(parseGQLQueryString(query));
27
+ if (gqlQuery) return gqlQuery;
28
+ } catch (err) {
29
+ // parsing failed, return undefined
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @param {string|Object} gql The GraphQL object body sent to a GQL server
35
+ * @returns {GQLMetadata}
36
+ */
37
+ function parseSingleGQL(contents) {
38
+ if (typeof contents !== 'object' || !contents.query || typeof contents.query !== 'string') return;
39
+
40
+ /** parses gql query string and returns [fullmatch, type match, name match] */
41
+ const matches = contents.query.trim().match(/^(query|mutation|subscription)\s?(\w*)/);
42
+ const operationType = matches?.[1];
43
+ if (!operationType) return;
44
+ const operationName = contents.operationName || matches?.[2] || 'Anonymous';
45
+ return {
46
+ operationName,
47
+ // the operation name of the indiv query
48
+ operationType,
49
+ // query, mutation, or subscription,
50
+ operationFramework: 'GraphQL'
51
+ };
52
+ }
53
+ function parseBatchGQL(contents) {
54
+ if (!contents) return;
55
+ if (!Array.isArray(contents)) contents = [contents];
56
+ const opNames = [];
57
+ const opTypes = [];
58
+ for (let content of contents) {
59
+ const operation = parseSingleGQL(content);
60
+ if (!operation) continue;
61
+ opNames.push(operation.operationName);
62
+ opTypes.push(operation.operationType);
63
+ }
64
+ if (!opTypes.length) return;
65
+ return {
66
+ operationName: opNames.join(','),
67
+ // the operation name of the indiv query -- joined by ',' for batched results
68
+ operationType: opTypes.join(','),
69
+ // query, mutation, or subscription -- joined by ',' for batched results
70
+ operationFramework: 'GraphQL'
71
+ };
72
+ }
73
+ function parseGQLContents(gqlContents) {
74
+ let contents;
75
+ if (!gqlContents || typeof gqlContents !== 'string' && typeof gqlContents !== 'object') return;else if (typeof gqlContents === 'string') contents = JSON.parse(gqlContents);else contents = gqlContents;
76
+ if (!isPureObject(contents) && !Array.isArray(contents)) return;
77
+ let isValid = false;
78
+ if (Array.isArray(contents)) isValid = contents.some(x => validateGQLObject(x));else isValid = validateGQLObject(contents);
79
+ if (!isValid) return;
80
+ return contents;
81
+ }
82
+ function parseGQLQueryString(gqlQueryString) {
83
+ if (!gqlQueryString || typeof gqlQueryString !== 'string') return;
84
+ const params = new URLSearchParams(gqlQueryString);
85
+ return parseGQLContents(Object.fromEntries(params));
86
+ }
87
+ function validateGQLObject(obj) {
88
+ return !(typeof obj !== 'object' || !obj.query || typeof obj.query !== 'string');
89
+ }
@@ -13,6 +13,7 @@ import { FEATURE_NAME } from '../constants';
13
13
  import { FEATURE_NAMES } from '../../../loaders/features/features';
14
14
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
15
15
  import { AggregateBase } from '../../utils/aggregate-base';
16
+ import { parseGQL } from './gql';
16
17
  export class Aggregate extends AggregateBase {
17
18
  static featureName = FEATURE_NAME;
18
19
  constructor(agentIdentifier, aggregator) {
@@ -109,6 +110,13 @@ export class Aggregate extends AggregateBase {
109
110
  event.spanTimestamp = xhrContext.dt.timestamp;
110
111
  }
111
112
 
113
+ // parsed from the AJAX body, looking for operationName param & parsing query for operationType
114
+ event.gql = params.gql = parseGQL({
115
+ body: this.body,
116
+ query: this?.parsedOrigin?.search
117
+ });
118
+ if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, ee);
119
+
112
120
  // if the ajax happened inside an interaction, hold it until the interaction finishes
113
121
  if (this.spaNode) {
114
122
  var interactionId = this.spaNode.interaction.id;
@@ -198,7 +206,11 @@ export class Aggregate extends AggregateBase {
198
206
  var insert = '2,';
199
207
 
200
208
  // add custom attributes
201
- var attrParts = addCustomAttributes(getInfo(agentIdentifier).jsAttributes || {}, this.addString);
209
+ // gql decorators are added as custom attributes to alleviate need for new BEL schema
210
+ var attrParts = addCustomAttributes({
211
+ ...(getInfo(agentIdentifier).jsAttributes || {}),
212
+ ...(event.gql || {})
213
+ }, this.addString);
202
214
  fields.unshift(numeric(attrParts.length));
203
215
  insert += fields.join(',');
204
216
  if (attrParts && attrParts.length > 0) {
@@ -153,6 +153,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
153
153
  if (size) metrics.txSize = size;
154
154
  }
155
155
  this.startTime = now();
156
+ this.body = data;
156
157
  this.listener = function (evt) {
157
158
  try {
158
159
  if (evt.type === 'abort' && !context.loadCaptureCalled) {
@@ -300,6 +301,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
300
301
  addUrl(this, url);
301
302
  var method = ('' + (target && target instanceof origRequest && target.method || opts.method || 'GET')).toUpperCase();
302
303
  this.params.method = method;
304
+ this.body = opts.body;
303
305
  this.txSize = dataSize(opts.body) || 0;
304
306
  }
305
307
 
@@ -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 = {
@@ -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.
@@ -84,6 +84,13 @@ export class Serializer extends SharedContext {
84
84
  break;
85
85
  case 2:
86
86
  fields.push(addString(params.method), numeric(params.status), addString(params.host), addString(params.pathname), numeric(metrics.txSize), numeric(metrics.rxSize), attrs.isFetch ? 1 : attrs.isJSONP ? 2 : '', addString(node.id), nullable(node.dt && node.dt.spanId, addString, true) + nullable(node.dt && node.dt.traceId, addString, true) + nullable(node.dt && node.dt.timestamp, numeric, false));
87
+
88
+ // add params.gql here
89
+ if (Object.keys(params?.gql || {}).length) {
90
+ var ajaxAttrParts = addCustomAttributes(params.gql, addString);
91
+ children = children.concat(ajaxAttrParts);
92
+ attrCount = ajaxAttrParts.length;
93
+ }
87
94
  break;
88
95
  case 4:
89
96
  var tracedTime = attrs.tracedTime;
@@ -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;
package/dist/esm/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export { Agent } from './loaders/agent';
2
2
  export { BrowserAgent } from './loaders/browser-agent';
3
- export { WorkerAgent } from './loaders/worker-agent';
4
3
  export { MicroAgent } from './loaders/micro-agent';
5
4
  export { Ajax } from './features/ajax';
6
5
  export { JSErrors } from './features/jserrors';
@@ -90,8 +90,8 @@ export function setAPI(agentIdentifier, forceDrain) {
90
90
  warn("Failed to execute setCustomAttribute.\nName must be a string type, but a type of <".concat(typeof name, "> was provided."));
91
91
  return;
92
92
  }
93
- if (!(['string', 'number'].includes(typeof value) || value === null)) {
94
- warn("Failed to execute setCustomAttribute.\nNon-null value must be a string or number type, but a type of <".concat(typeof value, "> was provided."));
93
+ if (!(['string', 'number', 'boolean'].includes(typeof value) || value === null)) {
94
+ 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."));
95
95
  return;
96
96
  }
97
97
  return appendJsAttribute(name, value, 'setCustomAttribute', persistAttribute);
@@ -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,wCA4DC"}
1
+ {"version":3,"file":"parse-url.d.ts","sourceRoot":"","sources":["../../../../src/common/url/parse-url.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;EAkCC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests a passed object to see if it is a pure object or not. All non-primatives in JS
3
+ * are technically objects and would pass a `typeof` check.
4
+ * @param {*} obj Input object to be tested
5
+ **/
6
+ export function isPureObject(obj: any): boolean;
7
+ //# sourceMappingURL=type-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-check.d.ts","sourceRoot":"","sources":["../../../../src/common/util/type-check.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,gDAEC"}
@@ -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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @typedef {object} GQLMetadata
3
+ * @property {string} operationName Name of the operation
4
+ * @property {string} operationType Type of the operation
5
+ * @property {string} operationFramework Framework responsible for the operation
6
+ */
7
+ /**
8
+ * Parses and returns the graphql metadata from a network request. If the network
9
+ * request is not a graphql call, undefined will be returned.
10
+ * @param {object|string} body Ajax request body
11
+ * @param {string} query Ajax request query param string
12
+ * @returns {GQLMetadata | undefined}
13
+ */
14
+ export function parseGQL({ body, query }?: object | string): GQLMetadata | undefined;
15
+ export type GQLMetadata = {
16
+ /**
17
+ * Name of the operation
18
+ */
19
+ operationName: string;
20
+ /**
21
+ * Type of the operation
22
+ */
23
+ operationType: string;
24
+ /**
25
+ * Framework responsible for the operation
26
+ */
27
+ operationFramework: string;
28
+ };
29
+ //# sourceMappingURL=gql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gql.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/gql.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,2CAJW,MAAM,GAAC,MAAM,GAEX,WAAW,GAAG,SAAS,CAYnC;;;;;mBAtBa,MAAM;;;;mBACN,MAAM;;;;wBACN,MAAM"}