@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAiBA;IACE,2BAAiC;IACjC,mDAyOC;IAlNC,qGAAwB;IACxB;;;;4BAAoC;IACpC;;;mBAA2E;CAiN9E;8BA/O6B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAA2C;IAC3C,mDAoBC;IAjBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAiBhC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAA2C;IAC3C,mDAoBC;IAjBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAiBhC,gBAgFC;CACF;8BA9G6B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,mDAoCC;IAjCC,eAAiB;IACjB,mBAAqB;IACrB,4BAA+B;IAuB7B,4BAGQ;IAOZ;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,mDAoCC;IAjCC,eAAiB;IACjB,mBAAqB;IACrB,4BAA+B;IAuB7B,4BAGQ;IAOZ;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAsBC;IAED,qCAKC;IAED,gDAWC;IAGD;;;;kBAWC;IAGD,8BAuBC;;CACF;8BApK6B,4BAA4B;iCANzB,2CAA2C"}
|
|
@@ -37,6 +37,7 @@ export class Aggregate extends AggregateBase {
|
|
|
37
37
|
body?: undefined;
|
|
38
38
|
} | {
|
|
39
39
|
qs: {
|
|
40
|
+
fsh?: number | undefined;
|
|
40
41
|
st: any;
|
|
41
42
|
/** hr === "hasReplay" in NR1, standalone is always checked and processed before harvesting
|
|
42
43
|
* so a race condition between ST and SR states should not be a concern if implemented here */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEAyHC;IAvHC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IAuGxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEAyHC;IAvHC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IAuGxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD;;;;;;;YAkCM;0GAC8F;;YAE9F;0FAC8E;;YAE9E,4IAA4I;;;;;;MAMjJ;IAED,mEA6BC;;CACF;8BAzf6B,4BAA4B;6BAH7B,2BAA2B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA4BA;IACE,2BAAiC;IACjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA4BA;IACE,2BAAiC;IACjC,mDAgsBC;IA7rBC;;;;;;;;;;;;;;;;;MAkBC;IAED,uBAAsC;CA0qBzC;8BA5sB6B,4BAA4B;2BAJ/B,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/serializer.js"],"names":[],"mappings":"AAUA;IAII;;;;;;OAMG;IACH,gBAFU,MAAM,GAAC,SAAS,CAEK;IAGjC,0EASC;IAED,oFAMC;IAED,
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/serializer.js"],"names":[],"mappings":"AAUA;IAII;;;;;;OAMG;IACH,gBAFU,MAAM,GAAC,SAAS,CAEK;IAGjC,0EASC;IAED,oFAMC;IAED,iHA+JC;CACF;8BApM6B,wCAAwC"}
|
package/dist/types/index.d.ts
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";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-path.npm.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.npm.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"public-path.npm.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.npm.js"],"names":[],"mappings":"AAAO,2CAEN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.246.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -26,9 +26,6 @@
|
|
|
26
26
|
"loaders/micro-agent": [
|
|
27
27
|
"dist/types/loaders/micro-agent.d.ts"
|
|
28
28
|
],
|
|
29
|
-
"loaders/worker-agent": [
|
|
30
|
-
"dist/types/loaders/worker-agent.d.ts"
|
|
31
|
-
],
|
|
32
29
|
"features/ajax": [
|
|
33
30
|
"dist/types/features/ajax/index.d.ts"
|
|
34
31
|
],
|
|
@@ -81,11 +78,6 @@
|
|
|
81
78
|
"require": "./dist/cjs/loaders/micro-agent.js",
|
|
82
79
|
"default": "./dist/esm/loaders/micro-agent.js"
|
|
83
80
|
},
|
|
84
|
-
"./loaders/worker-agent": {
|
|
85
|
-
"types": "./dist/types/loaders/worker-agent.d.ts",
|
|
86
|
-
"require": "./dist/cjs/loaders/worker-agent.js",
|
|
87
|
-
"default": "./dist/esm/loaders/worker-agent.js"
|
|
88
|
-
},
|
|
89
81
|
"./features/ajax": {
|
|
90
82
|
"types": "./dist/types/features/ajax/index.d.ts",
|
|
91
83
|
"require": "./dist/cjs/features/ajax/index.js",
|
package/src/cdn/polyfills.js
CHANGED
|
@@ -13,8 +13,12 @@ import 'core-js/stable/array/some'
|
|
|
13
13
|
import 'core-js/stable/object/assign'
|
|
14
14
|
import 'core-js/stable/object/entries'
|
|
15
15
|
import 'core-js/stable/object/values'
|
|
16
|
+
import 'core-js/stable/object/from-entries'
|
|
16
17
|
import 'core-js/stable/map'
|
|
17
18
|
import 'core-js/stable/reflect'
|
|
18
19
|
import 'core-js/stable/set'
|
|
19
20
|
import 'core-js/stable/weak-set'
|
|
20
21
|
import 'core-js/stable/object/get-own-property-descriptors'
|
|
22
|
+
import 'core-js/stable/url'
|
|
23
|
+
import 'core-js/stable/url-search-params'
|
|
24
|
+
import 'core-js/stable/string/starts-with'
|
|
@@ -13,7 +13,8 @@ export function getModeledObject (obj, model) {
|
|
|
13
13
|
for (let key in target) {
|
|
14
14
|
if (obj[key] !== undefined) {
|
|
15
15
|
try {
|
|
16
|
-
if (
|
|
16
|
+
if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]))
|
|
17
|
+
else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key])
|
|
17
18
|
else output[key] = obj[key]
|
|
18
19
|
} catch (e) {
|
|
19
20
|
warn('An error occurred while setting a property of a Configurable', e)
|
|
@@ -29,6 +29,7 @@ const model = () => {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
return {
|
|
32
|
+
feature_flags: [],
|
|
32
33
|
proxy: {
|
|
33
34
|
assets: undefined, // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
|
|
34
35
|
beacon: undefined // likewise for the url to which we send analytics
|
|
@@ -25,9 +25,10 @@ const model = {
|
|
|
25
25
|
inactiveAt: 0,
|
|
26
26
|
expiresAt: 0,
|
|
27
27
|
updatedAt: Date.now(),
|
|
28
|
-
|
|
28
|
+
sessionReplayMode: MODE.OFF,
|
|
29
29
|
sessionReplaySentFirstChunk: false,
|
|
30
30
|
sessionTraceMode: MODE.OFF,
|
|
31
|
+
traceHarvestStarted: false,
|
|
31
32
|
custom: {}
|
|
32
33
|
}
|
|
33
34
|
export const SESSION_EVENTS = {
|
|
@@ -140,7 +141,7 @@ export class SessionEntity {
|
|
|
140
141
|
|
|
141
142
|
// The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
|
|
142
143
|
// can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
|
|
143
|
-
|
|
144
|
+
this.isNew = !Object.keys(initialRead).length
|
|
144
145
|
// 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.
|
|
145
146
|
// we can use a modeled object here to help us know and manage what values are being used. -- see "model" above
|
|
146
147
|
if (this.isNew) this.write(getModeledObject(this.state, model), true)
|
|
@@ -3,15 +3,9 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { globalScope
|
|
7
|
-
|
|
8
|
-
var stringsToParsedUrls = {}
|
|
6
|
+
import { globalScope } from '../constants/runtime'
|
|
9
7
|
|
|
10
8
|
export function parseUrl (url) {
|
|
11
|
-
if (url in stringsToParsedUrls) {
|
|
12
|
-
return stringsToParsedUrls[url]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
9
|
// Return if URL is a data URL, parseUrl assumes urls are http/https
|
|
16
10
|
if ((url || '').indexOf('data:') === 0) {
|
|
17
11
|
return {
|
|
@@ -19,52 +13,30 @@ export function parseUrl (url) {
|
|
|
19
13
|
}
|
|
20
14
|
}
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
urlEl = new URL(url, location.href)
|
|
33
|
-
} catch (err) {
|
|
34
|
-
return ret
|
|
16
|
+
try {
|
|
17
|
+
const parsedUrl = new URL(url, location.href)
|
|
18
|
+
const returnVal = {
|
|
19
|
+
port: parsedUrl.port,
|
|
20
|
+
hostname: parsedUrl.hostname,
|
|
21
|
+
pathname: parsedUrl.pathname,
|
|
22
|
+
search: parsedUrl.search,
|
|
23
|
+
protocol: parsedUrl.protocol.slice(0, parsedUrl.protocol.indexOf(':')),
|
|
24
|
+
sameOrigin: parsedUrl.protocol === globalScope?.location?.protocol && parsedUrl.host === globalScope?.location?.host
|
|
35
25
|
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
ret.port = urlEl.port
|
|
39
|
-
|
|
40
|
-
var firstSplit = urlEl.href.split('://')
|
|
41
|
-
|
|
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
|
-
|
|
50
|
-
ret.pathname = urlEl.pathname
|
|
51
|
-
|
|
52
|
-
ret.protocol = firstSplit[0]
|
|
53
26
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
var sameProtocol = !urlEl.protocol || urlEl.protocol === ':' || urlEl.protocol === location.protocol
|
|
59
|
-
var sameDomain = urlEl.hostname === location.hostname && urlEl.port === location.port
|
|
27
|
+
if (!returnVal.port || returnVal.port === '') {
|
|
28
|
+
if (parsedUrl.protocol === 'http:') returnVal.port = '80'
|
|
29
|
+
if (parsedUrl.protocol === 'https:') returnVal.port = '443'
|
|
30
|
+
}
|
|
60
31
|
|
|
61
|
-
|
|
62
|
-
|
|
32
|
+
if (!returnVal.pathname || returnVal.pathname === '') {
|
|
33
|
+
returnVal.pathname = '/'
|
|
34
|
+
} else if (!returnVal.pathname.startsWith('/')) {
|
|
35
|
+
returnVal.pathname = `/${returnVal.pathname}`
|
|
36
|
+
}
|
|
63
37
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
return returnVal
|
|
39
|
+
} catch (err) {
|
|
40
|
+
return {}
|
|
67
41
|
}
|
|
68
|
-
|
|
69
|
-
return ret
|
|
70
42
|
}
|
|
@@ -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
|
+
}
|
|
@@ -13,8 +13,7 @@ if (isBrowserScope) {
|
|
|
13
13
|
firstInputDelay.update({
|
|
14
14
|
value: entries[0].startTime,
|
|
15
15
|
entries,
|
|
16
|
-
attrs: { type: entries[0].name, fid: Math.round(value) }
|
|
17
|
-
shouldAddConnectionAttributes: true
|
|
16
|
+
attrs: { type: entries[0].name, fid: Math.round(value) }
|
|
18
17
|
})
|
|
19
18
|
})
|
|
20
19
|
}
|
|
@@ -8,7 +8,7 @@ export class VitalMetric {
|
|
|
8
8
|
this.roundingMethod = typeof roundingMethod === 'function' ? roundingMethod : Math.floor
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
update ({ value, entries = [], attrs = {}
|
|
11
|
+
update ({ value, entries = [], attrs = {} }) {
|
|
12
12
|
if (value < 0) return
|
|
13
13
|
const state = {
|
|
14
14
|
value: this.roundingMethod(value),
|
|
@@ -16,7 +16,7 @@ export class VitalMetric {
|
|
|
16
16
|
entries,
|
|
17
17
|
attrs
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
this.history.push(state)
|
|
21
21
|
this.#subscribers.forEach(cb => {
|
|
22
22
|
try {
|
|
@@ -48,13 +48,3 @@ export class VitalMetric {
|
|
|
48
48
|
return () => { this.#subscribers.delete(callback) }
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
function addConnectionAttributes (obj) {
|
|
53
|
-
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection // to date, both window & worker shares the same support for connection
|
|
54
|
-
if (!connection) return
|
|
55
|
-
|
|
56
|
-
if (connection.type) obj['net-type'] = connection.type
|
|
57
|
-
if (connection.effectiveType) obj['net-etype'] = connection.effectiveType
|
|
58
|
-
if (connection.rtt) obj['net-rtt'] = connection.rtt
|
|
59
|
-
if (connection.downlink) obj['net-dlink'] = connection.downlink
|
|
60
|
-
}
|
|
@@ -0,0 +1,95 @@
|
|
|
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 ({ body, query } = {}) {
|
|
18
|
+
if (!body && !query) return
|
|
19
|
+
try {
|
|
20
|
+
const gqlBody = parseBatchGQL(parseGQLContents(body))
|
|
21
|
+
if (gqlBody) return gqlBody
|
|
22
|
+
const gqlQuery = parseSingleGQL(parseGQLQueryString(query))
|
|
23
|
+
if (gqlQuery) return gqlQuery
|
|
24
|
+
} catch (err) {
|
|
25
|
+
// parsing failed, return undefined
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string|Object} gql The GraphQL object body sent to a GQL server
|
|
31
|
+
* @returns {GQLMetadata}
|
|
32
|
+
*/
|
|
33
|
+
function parseSingleGQL (contents) {
|
|
34
|
+
if (typeof contents !== 'object' || !contents.query || typeof contents.query !== 'string') return
|
|
35
|
+
|
|
36
|
+
/** parses gql query string and returns [fullmatch, type match, name match] */
|
|
37
|
+
const matches = contents.query.trim().match(/^(query|mutation|subscription)\s?(\w*)/)
|
|
38
|
+
const operationType = matches?.[1]
|
|
39
|
+
if (!operationType) return
|
|
40
|
+
const operationName = contents.operationName || matches?.[2] || 'Anonymous'
|
|
41
|
+
return {
|
|
42
|
+
operationName, // the operation name of the indiv query
|
|
43
|
+
operationType, // query, mutation, or subscription,
|
|
44
|
+
operationFramework: 'GraphQL'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseBatchGQL (contents) {
|
|
49
|
+
if (!contents) return
|
|
50
|
+
if (!Array.isArray(contents)) contents = [contents]
|
|
51
|
+
|
|
52
|
+
const opNames = []
|
|
53
|
+
const opTypes = []
|
|
54
|
+
for (let content of contents) {
|
|
55
|
+
const operation = parseSingleGQL(content)
|
|
56
|
+
if (!operation) continue
|
|
57
|
+
|
|
58
|
+
opNames.push(operation.operationName)
|
|
59
|
+
opTypes.push(operation.operationType)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!opTypes.length) return
|
|
63
|
+
return {
|
|
64
|
+
operationName: opNames.join(','), // the operation name of the indiv query -- joined by ',' for batched results
|
|
65
|
+
operationType: opTypes.join(','), // query, mutation, or subscription -- joined by ',' for batched results
|
|
66
|
+
operationFramework: 'GraphQL'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseGQLContents (gqlContents) {
|
|
71
|
+
let contents
|
|
72
|
+
|
|
73
|
+
if (!gqlContents || (typeof gqlContents !== 'string' && typeof gqlContents !== 'object')) return
|
|
74
|
+
else if (typeof gqlContents === 'string') contents = JSON.parse(gqlContents)
|
|
75
|
+
else contents = gqlContents
|
|
76
|
+
|
|
77
|
+
if (!isPureObject(contents) && !Array.isArray(contents)) return
|
|
78
|
+
|
|
79
|
+
let isValid = false
|
|
80
|
+
if (Array.isArray(contents)) isValid = contents.some(x => validateGQLObject(x))
|
|
81
|
+
else isValid = validateGQLObject(contents)
|
|
82
|
+
|
|
83
|
+
if (!isValid) return
|
|
84
|
+
return contents
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseGQLQueryString (gqlQueryString) {
|
|
88
|
+
if (!gqlQueryString || typeof gqlQueryString !== 'string') return
|
|
89
|
+
const params = new URLSearchParams(gqlQueryString)
|
|
90
|
+
return parseGQLContents(Object.fromEntries(params))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateGQLObject (obj) {
|
|
94
|
+
return !(typeof obj !== 'object' || !obj.query || typeof obj.query !== 'string')
|
|
95
|
+
}
|
|
@@ -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
|
|
|
17
18
|
export class Aggregate extends AggregateBase {
|
|
18
19
|
static featureName = FEATURE_NAME
|
|
@@ -119,6 +120,14 @@ export class Aggregate extends AggregateBase {
|
|
|
119
120
|
event.spanTimestamp = xhrContext.dt.timestamp
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
// parsed from the AJAX body, looking for operationName param & parsing query for operationType
|
|
124
|
+
event.gql = params.gql = parseGQL({
|
|
125
|
+
body: this.body,
|
|
126
|
+
query: this?.parsedOrigin?.search
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, ee)
|
|
130
|
+
|
|
122
131
|
// if the ajax happened inside an interaction, hold it until the interaction finishes
|
|
123
132
|
if (this.spaNode) {
|
|
124
133
|
var interactionId = this.spaNode.interaction.id
|
|
@@ -221,7 +230,8 @@ export class Aggregate extends AggregateBase {
|
|
|
221
230
|
var insert = '2,'
|
|
222
231
|
|
|
223
232
|
// add custom attributes
|
|
224
|
-
|
|
233
|
+
// gql decorators are added as custom attributes to alleviate need for new BEL schema
|
|
234
|
+
var attrParts = addCustomAttributes({ ...(getInfo(agentIdentifier).jsAttributes || {}), ...(event.gql || {}) }, this.addString)
|
|
225
235
|
fields.unshift(numeric(attrParts.length))
|
|
226
236
|
|
|
227
237
|
insert += fields.join(',')
|
|
@@ -157,6 +157,8 @@ function subscribeToEvents (agentIdentifier, ee, handler, dt) {
|
|
|
157
157
|
|
|
158
158
|
this.startTime = now()
|
|
159
159
|
|
|
160
|
+
this.body = data
|
|
161
|
+
|
|
160
162
|
this.listener = function (evt) {
|
|
161
163
|
try {
|
|
162
164
|
if (evt.type === 'abort' && !(context.loadCaptureCalled)) {
|
|
@@ -329,6 +331,7 @@ function subscribeToEvents (agentIdentifier, ee, handler, dt) {
|
|
|
329
331
|
var method = ('' + ((target && target instanceof origRequest && target.method) ||
|
|
330
332
|
opts.method || 'GET')).toUpperCase()
|
|
331
333
|
this.params.method = method
|
|
334
|
+
this.body = opts.body
|
|
332
335
|
|
|
333
336
|
this.txSize = dataSize(opts.body) || 0
|
|
334
337
|
}
|
|
@@ -191,7 +191,7 @@ export class Aggregate extends AggregateBase {
|
|
|
191
191
|
this.pageviewReported[bucketHash] = true
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
if (agentRuntime?.session?.state?.
|
|
194
|
+
if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true
|
|
195
195
|
params.firstOccurrenceTimestamp = this.observedAt[bucketHash]
|
|
196
196
|
|
|
197
197
|
var type = internal ? 'ierr' : 'err'
|
|
@@ -69,6 +69,8 @@ export class Aggregate extends AggregateBase {
|
|
|
69
69
|
at: info.atts
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
if (agentRuntime.session) queryParameters.fsh = Number(agentRuntime.session.isNew) // "first session harvest" aka RUM request or PageView event of a session
|
|
73
|
+
|
|
72
74
|
let body
|
|
73
75
|
if (typeof info.jsAttributes === 'object' && Object.keys(info.jsAttributes).length > 0) {
|
|
74
76
|
body = { ja: info.jsAttributes }
|
|
@@ -94,6 +94,7 @@ export class Aggregate extends AggregateBase {
|
|
|
94
94
|
|
|
95
95
|
addTiming (name, value, attrs) {
|
|
96
96
|
attrs = attrs || {}
|
|
97
|
+
addConnectionAttributes(attrs) // network conditions may differ from the actual for VitalMetrics when they were captured
|
|
97
98
|
|
|
98
99
|
// 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.
|
|
99
100
|
/*
|
|
@@ -175,3 +176,13 @@ export class Aggregate extends AggregateBase {
|
|
|
175
176
|
return payload
|
|
176
177
|
}
|
|
177
178
|
}
|
|
179
|
+
|
|
180
|
+
function addConnectionAttributes (obj) {
|
|
181
|
+
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection // to date, both window & worker shares the same support for connection
|
|
182
|
+
if (!connection) return
|
|
183
|
+
|
|
184
|
+
if (connection.type) obj['net-type'] = connection.type
|
|
185
|
+
if (connection.effectiveType) obj['net-etype'] = connection.effectiveType
|
|
186
|
+
if (connection.rtt) obj['net-rtt'] = connection.rtt
|
|
187
|
+
if (connection.downlink) obj['net-dlink'] = connection.downlink
|
|
188
|
+
}
|
|
@@ -133,14 +133,14 @@ export class Aggregate extends AggregateBase {
|
|
|
133
133
|
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
134
134
|
// if the mode changed on a different tab, it needs to update this instance to match
|
|
135
135
|
const { session } = getRuntime(this.agentIdentifier)
|
|
136
|
-
this.mode = session.state.
|
|
136
|
+
this.mode = session.state.sessionReplayMode
|
|
137
137
|
if (!this.initialized || this.mode === MODE.OFF) return
|
|
138
138
|
this.startRecording()
|
|
139
139
|
})
|
|
140
140
|
|
|
141
141
|
this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
|
|
142
142
|
if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return
|
|
143
|
-
if (this.mode !== MODE.OFF && data.
|
|
143
|
+
if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB)
|
|
144
144
|
this.mode = data.sessionReplay
|
|
145
145
|
})
|
|
146
146
|
|
|
@@ -167,7 +167,7 @@ export class Aggregate extends AggregateBase {
|
|
|
167
167
|
|
|
168
168
|
this.scheduler.startTimer(this.harvestTimeSeconds)
|
|
169
169
|
|
|
170
|
-
this.syncWithSessionManager({
|
|
170
|
+
this.syncWithSessionManager({ sessionReplayMode: this.mode })
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}, this.featureName, this.ee)
|
|
@@ -201,7 +201,7 @@ export class Aggregate extends AggregateBase {
|
|
|
201
201
|
// session replay samples can only be decided on the first load of a session
|
|
202
202
|
// session replays can continue if already in progress
|
|
203
203
|
if (!session.isNew) { // inherit the mode of the existing session
|
|
204
|
-
this.mode = session.state.
|
|
204
|
+
this.mode = session.state.sessionReplayMode
|
|
205
205
|
} else {
|
|
206
206
|
// The session is new... determine the mode the new session should start in
|
|
207
207
|
if (fullSample) this.mode = MODE.FULL // full mode has precedence over error mode
|
|
@@ -241,7 +241,7 @@ export class Aggregate extends AggregateBase {
|
|
|
241
241
|
}
|
|
242
242
|
this.startRecording()
|
|
243
243
|
|
|
244
|
-
this.syncWithSessionManager({
|
|
244
|
+
this.syncWithSessionManager({ sessionReplayMode: this.mode })
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
prepareHarvest () {
|
|
@@ -439,7 +439,7 @@ export class Aggregate extends AggregateBase {
|
|
|
439
439
|
this.blocked = true
|
|
440
440
|
this.mode = MODE.OFF
|
|
441
441
|
this.stopRecording()
|
|
442
|
-
this.syncWithSessionManager({
|
|
442
|
+
this.syncWithSessionManager({ sessionReplayMode: this.mode })
|
|
443
443
|
this.clearTimestamps()
|
|
444
444
|
this.ee.emit('REPLAY_ABORTED')
|
|
445
445
|
}
|
|
@@ -125,7 +125,7 @@ export class Aggregate extends AggregateBase {
|
|
|
125
125
|
this.ee.on(SESSION_EVENTS.PAUSE, () => { mostRecentModeKnown = sessionEntity.state.sessionTraceMode })
|
|
126
126
|
|
|
127
127
|
if (!sessionEntity.isNew) { // inherit the same mode as existing session's Trace
|
|
128
|
-
if (sessionEntity.state.
|
|
128
|
+
if (sessionEntity.state.sessionReplayMode === MODE.OFF) this.isStandalone = true
|
|
129
129
|
controlTraceOp(mostRecentModeKnown = sessionEntity.state.sessionTraceMode)
|
|
130
130
|
} else { // for new sessions, see the truth table associated with NEWRELIC-8662 wrt the new Trace behavior under session management
|
|
131
131
|
const replayMode = await getSessionReplayMode(agentIdentifier)
|
|
@@ -462,6 +462,13 @@ export class Aggregate extends AggregateBase {
|
|
|
462
462
|
this.trace = {}
|
|
463
463
|
this.nodeCount = 0
|
|
464
464
|
|
|
465
|
+
let firstHarvestOfSession
|
|
466
|
+
if (this.agentRuntime.session) {
|
|
467
|
+
const isFirstPayload = !this.agentRuntime.session.state.traceHarvestStarted
|
|
468
|
+
firstHarvestOfSession = { fsh: Number(isFirstPayload) } // converted to '0' | '1'
|
|
469
|
+
if (isFirstPayload) this.agentRuntime.session.write({ traceHarvestStarted: true })
|
|
470
|
+
}
|
|
471
|
+
|
|
465
472
|
return {
|
|
466
473
|
qs: {
|
|
467
474
|
st: this.agentRuntime.offset,
|
|
@@ -472,7 +479,8 @@ export class Aggregate extends AggregateBase {
|
|
|
472
479
|
* so that blob parsing doesn't need to happen to support UI/API functions */
|
|
473
480
|
fts: this.agentRuntime.offset + earliestTimeStamp,
|
|
474
481
|
/** 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 */
|
|
475
|
-
n: stns.length // node count
|
|
482
|
+
n: stns.length, // node count
|
|
483
|
+
...firstHarvestOfSession
|
|
476
484
|
},
|
|
477
485
|
body: { res: stns }
|
|
478
486
|
}
|
|
@@ -46,7 +46,9 @@ export class Aggregate extends AggregateBase {
|
|
|
46
46
|
depth: 0,
|
|
47
47
|
harvestTimeSeconds: getConfigurationValue(agentIdentifier, 'spa.harvestTimeSeconds') || 10,
|
|
48
48
|
interactionsToHarvest: [],
|
|
49
|
-
interactionsSent: []
|
|
49
|
+
interactionsSent: [],
|
|
50
|
+
// The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
|
|
51
|
+
disableSpaFix: (getConfigurationValue(agentIdentifier, 'feature_flags') || []).indexOf('disable-spa-fix') > -1
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
this.serializer = new Serializer(this)
|
|
@@ -279,7 +281,7 @@ export class Aggregate extends AggregateBase {
|
|
|
279
281
|
// context is stored on the xhr and is shared with all callbacks associated
|
|
280
282
|
// with the new xhr
|
|
281
283
|
register('new-xhr', function () {
|
|
282
|
-
if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
284
|
+
if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
283
285
|
/*
|
|
284
286
|
* The previous interaction was discarded before a route change. Restore the interaction
|
|
285
287
|
* in case this XHR is associated with a route change.
|
|
@@ -382,7 +384,7 @@ export class Aggregate extends AggregateBase {
|
|
|
382
384
|
|
|
383
385
|
register(FETCH_START, function (fetchArguments, dtPayload) {
|
|
384
386
|
if (fetchArguments) {
|
|
385
|
-
if (!state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
387
|
+
if (!state.disableSpaFix && !state.currentNode && state.prevInteraction && !state.prevInteraction.ignored) {
|
|
386
388
|
/*
|
|
387
389
|
* The previous interaction was discarded before a route change. Restore the interaction
|
|
388
390
|
* in case this XHR is associated with a route change.
|
|
@@ -102,7 +102,6 @@ export class Serializer extends SharedContext {
|
|
|
102
102
|
nullable(attrs.firstPaint, numeric, true) +
|
|
103
103
|
nullable(attrs.firstContentfulPaint, numeric, false)
|
|
104
104
|
)
|
|
105
|
-
|
|
106
105
|
var attrParts = addCustomAttributes(attrs.custom, addString)
|
|
107
106
|
children = children.concat(attrParts)
|
|
108
107
|
attrCount = attrParts.length
|
|
@@ -128,6 +127,13 @@ export class Serializer extends SharedContext {
|
|
|
128
127
|
nullable(node.dt && node.dt.traceId, addString, true) +
|
|
129
128
|
nullable(node.dt && node.dt.timestamp, numeric, false)
|
|
130
129
|
)
|
|
130
|
+
|
|
131
|
+
// add params.gql here
|
|
132
|
+
if (Object.keys(params?.gql || {}).length) {
|
|
133
|
+
var ajaxAttrParts = addCustomAttributes(params.gql, addString)
|
|
134
|
+
children = children.concat(ajaxAttrParts)
|
|
135
|
+
attrCount = ajaxAttrParts.length
|
|
136
|
+
}
|
|
131
137
|
break
|
|
132
138
|
|
|
133
139
|
case 4:
|
|
@@ -129,7 +129,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
129
129
|
if (featureName === FEATURE_NAMES.sessionReplay) {
|
|
130
130
|
if (!originals.MO) return false // Session Replay cannot work without Mutation Observer
|
|
131
131
|
if (getConfigurationValue(this.agentIdentifier, 'session_trace.enabled') === false) return false // Session Replay as of now is tightly coupled with Session Trace in the UI
|
|
132
|
-
return !!session?.isNew || !!session?.state.
|
|
132
|
+
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
|
|
133
133
|
}
|
|
134
134
|
return true
|
|
135
135
|
}
|