@newrelic/browser-agent 1.245.0 → 1.246.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/cdn/polyfills.js +2 -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 +20 -44
- 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/index.js +1 -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/utils/instrument-base.js +1 -1
- package/dist/esm/cdn/polyfills.js +2 -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 +21 -45
- 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/index.js +1 -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/utils/instrument-base.js +1 -1
- 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/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/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/loaders/configure/public-path.npm.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cdn/polyfills.js +1 -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 +21 -51
- 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/index.js +2 -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/utils/instrument-base.js +1 -1
- package/src/loaders/configure/public-path.npm.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.246.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.245.0...v1.246.0) (2023-10-23)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add network info to all page view timing events ([#768](https://github.com/newrelic/newrelic-browser-agent/issues/768)) ([757cf19](https://github.com/newrelic/newrelic-browser-agent/commit/757cf1953af471118d809414cd41297a87c89a34))
|
|
12
|
+
* Replace url parsing with URL class ([#781](https://github.com/newrelic/newrelic-browser-agent/issues/781)) ([4206263](https://github.com/newrelic/newrelic-browser-agent/commit/42062638850b4b410ac75eb008120ec4a82583c1))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* Add feature flag support for Browser Interactions ([#779](https://github.com/newrelic/newrelic-browser-agent/issues/779)) ([aa39c6c](https://github.com/newrelic/newrelic-browser-agent/commit/aa39c6cd2aeefaecd803aeb0736ad7aef8477bc4))
|
|
18
|
+
* Add first harvest of session flags to RUM and Trace ([#765](https://github.com/newrelic/newrelic-browser-agent/issues/765)) ([ab2e9dd](https://github.com/newrelic/newrelic-browser-agent/commit/ab2e9dd2252143635b67ea9da4e07867ec68cd0f))
|
|
19
|
+
|
|
6
20
|
## [1.245.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.244.0...v1.245.0) (2023-10-18)
|
|
7
21
|
|
|
8
22
|
|
|
@@ -17,4 +17,5 @@ require("core-js/stable/set");
|
|
|
17
17
|
require("core-js/stable/weak-set");
|
|
18
18
|
require("core-js/stable/object/get-own-property-descriptors");
|
|
19
19
|
require("core-js/stable/url");
|
|
20
|
-
require("core-js/stable/url-search-params");
|
|
20
|
+
require("core-js/stable/url-search-params");
|
|
21
|
+
require("core-js/stable/string/starts-with");
|
|
@@ -15,7 +15,7 @@ function getModeledObject(obj, model) {
|
|
|
15
15
|
for (let key in target) {
|
|
16
16
|
if (obj[key] !== undefined) {
|
|
17
17
|
try {
|
|
18
|
-
if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
|
|
18
|
+
if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]));else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
|
|
19
19
|
} catch (e) {
|
|
20
20
|
(0, _console.warn)('An error occurred while setting a property of a Configurable', e);
|
|
21
21
|
}
|
|
@@ -31,9 +31,10 @@ const model = {
|
|
|
31
31
|
inactiveAt: 0,
|
|
32
32
|
expiresAt: 0,
|
|
33
33
|
updatedAt: Date.now(),
|
|
34
|
-
|
|
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
|
-
|
|
156
|
+
this.isNew = !Object.keys(initialRead).length;
|
|
156
157
|
// if its a "new" session, we write to storage API with the default values. These values may change over the lifespan of the agent run.
|
|
157
158
|
// we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
|
|
158
159
|
if (this.isNew) this.write((0, _configurable.getModeledObject)(this.state, model), true);else this.sync(initialRead);
|
|
@@ -10,58 +10,34 @@ var _runtime = require("../constants/runtime");
|
|
|
10
10
|
* SPDX-License-Identifier: Apache-2.0
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
var stringsToParsedUrls = {};
|
|
14
13
|
function parseUrl(url) {
|
|
15
|
-
if (url in stringsToParsedUrls) {
|
|
16
|
-
return stringsToParsedUrls[url];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
// Return if URL is a data URL, parseUrl assumes urls are http/https
|
|
20
15
|
if ((url || '').indexOf('data:') === 0) {
|
|
21
16
|
return {
|
|
22
17
|
protocol: 'data'
|
|
23
18
|
};
|
|
24
19
|
}
|
|
25
|
-
let urlEl;
|
|
26
|
-
var location = _runtime.globalScope?.location;
|
|
27
|
-
var ret = {};
|
|
28
20
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
const parsedUrl = new URL(url, location.href);
|
|
22
|
+
const returnVal = {
|
|
23
|
+
port: parsedUrl.port,
|
|
24
|
+
hostname: parsedUrl.hostname,
|
|
25
|
+
pathname: parsedUrl.pathname,
|
|
26
|
+
search: parsedUrl.search,
|
|
27
|
+
protocol: parsedUrl.protocol.slice(0, parsedUrl.protocol.indexOf(':')),
|
|
28
|
+
sameOrigin: parsedUrl.protocol === _runtime.globalScope?.location?.protocol && parsedUrl.host === _runtime.globalScope?.location?.host
|
|
29
|
+
};
|
|
30
|
+
if (!returnVal.port || returnVal.port === '') {
|
|
31
|
+
if (parsedUrl.protocol === 'http:') returnVal.port = '80';
|
|
32
|
+
if (parsedUrl.protocol === 'https:') returnVal.port = '443';
|
|
37
33
|
}
|
|
34
|
+
if (!returnVal.pathname || returnVal.pathname === '') {
|
|
35
|
+
returnVal.pathname = '/';
|
|
36
|
+
} else if (!returnVal.pathname.startsWith('/')) {
|
|
37
|
+
returnVal.pathname = "/".concat(returnVal.pathname);
|
|
38
|
+
}
|
|
39
|
+
return returnVal;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return {};
|
|
38
42
|
}
|
|
39
|
-
ret.port = urlEl.port;
|
|
40
|
-
ret.search = urlEl.search;
|
|
41
|
-
var firstSplit = urlEl.href.split('://');
|
|
42
|
-
if (!ret.port && firstSplit[1]) {
|
|
43
|
-
ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1];
|
|
44
|
-
}
|
|
45
|
-
if (!ret.port || ret.port === '0') ret.port = firstSplit[0] === 'https' ? '443' : '80';
|
|
46
|
-
|
|
47
|
-
// Host not provided in IE for relative urls
|
|
48
|
-
ret.hostname = urlEl.hostname || location.hostname;
|
|
49
|
-
ret.pathname = urlEl.pathname;
|
|
50
|
-
ret.protocol = firstSplit[0];
|
|
51
|
-
|
|
52
|
-
// Pathname sometimes doesn't have leading slash (IE 8 and 9)
|
|
53
|
-
if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname;
|
|
54
|
-
|
|
55
|
-
// urlEl.protocol is ':' in old ie when protocol is not specified
|
|
56
|
-
var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol;
|
|
57
|
-
var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port;
|
|
58
|
-
|
|
59
|
-
// urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin
|
|
60
|
-
ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain);
|
|
61
|
-
|
|
62
|
-
// Only cache if url doesn't have a path
|
|
63
|
-
if (ret.pathname === '/') {
|
|
64
|
-
stringsToParsedUrls[url] = ret;
|
|
65
|
-
}
|
|
66
|
-
return ret;
|
|
67
43
|
}
|
|
@@ -16,8 +16,7 @@ class VitalMetric {
|
|
|
16
16
|
let {
|
|
17
17
|
value,
|
|
18
18
|
entries = [],
|
|
19
|
-
attrs = {}
|
|
20
|
-
shouldAddConnectionAttributes = false
|
|
19
|
+
attrs = {}
|
|
21
20
|
} = _ref;
|
|
22
21
|
if (value < 0) return;
|
|
23
22
|
const state = {
|
|
@@ -26,7 +25,6 @@ class VitalMetric {
|
|
|
26
25
|
entries,
|
|
27
26
|
attrs
|
|
28
27
|
};
|
|
29
|
-
if (shouldAddConnectionAttributes) addConnectionAttributes(state.attrs);
|
|
30
28
|
this.history.push(state);
|
|
31
29
|
this.#subscribers.forEach(cb => {
|
|
32
30
|
try {
|
|
@@ -60,12 +58,4 @@ class VitalMetric {
|
|
|
60
58
|
};
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
|
-
exports.VitalMetric = VitalMetric;
|
|
64
|
-
function addConnectionAttributes(obj) {
|
|
65
|
-
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; // to date, both window & worker shares the same support for connection
|
|
66
|
-
if (!connection) return;
|
|
67
|
-
if (connection.type) obj['net-type'] = connection.type;
|
|
68
|
-
if (connection.effectiveType) obj['net-etype'] = connection.effectiveType;
|
|
69
|
-
if (connection.rtt) obj['net-rtt'] = connection.rtt;
|
|
70
|
-
if (connection.downlink) obj['net-dlink'] = connection.downlink;
|
|
71
|
-
}
|
|
61
|
+
exports.VitalMetric = VitalMetric;
|
|
@@ -122,6 +122,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
122
122
|
body: this.body,
|
|
123
123
|
query: this?.parsedOrigin?.search
|
|
124
124
|
});
|
|
125
|
+
if (event.gql) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', (0, _stringify.stringify)(event.gql).length], undefined, _features.FEATURE_NAMES.metrics, ee);
|
|
125
126
|
|
|
126
127
|
// if the ajax happened inside an interaction, hold it until the interaction finishes
|
|
127
128
|
if (this.spaNode) {
|
|
@@ -187,7 +187,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
187
187
|
params.pageview = 1;
|
|
188
188
|
this.pageviewReported[bucketHash] = true;
|
|
189
189
|
}
|
|
190
|
-
if (agentRuntime?.session?.state?.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
533
|
+
n: stns.length,
|
|
534
|
+
// node count
|
|
535
|
+
...firstHarvestOfSession
|
|
524
536
|
},
|
|
525
|
-
|
|
526
537
|
body: {
|
|
527
538
|
res: stns
|
|
528
539
|
}
|
|
@@ -67,7 +67,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
67
67
|
depth: 0,
|
|
68
68
|
harvestTimeSeconds: (0, _config.getConfigurationValue)(agentIdentifier, 'spa.harvestTimeSeconds') || 10,
|
|
69
69
|
interactionsToHarvest: [],
|
|
70
|
-
interactionsSent: []
|
|
70
|
+
interactionsSent: [],
|
|
71
|
+
// The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
|
|
72
|
+
disableSpaFix: ((0, _config.getConfigurationValue)(agentIdentifier, 'feature_flags') || []).indexOf('disable-spa-fix') > -1
|
|
71
73
|
};
|
|
72
74
|
this.serializer = new _serializer.Serializer(this);
|
|
73
75
|
const {
|
|
@@ -287,7 +289,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
287
289
|
// context is stored on the xhr and is shared with all callbacks associated
|
|
288
290
|
// with the new xhr
|
|
289
291
|
(0, _registerHandler.registerHandler)('new-xhr', function () {
|
|
290
|
-
if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
292
|
+
if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
291
293
|
/*
|
|
292
294
|
* The previous interaction was discarded before a route change. Restore the interaction
|
|
293
295
|
* in case this XHR is associated with a route change.
|
|
@@ -378,7 +380,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
378
380
|
}, this.featureName, jsonpEE);
|
|
379
381
|
(0, _registerHandler.registerHandler)(FETCH_START, function (fetchArguments, dtPayload) {
|
|
380
382
|
if (fetchArguments) {
|
|
381
|
-
if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
383
|
+
if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
382
384
|
/*
|
|
383
385
|
* The previous interaction was discarded before a route change. Restore the interaction
|
|
384
386
|
* in case this XHR is associated with a route change.
|
|
@@ -139,7 +139,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
139
139
|
if (featureName === _features.FEATURE_NAMES.sessionReplay) {
|
|
140
140
|
if (!_config.originals.MO) return false; // Session Replay cannot work without Mutation Observer
|
|
141
141
|
if ((0, _config.getConfigurationValue)(this.agentIdentifier, 'session_trace.enabled') === false) return false; // Session Replay as of now is tightly coupled with Session Trace in the UI
|
|
142
|
-
return !!session?.isNew || !!session?.state.
|
|
142
|
+
return !!session?.isNew || !!session?.state.sessionReplayMode; // Session Replay should only try to run if already running from a previous page, or at the beginning of a session
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
return true;
|
|
@@ -20,4 +20,5 @@ import 'core-js/stable/set';
|
|
|
20
20
|
import 'core-js/stable/weak-set';
|
|
21
21
|
import 'core-js/stable/object/get-own-property-descriptors';
|
|
22
22
|
import 'core-js/stable/url';
|
|
23
|
-
import 'core-js/stable/url-search-params';
|
|
23
|
+
import 'core-js/stable/url-search-params';
|
|
24
|
+
import 'core-js/stable/string/starts-with';
|
|
@@ -9,7 +9,7 @@ export function getModeledObject(obj, model) {
|
|
|
9
9
|
for (let key in target) {
|
|
10
10
|
if (obj[key] !== undefined) {
|
|
11
11
|
try {
|
|
12
|
-
if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
|
|
12
|
+
if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]));else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
|
|
13
13
|
} catch (e) {
|
|
14
14
|
warn('An error occurred while setting a property of a Configurable', e);
|
|
15
15
|
}
|
|
@@ -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,59 +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
|
-
let urlEl;
|
|
20
|
-
var location = globalScope?.location;
|
|
21
|
-
var ret = {};
|
|
22
14
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
const parsedUrl = new URL(url, location.href);
|
|
16
|
+
const returnVal = {
|
|
17
|
+
port: parsedUrl.port,
|
|
18
|
+
hostname: parsedUrl.hostname,
|
|
19
|
+
pathname: parsedUrl.pathname,
|
|
20
|
+
search: parsedUrl.search,
|
|
21
|
+
protocol: parsedUrl.protocol.slice(0, parsedUrl.protocol.indexOf(':')),
|
|
22
|
+
sameOrigin: parsedUrl.protocol === globalScope?.location?.protocol && parsedUrl.host === globalScope?.location?.host
|
|
23
|
+
};
|
|
24
|
+
if (!returnVal.port || returnVal.port === '') {
|
|
25
|
+
if (parsedUrl.protocol === 'http:') returnVal.port = '80';
|
|
26
|
+
if (parsedUrl.protocol === 'https:') returnVal.port = '443';
|
|
31
27
|
}
|
|
28
|
+
if (!returnVal.pathname || returnVal.pathname === '') {
|
|
29
|
+
returnVal.pathname = '/';
|
|
30
|
+
} else if (!returnVal.pathname.startsWith('/')) {
|
|
31
|
+
returnVal.pathname = "/".concat(returnVal.pathname);
|
|
32
|
+
}
|
|
33
|
+
return returnVal;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return {};
|
|
32
36
|
}
|
|
33
|
-
ret.port = urlEl.port;
|
|
34
|
-
ret.search = urlEl.search;
|
|
35
|
-
var firstSplit = urlEl.href.split('://');
|
|
36
|
-
if (!ret.port && firstSplit[1]) {
|
|
37
|
-
ret.port = firstSplit[1].split('/')[0].split('@').pop().split(':')[1];
|
|
38
|
-
}
|
|
39
|
-
if (!ret.port || ret.port === '0') ret.port = firstSplit[0] === 'https' ? '443' : '80';
|
|
40
|
-
|
|
41
|
-
// Host not provided in IE for relative urls
|
|
42
|
-
ret.hostname = urlEl.hostname || location.hostname;
|
|
43
|
-
ret.pathname = urlEl.pathname;
|
|
44
|
-
ret.protocol = firstSplit[0];
|
|
45
|
-
|
|
46
|
-
// Pathname sometimes doesn't have leading slash (IE 8 and 9)
|
|
47
|
-
if (ret.pathname.charAt(0) !== '/') ret.pathname = '/' + ret.pathname;
|
|
48
|
-
|
|
49
|
-
// urlEl.protocol is ':' in old ie when protocol is not specified
|
|
50
|
-
var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol;
|
|
51
|
-
var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port;
|
|
52
|
-
|
|
53
|
-
// urlEl.hostname is not provided by IE for relative urls, but relative urls are also same-origin
|
|
54
|
-
ret.sameOrigin = sameProtocol && (!urlEl.hostname || sameDomain);
|
|
55
|
-
|
|
56
|
-
// Only cache if url doesn't have a path
|
|
57
|
-
if (ret.pathname === '/') {
|
|
58
|
-
stringsToParsedUrls[url] = ret;
|
|
59
|
-
}
|
|
60
|
-
return ret;
|
|
61
37
|
}
|
|
@@ -10,8 +10,7 @@ export class VitalMetric {
|
|
|
10
10
|
let {
|
|
11
11
|
value,
|
|
12
12
|
entries = [],
|
|
13
|
-
attrs = {}
|
|
14
|
-
shouldAddConnectionAttributes = false
|
|
13
|
+
attrs = {}
|
|
15
14
|
} = _ref;
|
|
16
15
|
if (value < 0) return;
|
|
17
16
|
const state = {
|
|
@@ -20,7 +19,6 @@ export class VitalMetric {
|
|
|
20
19
|
entries,
|
|
21
20
|
attrs
|
|
22
21
|
};
|
|
23
|
-
if (shouldAddConnectionAttributes) addConnectionAttributes(state.attrs);
|
|
24
22
|
this.history.push(state);
|
|
25
23
|
this.#subscribers.forEach(cb => {
|
|
26
24
|
try {
|
|
@@ -53,12 +51,4 @@ export class VitalMetric {
|
|
|
53
51
|
this.#subscribers.delete(callback);
|
|
54
52
|
};
|
|
55
53
|
}
|
|
56
|
-
}
|
|
57
|
-
function addConnectionAttributes(obj) {
|
|
58
|
-
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; // to date, both window & worker shares the same support for connection
|
|
59
|
-
if (!connection) return;
|
|
60
|
-
if (connection.type) obj['net-type'] = connection.type;
|
|
61
|
-
if (connection.effectiveType) obj['net-etype'] = connection.effectiveType;
|
|
62
|
-
if (connection.rtt) obj['net-rtt'] = connection.rtt;
|
|
63
|
-
if (connection.downlink) obj['net-dlink'] = connection.downlink;
|
|
64
54
|
}
|
|
@@ -115,6 +115,7 @@ export class Aggregate extends AggregateBase {
|
|
|
115
115
|
body: this.body,
|
|
116
116
|
query: this?.parsedOrigin?.search
|
|
117
117
|
});
|
|
118
|
+
if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, ee);
|
|
118
119
|
|
|
119
120
|
// if the ajax happened inside an interaction, hold it until the interaction finishes
|
|
120
121
|
if (this.spaNode) {
|
|
@@ -182,7 +182,7 @@ export class Aggregate extends AggregateBase {
|
|
|
182
182
|
params.pageview = 1;
|
|
183
183
|
this.pageviewReported[bucketHash] = true;
|
|
184
184
|
}
|
|
185
|
-
if (agentRuntime?.session?.state?.
|
|
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 = {
|