@newrelic/browser-agent 1.243.1 → 1.245.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 +24 -0
- package/dist/cjs/cdn/polyfills.js +4 -1
- package/dist/cjs/common/config/state/init.js +6 -0
- package/dist/cjs/common/constants/env.cdn.js +9 -3
- package/dist/cjs/common/constants/env.js +8 -2
- package/dist/cjs/common/constants/env.npm.js +9 -3
- package/dist/cjs/common/url/parse-url.js +9 -8
- package/dist/cjs/common/util/console.js +2 -2
- package/dist/cjs/common/util/type-check.js +14 -0
- package/dist/cjs/features/ajax/aggregate/gql.js +94 -0
- package/dist/cjs/features/ajax/aggregate/index.js +12 -1
- package/dist/cjs/features/ajax/instrument/index.js +2 -0
- package/dist/cjs/features/session_replay/aggregate/index.js +33 -23
- package/dist/cjs/features/spa/aggregate/index.js +3 -2
- package/dist/cjs/features/spa/aggregate/serializer.js +7 -0
- package/dist/cjs/index.js +0 -7
- package/dist/cjs/loaders/agent-base.js +3 -3
- package/dist/cjs/loaders/agent.js +1 -1
- package/dist/cjs/loaders/api/api.js +2 -2
- package/dist/esm/cdn/polyfills.js +4 -1
- package/dist/esm/common/config/state/init.js +6 -0
- package/dist/esm/common/constants/env.cdn.js +7 -2
- package/dist/esm/common/constants/env.js +6 -1
- package/dist/esm/common/constants/env.npm.js +7 -2
- package/dist/esm/common/url/parse-url.js +9 -8
- package/dist/esm/common/util/console.js +2 -2
- package/dist/esm/common/util/type-check.js +8 -0
- package/dist/esm/features/ajax/aggregate/gql.js +89 -0
- package/dist/esm/features/ajax/aggregate/index.js +12 -1
- package/dist/esm/features/ajax/instrument/index.js +2 -0
- package/dist/esm/features/session_replay/aggregate/index.js +31 -21
- package/dist/esm/features/spa/aggregate/index.js +3 -2
- package/dist/esm/features/spa/aggregate/serializer.js +7 -0
- package/dist/esm/index.js +0 -1
- package/dist/esm/loaders/agent-base.js +3 -3
- package/dist/esm/loaders/agent.js +1 -1
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/constants/env.cdn.d.ts +4 -0
- package/dist/types/common/constants/env.cdn.d.ts.map +1 -1
- package/dist/types/common/constants/env.d.ts +4 -0
- package/dist/types/common/constants/env.d.ts.map +1 -1
- package/dist/types/common/constants/env.npm.d.ts +4 -0
- package/dist/types/common/constants/env.npm.d.ts.map +1 -1
- package/dist/types/common/url/parse-url.d.ts.map +1 -1
- package/dist/types/common/util/console.d.ts +3 -3
- package/dist/types/common/util/console.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/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/session_replay/aggregate/index.d.ts +2 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- 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/agent-base.d.ts +2 -2
- package/dist/types/loaders/agent-base.d.ts.map +1 -1
- package/package.json +2 -10
- package/src/cdn/polyfills.js +3 -0
- package/src/common/config/state/init.js +3 -0
- package/src/common/constants/__mocks__/env.js +1 -0
- package/src/common/constants/env.cdn.js +5 -0
- package/src/common/constants/env.js +5 -0
- package/src/common/constants/env.npm.js +5 -0
- package/src/common/url/parse-url.js +9 -7
- package/src/common/util/console.js +2 -2
- package/src/common/util/type-check.js +8 -0
- package/src/features/ajax/aggregate/gql.js +95 -0
- package/src/features/ajax/aggregate/index.js +9 -1
- package/src/features/ajax/instrument/index.js +3 -0
- package/src/features/session_replay/aggregate/index.js +30 -22
- package/src/features/spa/aggregate/index.js +3 -2
- package/src/features/spa/aggregate/serializer.js +7 -1
- package/src/index.js +0 -1
- package/src/loaders/agent-base.js +3 -3
- package/src/loaders/agent.js +1 -1
- package/src/loaders/api/api.js +2 -2
- 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
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";
|
|
@@ -67,8 +67,8 @@ export class AgentBase {
|
|
|
67
67
|
/**
|
|
68
68
|
* Starts a set of agent features if not running in "autoStart" mode
|
|
69
69
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/start/}
|
|
70
|
-
* @param {string|string[]
|
|
70
|
+
* @param {string|string[]} [featureNames] The name(s) of the features to start. If no name(s) are passed, all features will be started
|
|
71
71
|
*/
|
|
72
|
-
start(featureNames
|
|
72
|
+
start(featureNames?: string | string[] | undefined): void;
|
|
73
73
|
}
|
|
74
74
|
//# sourceMappingURL=agent-base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/agent-base.js"],"names":[],"mappings":"AAIA;IACE;;;;;OAKG;IACH,oBAHW,MAAM,yCAKhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,mCAKhB;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,IAAI,uCAK5B;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,+CAKtB;IAED;;;;OAIG;IACH,iBAFW,MAAM,GAAC,IAAI,QAIrB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,QAMrB;IAED;;;;OAIG;IACH,kCAFmB,KAAK,GAAC,MAAM,KAAK,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,QAI9D;IAED;;;;OAIG;IACH,+CAEC;IAED;;;;;OAKG;IACH,iBAHW,MAAM,MACN,MAAM,QAIhB;IAED;;;;OAIG;IACH
|
|
1
|
+
{"version":3,"file":"agent-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/agent-base.js"],"names":[],"mappings":"AAIA;IACE;;;;;OAKG;IACH,oBAHW,MAAM,yCAKhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,mCAKhB;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,IAAI,uCAK5B;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,+CAKtB;IAED;;;;OAIG;IACH,iBAFW,MAAM,GAAC,IAAI,QAIrB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,QAMrB;IAED;;;;OAIG;IACH,kCAFmB,KAAK,GAAC,MAAM,KAAK,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,QAI9D;IAED;;;;OAIG;IACH,+CAEC;IAED;;;;;OAKG;IACH,iBAHW,MAAM,MACN,MAAM,QAIhB;IAED;;;;OAIG;IACH,0DAEC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.245.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",
|
|
@@ -176,7 +168,7 @@
|
|
|
176
168
|
"dependencies": {
|
|
177
169
|
"core-js": "^3.26.0",
|
|
178
170
|
"fflate": "^0.7.4",
|
|
179
|
-
"rrweb": "2.0.0-alpha.
|
|
171
|
+
"rrweb": "2.0.0-alpha.11",
|
|
180
172
|
"web-vitals": "^3.1.0"
|
|
181
173
|
},
|
|
182
174
|
"devDependencies": {
|
package/src/cdn/polyfills.js
CHANGED
|
@@ -13,8 +13,11 @@ 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'
|
|
@@ -63,6 +63,9 @@ const model = () => {
|
|
|
63
63
|
harvestTimeSeconds: 60,
|
|
64
64
|
sampling_rate: 50, // float from 0 - 100
|
|
65
65
|
error_sampling_rate: 50, // float from 0 - 100
|
|
66
|
+
collect_fonts: false, // serialize fonts for collection without public asset url, this is currently broken in RRWeb -- https://github.com/rrweb-io/rrweb/issues/1304. When fixed, revisit with test cases
|
|
67
|
+
inline_images: false, // serialize images for collection without public asset url
|
|
68
|
+
inline_stylesheet: true, // serialize css for collection without public asset url
|
|
66
69
|
// recording config settings
|
|
67
70
|
mask_all_inputs: true,
|
|
68
71
|
// this has a getter/setter to facilitate validation of the selectors
|
|
@@ -23,20 +23,22 @@ export function parseUrl (url) {
|
|
|
23
23
|
var location = globalScope?.location
|
|
24
24
|
var ret = {}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
try {
|
|
27
|
+
urlEl = new URL(url, location.href)
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (isBrowserScope) {
|
|
27
30
|
// Use an anchor dom element to resolve the url natively.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
urlEl = new URL(url, location.href)
|
|
33
|
-
} catch (err) {
|
|
31
|
+
urlEl = document.createElement('a')
|
|
32
|
+
urlEl.href = url
|
|
33
|
+
} else {
|
|
34
34
|
return ret
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
ret.port = urlEl.port
|
|
39
39
|
|
|
40
|
+
ret.search = urlEl.search
|
|
41
|
+
|
|
40
42
|
var firstSplit = urlEl.href.split('://')
|
|
41
43
|
|
|
42
44
|
if (!ret.port && firstSplit[1]) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A helper method to warn to the console with New Relic: decoration
|
|
3
|
-
* @param {string} message
|
|
4
|
-
* @param {*} secondary
|
|
3
|
+
* @param {string} message The primary message to warn
|
|
4
|
+
* @param {*} [secondary] Secondary data to include, usually an error or object
|
|
5
5
|
* @returns
|
|
6
6
|
*/
|
|
7
7
|
export function warn (message, secondary) {
|
|
@@ -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
|
+
}
|
|
@@ -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,12 @@ 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
|
+
|
|
122
129
|
// if the ajax happened inside an interaction, hold it until the interaction finishes
|
|
123
130
|
if (this.spaNode) {
|
|
124
131
|
var interactionId = this.spaNode.interaction.id
|
|
@@ -221,7 +228,8 @@ export class Aggregate extends AggregateBase {
|
|
|
221
228
|
var insert = '2,'
|
|
222
229
|
|
|
223
230
|
// add custom attributes
|
|
224
|
-
|
|
231
|
+
// gql decorators are added as custom attributes to alleviate need for new BEL schema
|
|
232
|
+
var attrParts = addCustomAttributes({ ...(getInfo(agentIdentifier).jsAttributes || {}), ...(event.gql || {}) }, this.addString)
|
|
225
233
|
fields.unshift(numeric(attrParts.length))
|
|
226
234
|
|
|
227
235
|
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
|
}
|
|
@@ -24,9 +24,7 @@ import { globalScope } from '../../../common/constants/runtime'
|
|
|
24
24
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
25
25
|
import { handle } from '../../../common/event-emitter/handle'
|
|
26
26
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
27
|
-
|
|
28
|
-
// would be better to get this dynamically in some way
|
|
29
|
-
export const RRWEB_VERSION = '2.0.0-alpha.8'
|
|
27
|
+
import { RRWEB_VERSION } from '../../../common/constants/env'
|
|
30
28
|
|
|
31
29
|
export const AVG_COMPRESSION = 0.12
|
|
32
30
|
|
|
@@ -68,8 +66,8 @@ let recorder, gzipper, u8
|
|
|
68
66
|
export const MAX_PAYLOAD_SIZE = 1000000
|
|
69
67
|
/** Unloading caps around 64kb */
|
|
70
68
|
export const IDEAL_PAYLOAD_SIZE = 64000
|
|
71
|
-
/** Interval between forcing new full snapshots --
|
|
72
|
-
const CHECKOUT_MS = { [MODE.ERROR]:
|
|
69
|
+
/** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
|
|
70
|
+
const CHECKOUT_MS = { [MODE.ERROR]: 15000, [MODE.FULL]: 300000, [MODE.OFF]: 0 }
|
|
73
71
|
|
|
74
72
|
export class Aggregate extends AggregateBase {
|
|
75
73
|
static featureName = FEATURE_NAME
|
|
@@ -77,6 +75,8 @@ export class Aggregate extends AggregateBase {
|
|
|
77
75
|
super(agentIdentifier, aggregator, FEATURE_NAME)
|
|
78
76
|
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
79
77
|
this.events = []
|
|
78
|
+
/** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
|
|
79
|
+
this.backloggedEvents = []
|
|
80
80
|
/** The interval to harvest at. This gets overridden if the size of the payload exceeds certain thresholds */
|
|
81
81
|
this.harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'session_replay.harvestTimeSeconds') || 60
|
|
82
82
|
/** Set once the recorder has fully initialized after flag checks and sampling */
|
|
@@ -247,7 +247,10 @@ export class Aggregate extends AggregateBase {
|
|
|
247
247
|
prepareHarvest () {
|
|
248
248
|
if (this.events.length === 0 || (this.mode !== MODE.FULL && !this.blocked)) return
|
|
249
249
|
const payload = this.getHarvestContents()
|
|
250
|
-
|
|
250
|
+
if (!payload.body.length) {
|
|
251
|
+
this.clearBuffer()
|
|
252
|
+
return
|
|
253
|
+
}
|
|
251
254
|
if (this.shouldCompress) {
|
|
252
255
|
payload.body = gzipper(u8(stringify(payload.body)))
|
|
253
256
|
this.scheduler.opts.gzip = true
|
|
@@ -265,6 +268,17 @@ export class Aggregate extends AggregateBase {
|
|
|
265
268
|
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
266
269
|
const info = getInfo(this.agentIdentifier)
|
|
267
270
|
|
|
271
|
+
if (this.backloggedEvents.length) this.events = [...this.backloggedEvents, ...this.events]
|
|
272
|
+
|
|
273
|
+
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
274
|
+
// we will manually inject it if this happens
|
|
275
|
+
const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot
|
|
276
|
+
if (payloadStartsWithFullSnapshot && !!this.lastMeta) {
|
|
277
|
+
this.hasMeta = true
|
|
278
|
+
this.events.unshift(this.lastMeta) // --> pushed the meta from a previous payload into newer payload... but it still has old timestamps
|
|
279
|
+
this.lastMeta = undefined
|
|
280
|
+
}
|
|
281
|
+
|
|
268
282
|
// do not let the last node be a meta node, since this NEEDS to precede a snapshot
|
|
269
283
|
// we will manually inject it later if we find a payload that is missing a meta node
|
|
270
284
|
const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta
|
|
@@ -274,16 +288,8 @@ export class Aggregate extends AggregateBase {
|
|
|
274
288
|
this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta)
|
|
275
289
|
}
|
|
276
290
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot
|
|
280
|
-
if (payloadStartsWithFullSnapshot) {
|
|
281
|
-
this.hasMeta = true
|
|
282
|
-
this.events.unshift(this.lastMeta)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const firstEventTimestamp = this.events[0]?.timestamp
|
|
286
|
-
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp
|
|
291
|
+
const firstEventTimestamp = this.events[0]?.timestamp // from rrweb node
|
|
292
|
+
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp // from rrweb node
|
|
287
293
|
const firstTimestamp = firstEventTimestamp || this.cycleTimestamp
|
|
288
294
|
const lastTimestamp = lastEventTimestamp || getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
|
|
289
295
|
return {
|
|
@@ -323,6 +329,8 @@ export class Aggregate extends AggregateBase {
|
|
|
323
329
|
|
|
324
330
|
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
325
331
|
clearBuffer () {
|
|
332
|
+
if (this.mode === MODE.ERROR) this.backloggedEvents = this.events
|
|
333
|
+
else this.backloggedEvents = []
|
|
326
334
|
this.events = []
|
|
327
335
|
this.hasSnapshot = false
|
|
328
336
|
this.hasMeta = false
|
|
@@ -337,11 +345,8 @@ export class Aggregate extends AggregateBase {
|
|
|
337
345
|
warn('Recording library was never imported')
|
|
338
346
|
return this.abort(ABORT_REASONS.IMPORT)
|
|
339
347
|
}
|
|
340
|
-
this.clearTimestamps()
|
|
341
|
-
// set the fallbacks as early as possible
|
|
342
|
-
this.setTimestamps()
|
|
343
348
|
this.recording = true
|
|
344
|
-
const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs } = getConfigurationValue(this.agentIdentifier, 'session_replay')
|
|
349
|
+
const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs, inline_images, inline_stylesheet, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')
|
|
345
350
|
// set up rrweb configurations for maximum privacy --
|
|
346
351
|
// https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
|
|
347
352
|
const stop = recorder({
|
|
@@ -353,6 +358,9 @@ export class Aggregate extends AggregateBase {
|
|
|
353
358
|
maskInputOptions: mask_input_options,
|
|
354
359
|
maskTextSelector: mask_text_selector,
|
|
355
360
|
maskAllInputs: mask_all_inputs,
|
|
361
|
+
inlineImages: inline_images,
|
|
362
|
+
inlineStylesheet: inline_stylesheet,
|
|
363
|
+
collectFonts: collect_fonts,
|
|
356
364
|
checkoutEveryNms: CHECKOUT_MS[this.mode]
|
|
357
365
|
})
|
|
358
366
|
|
|
@@ -377,7 +385,8 @@ export class Aggregate extends AggregateBase {
|
|
|
377
385
|
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
378
386
|
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
379
387
|
// each time we see a new checkout, we can drop the old data.
|
|
380
|
-
|
|
388
|
+
// we need to check for meta because rrweb will flag it as checkout twice, once for meta, then once for snapshot
|
|
389
|
+
if (this.mode === MODE.ERROR && isCheckout && event.type === RRWEB_EVENT_TYPES.Meta) {
|
|
381
390
|
// we are still waiting for an error to throw, so keep wiping the buffer over time
|
|
382
391
|
this.clearBuffer()
|
|
383
392
|
}
|
|
@@ -385,7 +394,6 @@ export class Aggregate extends AggregateBase {
|
|
|
385
394
|
// meta event
|
|
386
395
|
if (event.type === RRWEB_EVENT_TYPES.Meta) {
|
|
387
396
|
this.hasMeta = true
|
|
388
|
-
this.lastMeta = event
|
|
389
397
|
}
|
|
390
398
|
// snapshot event
|
|
391
399
|
if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
|
|
@@ -20,6 +20,7 @@ import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
|
20
20
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
21
21
|
import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
|
|
22
22
|
import { firstPaint } from '../../../common/vitals/first-paint'
|
|
23
|
+
import { bundleId } from '../../../common/ids/bundle-id'
|
|
23
24
|
|
|
24
25
|
const {
|
|
25
26
|
FEATURE_NAME, INTERACTION_EVENTS, MAX_TIMER_BUDGET, FN_START, FN_END, CB_START, INTERACTION_API, REMAINING,
|
|
@@ -166,7 +167,7 @@ export class Aggregate extends AggregateBase {
|
|
|
166
167
|
register(FN_START, function (args, eventSource) {
|
|
167
168
|
var ev = args[0]
|
|
168
169
|
var evName = ev.type
|
|
169
|
-
var eventNode = ev
|
|
170
|
+
var eventNode = ev[`__nrNode:${bundleId}`]
|
|
170
171
|
|
|
171
172
|
if (!state.pageLoaded && evName === 'load' && eventSource === window) {
|
|
172
173
|
state.pageLoaded = true
|
|
@@ -216,7 +217,7 @@ export class Aggregate extends AggregateBase {
|
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
ev
|
|
220
|
+
ev[`__nrNode:${bundleId}`] = state.currentNode
|
|
220
221
|
}, this.featureName, eventsEE)
|
|
221
222
|
|
|
222
223
|
/**
|
|
@@ -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:
|
package/src/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export class AgentBase {
|
|
|
10
10
|
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}. The key is reported as its own PageAction attribute with the specified values.
|
|
11
11
|
*/
|
|
12
12
|
addPageAction (name, attributes) {
|
|
13
|
-
warn('Call to agent api addPageAction failed. The
|
|
13
|
+
warn('Call to agent api addPageAction failed. The page action feature is not currently initialized.')
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -90,13 +90,13 @@ export class AgentBase {
|
|
|
90
90
|
* @param {string} id The ID or version of this release; for example, a version number, build number from your CI environment, GitHub SHA, GUID, or a hash of the contents.
|
|
91
91
|
*/
|
|
92
92
|
addRelease (name, id) {
|
|
93
|
-
warn('Call to agent api addRelease failed. The
|
|
93
|
+
warn('Call to agent api addRelease failed. The js errors feature is not currently initialized.')
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* Starts a set of agent features if not running in "autoStart" mode
|
|
98
98
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/start/}
|
|
99
|
-
* @param {string|string[]
|
|
99
|
+
* @param {string|string[]} [featureNames] The name(s) of the features to start. If no name(s) are passed, all features will be started
|
|
100
100
|
*/
|
|
101
101
|
start (featureNames) {
|
|
102
102
|
warn('Call to agent api addRelease failed. The agent is not currently initialized.')
|
package/src/loaders/agent.js
CHANGED
|
@@ -107,7 +107,7 @@ export class Agent extends AgentBase {
|
|
|
107
107
|
* If you are sending the same event object to New Relic as a PageAction, omit the TYPE attribute. (type is a string to describe what type of event you are marking inside of a session trace.) If included, it will override the event type and cause the PageAction event to be sent incorrectly. Instead, use the name attribute for event information.
|
|
108
108
|
*/
|
|
109
109
|
addToTrace (customAttributes) {
|
|
110
|
-
warn('Call to agent api addToTrace failed. The
|
|
110
|
+
warn('Call to agent api addToTrace failed. The session trace feature is not currently initialized.')
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
package/src/loaders/api/api.js
CHANGED
|
@@ -89,8 +89,8 @@ export function setAPI (agentIdentifier, forceDrain) {
|
|
|
89
89
|
warn(`Failed to execute setCustomAttribute.\nName must be a string type, but a type of <${typeof name}> was provided.`)
|
|
90
90
|
return
|
|
91
91
|
}
|
|
92
|
-
if (!(['string', 'number'].includes(typeof value) || value === null)) {
|
|
93
|
-
warn(`Failed to execute setCustomAttribute.\nNon-null value must be a string or
|
|
92
|
+
if (!(['string', 'number', 'boolean'].includes(typeof value) || value === null)) {
|
|
93
|
+
warn(`Failed to execute setCustomAttribute.\nNon-null value must be a string, number or boolean type, but a type of <${typeof value}> was provided.`)
|
|
94
94
|
return
|
|
95
95
|
}
|
|
96
96
|
return appendJsAttribute(name, value, 'setCustomAttribute', persistAttribute)
|
package/dist/cjs/cdn/worker.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _agent = require("../loaders/agent");
|
|
4
|
-
var _instrument = require("../features/metrics/instrument");
|
|
5
|
-
var _instrument2 = require("../features/jserrors/instrument");
|
|
6
|
-
var _instrument3 = require("../features/ajax/instrument");
|
|
7
|
-
var _instrument4 = require("../features/page_action/instrument");
|
|
8
|
-
/**
|
|
9
|
-
* @file Creates a "Worker" agent loader bundle composed of the core Agent and the subset of feature modules applicable
|
|
10
|
-
* in a service worker context.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
new _agent.Agent({
|
|
14
|
-
features: [_instrument.Instrument, _instrument2.Instrument, _instrument3.Instrument, _instrument4.Instrument],
|
|
15
|
-
loaderType: 'worker'
|
|
16
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.WorkerAgent = void 0;
|
|
7
|
-
var _agent = require("./agent");
|
|
8
|
-
var _instrument = require("../features/metrics/instrument");
|
|
9
|
-
var _instrument2 = require("../features/jserrors/instrument");
|
|
10
|
-
var _instrument3 = require("../features/ajax/instrument");
|
|
11
|
-
var _instrument4 = require("../features/page_action/instrument");
|
|
12
|
-
/**
|
|
13
|
-
* A streamlined agent class designed for the service worker context, limited to features relevant in that scope.
|
|
14
|
-
*/
|
|
15
|
-
class WorkerAgent extends _agent.Agent {
|
|
16
|
-
constructor(args) {
|
|
17
|
-
super({
|
|
18
|
-
...args,
|
|
19
|
-
features: [_instrument.Instrument, _instrument2.Instrument, _instrument3.Instrument, _instrument4.Instrument],
|
|
20
|
-
loaderType: 'worker-agent'
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
exports.WorkerAgent = WorkerAgent;
|
package/dist/esm/cdn/worker.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Creates a "Worker" agent loader bundle composed of the core Agent and the subset of feature modules applicable
|
|
3
|
-
* in a service worker context.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Agent } from '../loaders/agent';
|
|
7
|
-
import { Instrument as InstrumentMetrics } from '../features/metrics/instrument';
|
|
8
|
-
import { Instrument as InstrumentErrors } from '../features/jserrors/instrument';
|
|
9
|
-
import { Instrument as InstrumentXhr } from '../features/ajax/instrument';
|
|
10
|
-
import { Instrument as InstrumentPageAction } from '../features/page_action/instrument';
|
|
11
|
-
new Agent({
|
|
12
|
-
features: [InstrumentMetrics, InstrumentErrors, InstrumentXhr, InstrumentPageAction],
|
|
13
|
-
loaderType: 'worker'
|
|
14
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Agent } from './agent';
|
|
2
|
-
import { Instrument as InstrumentMetrics } from '../features/metrics/instrument';
|
|
3
|
-
import { Instrument as InstrumentErrors } from '../features/jserrors/instrument';
|
|
4
|
-
import { Instrument as InstrumentXhr } from '../features/ajax/instrument';
|
|
5
|
-
import { Instrument as InstrumentPageAction } from '../features/page_action/instrument';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A streamlined agent class designed for the service worker context, limited to features relevant in that scope.
|
|
9
|
-
*/
|
|
10
|
-
export class WorkerAgent extends Agent {
|
|
11
|
-
constructor(args) {
|
|
12
|
-
super({
|
|
13
|
-
...args,
|
|
14
|
-
features: [InstrumentMetrics, InstrumentErrors, InstrumentXhr, InstrumentPageAction],
|
|
15
|
-
loaderType: 'worker-agent'
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/cdn/worker.js"],"names":[],"mappings":""}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A streamlined agent class designed for the service worker context, limited to features relevant in that scope.
|
|
3
|
-
*/
|
|
4
|
-
export class WorkerAgent extends Agent {
|
|
5
|
-
constructor(args: any);
|
|
6
|
-
}
|
|
7
|
-
import { Agent } from './agent';
|
|
8
|
-
//# sourceMappingURL=worker-agent.d.ts.map
|