@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.
- package/CHANGELOG.md +23 -0
- package/dist/cjs/cdn/polyfills.js +5 -1
- package/dist/cjs/common/config/state/configurable.js +1 -1
- package/dist/cjs/common/config/state/init.js +1 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/session/session-entity.js +3 -2
- package/dist/cjs/common/url/parse-url.js +21 -44
- package/dist/cjs/common/util/type-check.js +14 -0
- package/dist/cjs/common/vitals/first-input-delay.js +1 -2
- package/dist/cjs/common/vitals/largest-contentful-paint.js +1 -2
- package/dist/cjs/common/vitals/vital-metric.js +2 -12
- package/dist/cjs/features/ajax/aggregate/gql.js +94 -0
- package/dist/cjs/features/ajax/aggregate/index.js +13 -1
- package/dist/cjs/features/ajax/instrument/index.js +2 -0
- package/dist/cjs/features/jserrors/aggregate/index.js +1 -1
- package/dist/cjs/features/page_view_event/aggregate/index.js +2 -0
- package/dist/cjs/features/page_view_timing/aggregate/index.js +10 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +6 -6
- package/dist/cjs/features/session_trace/aggregate/index.js +14 -3
- package/dist/cjs/features/spa/aggregate/index.js +5 -3
- package/dist/cjs/features/spa/aggregate/serializer.js +7 -0
- package/dist/cjs/features/utils/instrument-base.js +1 -1
- package/dist/cjs/index.js +0 -7
- package/dist/cjs/loaders/api/api.js +2 -2
- package/dist/esm/cdn/polyfills.js +5 -1
- package/dist/esm/common/config/state/configurable.js +1 -1
- package/dist/esm/common/config/state/init.js +1 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/session/session-entity.js +3 -2
- package/dist/esm/common/url/parse-url.js +22 -45
- package/dist/esm/common/util/type-check.js +8 -0
- package/dist/esm/common/vitals/first-input-delay.js +1 -2
- package/dist/esm/common/vitals/largest-contentful-paint.js +1 -2
- package/dist/esm/common/vitals/vital-metric.js +1 -11
- package/dist/esm/features/ajax/aggregate/gql.js +89 -0
- package/dist/esm/features/ajax/aggregate/index.js +13 -1
- package/dist/esm/features/ajax/instrument/index.js +2 -0
- package/dist/esm/features/jserrors/aggregate/index.js +1 -1
- package/dist/esm/features/page_view_event/aggregate/index.js +2 -0
- package/dist/esm/features/page_view_timing/aggregate/index.js +9 -0
- package/dist/esm/features/session_replay/aggregate/index.js +6 -6
- package/dist/esm/features/session_trace/aggregate/index.js +14 -3
- package/dist/esm/features/spa/aggregate/index.js +5 -3
- package/dist/esm/features/spa/aggregate/serializer.js +7 -0
- package/dist/esm/features/utils/instrument-base.js +1 -1
- package/dist/esm/index.js +0 -1
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/types/common/config/state/configurable.d.ts.map +1 -1
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/url/parse-url.d.ts +12 -1
- package/dist/types/common/url/parse-url.d.ts.map +1 -1
- package/dist/types/common/util/type-check.d.ts +7 -0
- package/dist/types/common/util/type-check.d.ts.map +1 -0
- package/dist/types/common/vitals/vital-metric.d.ts +1 -2
- package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/gql.d.ts +29 -0
- package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -0
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts +1 -0
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/serializer.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/loaders/configure/public-path.npm.d.ts.map +1 -1
- package/package.json +1 -9
- package/src/cdn/polyfills.js +4 -0
- package/src/common/config/state/configurable.js +2 -1
- package/src/common/config/state/init.js +1 -0
- package/src/common/session/session-entity.js +3 -2
- package/src/common/url/parse-url.js +22 -50
- package/src/common/util/type-check.js +8 -0
- package/src/common/vitals/first-input-delay.js +1 -2
- package/src/common/vitals/largest-contentful-paint.js +1 -2
- package/src/common/vitals/vital-metric.js +2 -12
- package/src/features/ajax/aggregate/gql.js +95 -0
- package/src/features/ajax/aggregate/index.js +11 -1
- package/src/features/ajax/instrument/index.js +3 -0
- package/src/features/jserrors/aggregate/index.js +1 -1
- package/src/features/page_view_event/aggregate/index.js +2 -0
- package/src/features/page_view_timing/aggregate/index.js +11 -0
- package/src/features/session_replay/aggregate/index.js +6 -6
- package/src/features/session_trace/aggregate/index.js +10 -2
- package/src/features/spa/aggregate/index.js +5 -3
- package/src/features/spa/aggregate/serializer.js +7 -1
- package/src/features/utils/instrument-base.js +1 -1
- package/src/index.js +0 -1
- package/src/loaders/api/api.js +2 -2
- package/src/loaders/configure/public-path.npm.js +0 -1
- package/dist/cjs/cdn/worker.js +0 -16
- package/dist/cjs/loaders/worker-agent.js +0 -24
- package/dist/esm/cdn/worker.js +0 -14
- package/dist/esm/loaders/worker-agent.js +0 -18
- package/dist/types/cdn/worker.d.ts +0 -2
- package/dist/types/cdn/worker.d.ts.map +0 -1
- package/dist/types/loaders/worker-agent.d.ts +0 -8
- package/dist/types/loaders/worker-agent.d.ts.map +0 -1
- package/src/cdn/worker.js +0 -21
- package/src/loaders/worker-agent.js +0 -24
|
@@ -24,9 +24,10 @@ const model = {
|
|
|
24
24
|
inactiveAt: 0,
|
|
25
25
|
expiresAt: 0,
|
|
26
26
|
updatedAt: Date.now(),
|
|
27
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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?.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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,
|
|
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":"
|
|
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":";;;;;;;;;;;;;;;
|
|
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):
|
|
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":"
|
|
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
|
|
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
|
|
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"}
|