@newrelic/browser-agent 1.236.0 → 1.237.1
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/dist/cjs/common/config/state/init.js +1 -0
- package/dist/cjs/common/config/state/runtime.js +2 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/deny-list/deny-list.js +14 -10
- package/dist/cjs/common/harvest/harvest.js +8 -22
- package/dist/cjs/common/util/submit-data.js +4 -36
- package/dist/cjs/common/wrap/wrap-jsonp.js +12 -6
- package/dist/cjs/features/ajax/aggregate/index.js +24 -27
- package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +1 -1
- package/dist/cjs/features/jserrors/constants.js +2 -4
- package/dist/cjs/features/jserrors/instrument/index.js +79 -88
- package/dist/cjs/features/jserrors/instrument/uncaught-error.js +22 -0
- package/dist/cjs/features/metrics/aggregate/index.js +8 -0
- package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +23 -19
- package/dist/cjs/features/session_replay/aggregate/index.js +65 -34
- package/dist/cjs/features/session_trace/aggregate/index.js +3 -4
- package/dist/cjs/features/spa/aggregate/index.js +1 -1
- package/dist/cjs/features/utils/instrument-base.js +6 -8
- package/dist/cjs/loaders/agent-base.js +87 -0
- package/dist/cjs/loaders/agent.js +41 -1
- package/dist/cjs/loaders/api/api.js +1 -1
- package/dist/cjs/loaders/api/interaction-types.js +87 -0
- package/dist/cjs/loaders/configure/configure.js +3 -1
- package/dist/cjs/loaders/micro-agent.js +3 -1
- package/dist/esm/common/config/state/init.js +1 -0
- package/dist/esm/common/config/state/runtime.js +2 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/deny-list/deny-list.js +14 -10
- package/dist/esm/common/harvest/harvest.js +7 -22
- package/dist/esm/common/util/submit-data.js +4 -35
- package/dist/esm/common/wrap/wrap-jsonp.js +12 -6
- package/dist/esm/features/ajax/aggregate/index.js +25 -28
- package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +1 -1
- package/dist/esm/features/jserrors/constants.js +1 -2
- package/dist/esm/features/jserrors/instrument/index.js +78 -87
- package/dist/esm/features/jserrors/instrument/uncaught-error.js +15 -0
- package/dist/esm/features/metrics/aggregate/index.js +8 -0
- package/dist/esm/features/page_view_event/aggregate/initialized-features.js +23 -19
- package/dist/esm/features/session_replay/aggregate/index.js +65 -34
- package/dist/esm/features/session_trace/aggregate/index.js +3 -4
- package/dist/esm/features/spa/aggregate/index.js +1 -1
- package/dist/esm/features/utils/instrument-base.js +7 -9
- package/dist/esm/loaders/agent-base.js +80 -0
- package/dist/esm/loaders/agent.js +41 -1
- package/dist/esm/loaders/api/api.js +1 -1
- package/dist/esm/loaders/api/interaction-types.js +80 -0
- package/dist/esm/loaders/configure/configure.js +5 -3
- package/dist/esm/loaders/micro-agent.js +3 -1
- package/dist/types/common/config/state/runtime.d.ts.map +1 -1
- package/dist/types/common/event-emitter/register-handler.d.ts +1 -1
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts +6 -6
- package/dist/types/common/util/submit-data.d.ts +2 -20
- package/dist/types/common/util/submit-data.d.ts.map +1 -1
- package/dist/types/common/window/nreum.d.ts +2 -2
- package/dist/types/common/wrap/wrap-jsonp.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts +5 -5
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/constants.d.ts +0 -1
- package/dist/types/features/jserrors/constants.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/index.d.ts +0 -13
- package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/uncaught-error.d.ts +15 -0
- package/dist/types/features/jserrors/instrument/uncaught-error.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/endpoint-map.d.ts +5 -5
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +16 -30
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/agent-base.d.ts +59 -0
- package/dist/types/loaders/agent-base.d.ts.map +1 -0
- package/dist/types/loaders/agent.d.ts +35 -1
- package/dist/types/loaders/agent.d.ts.map +1 -1
- package/dist/types/loaders/api/interaction-types.d.ts +122 -0
- package/dist/types/loaders/api/interaction-types.d.ts.map +1 -0
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/dist/types/loaders/features/features.d.ts +9 -9
- package/dist/types/loaders/micro-agent.d.ts +3 -2
- package/dist/types/loaders/micro-agent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/config/state/init.js +1 -1
- package/src/common/config/state/runtime.js +2 -1
- package/src/common/deny-list/deny-list.js +11 -11
- package/src/common/deny-list/deny-list.test.js +31 -0
- package/src/common/harvest/harvest.js +8 -18
- package/src/common/harvest/harvest.test.js +16 -36
- package/src/common/util/__mocks__/submit-data.js +0 -1
- package/src/common/util/submit-data.js +2 -24
- package/src/common/util/submit-data.test.js +0 -56
- package/src/common/wrap/wrap-jsonp.js +11 -6
- package/src/features/ajax/aggregate/index.js +25 -31
- package/src/features/jserrors/aggregate/compute-stack-trace.js +1 -1
- package/src/features/jserrors/constants.js +0 -1
- package/src/features/jserrors/instrument/index.js +91 -87
- package/src/features/jserrors/instrument/uncaught-error.js +15 -0
- package/src/features/metrics/aggregate/index.js +8 -0
- package/src/features/page_view_event/aggregate/initialized-features.js +18 -14
- package/src/features/session_replay/aggregate/index.component-test.js +17 -56
- package/src/features/session_replay/aggregate/index.js +47 -28
- package/src/features/session_trace/aggregate/index.js +3 -4
- package/src/features/spa/aggregate/index.js +1 -1
- package/src/features/utils/instrument-base.js +6 -9
- package/src/features/utils/instrument-base.test.js +7 -0
- package/src/loaders/agent-base.js +81 -0
- package/src/loaders/agent.js +42 -1
- package/src/loaders/api/api.js +1 -1
- package/src/loaders/api/interaction-types.js +80 -0
- package/src/loaders/configure/configure.js +14 -4
- package/src/loaders/micro-agent.js +4 -1
- package/dist/cjs/features/jserrors/instrument/debug.js +0 -40
- package/dist/esm/features/jserrors/instrument/debug.js +0 -38
- package/dist/types/features/jserrors/instrument/debug.d.ts +0 -2
- package/dist/types/features/jserrors/instrument/debug.d.ts.map +0 -1
- package/src/features/jserrors/instrument/debug.js +0 -36
|
@@ -23,7 +23,8 @@ const model = {
|
|
|
23
23
|
releaseIds: {},
|
|
24
24
|
session: undefined,
|
|
25
25
|
xhrWrappable: typeof globalScope.XMLHttpRequest?.prototype?.addEventListener === 'function',
|
|
26
|
-
version: VERSION
|
|
26
|
+
version: VERSION,
|
|
27
|
+
denyList: undefined
|
|
27
28
|
};
|
|
28
29
|
const _cache = {};
|
|
29
30
|
export function getRuntime(id) {
|
|
@@ -40,24 +40,28 @@ export function setDenyList(denyListConfig) {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
for (var i = 0; i < denyListConfig.length; i++) {
|
|
43
|
-
|
|
43
|
+
let url = denyListConfig[i];
|
|
44
|
+
if (!url) continue; // ignore bad values like undefined or empty strings
|
|
45
|
+
|
|
44
46
|
if (url.indexOf('http://') === 0) {
|
|
45
47
|
url = url.substring(7);
|
|
46
48
|
} else if (url.indexOf('https://') === 0) {
|
|
47
49
|
url = url.substring(8);
|
|
48
50
|
}
|
|
49
|
-
|
|
51
|
+
const firstSlash = url.indexOf('/');
|
|
52
|
+
let host, pathname;
|
|
50
53
|
if (firstSlash > 0) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
pathname: url.substring(firstSlash)
|
|
54
|
-
});
|
|
54
|
+
host = url.substring(0, firstSlash);
|
|
55
|
+
pathname = url.substring(firstSlash);
|
|
55
56
|
} else {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
pathname: ''
|
|
59
|
-
});
|
|
57
|
+
host = url;
|
|
58
|
+
pathname = '';
|
|
60
59
|
}
|
|
60
|
+
let [hostname, port] = host.split(':');
|
|
61
|
+
denyList.push({
|
|
62
|
+
hostname,
|
|
63
|
+
pathname
|
|
64
|
+
});
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
/**
|
|
@@ -16,6 +16,8 @@ import { applyFnToProps } from '../util/traverse';
|
|
|
16
16
|
import { SharedContext } from '../context/shared-context';
|
|
17
17
|
import { VERSION } from "../constants/env.npm";
|
|
18
18
|
import { isWorkerScope, isIE } from '../constants/runtime';
|
|
19
|
+
import { warn } from '../util/console';
|
|
20
|
+
const warnings = {};
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* @typedef {import('./types.js').NetworkSendSpec} NetworkSendSpec
|
|
@@ -24,7 +26,6 @@ import { isWorkerScope, isIE } from '../constants/runtime';
|
|
|
24
26
|
* @typedef {import('./types.js').FeatureHarvestCallback} FeatureHarvestCallback
|
|
25
27
|
* @typedef {import('./types.js').FeatureHarvestCallbackOptions} FeatureHarvestCallbackOptions
|
|
26
28
|
*/
|
|
27
|
-
|
|
28
29
|
export class Harvest extends SharedContext {
|
|
29
30
|
constructor(parent) {
|
|
30
31
|
super(parent); // gets any allowed properties from the parent and stores them in `sharedContext`
|
|
@@ -135,13 +136,15 @@ export class Harvest extends SharedContext {
|
|
|
135
136
|
payloadParams = payloadParams.substring(1);
|
|
136
137
|
}
|
|
137
138
|
const fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
|
|
138
|
-
const gzip = qs
|
|
139
|
+
const gzip = !!qs?.attributes?.includes('gzip');
|
|
139
140
|
if (!gzip) {
|
|
140
141
|
if (endpoint === 'events') {
|
|
141
142
|
body = body.e;
|
|
142
143
|
} else {
|
|
143
144
|
body = stringify(body);
|
|
144
145
|
}
|
|
146
|
+
/** Warn --once per endpoint-- if the agent tries to send large payloads */
|
|
147
|
+
if (body.length > 750000 && (warnings[endpoint] = (warnings?.[endpoint] || 0) + 1) === 1) warn("The Browser Agent is attempting to send a very large payload to /".concat(endpoint, ". This is usually tied to large amounts of custom attributes. Please check your configurations."));
|
|
145
148
|
}
|
|
146
149
|
if (!body || body.length === 0 || body === '{}' || body === '[]') {
|
|
147
150
|
// If body is null, undefined, or an empty object or array, send an empty string instead
|
|
@@ -188,23 +191,6 @@ export class Harvest extends SharedContext {
|
|
|
188
191
|
cbFinished(cbResult);
|
|
189
192
|
}, eventListenerOpts(false));
|
|
190
193
|
}
|
|
191
|
-
|
|
192
|
-
// if beacon request failed, retry with an alternative method -- will not happen for workers
|
|
193
|
-
if (!result && submitMethod === submitData.beacon) {
|
|
194
|
-
// browsers that support sendBeacon also support fetch with keepalive - IE will not retry unload calls
|
|
195
|
-
submitMethod = submitData.fetchKeepAlive;
|
|
196
|
-
try {
|
|
197
|
-
submitMethod({
|
|
198
|
-
url: fullUrl,
|
|
199
|
-
body,
|
|
200
|
-
headers
|
|
201
|
-
});
|
|
202
|
-
} catch (e) {
|
|
203
|
-
// Ignore error in final harvest
|
|
204
|
-
} finally {
|
|
205
|
-
result = true;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
194
|
return result;
|
|
209
195
|
}
|
|
210
196
|
|
|
@@ -264,9 +250,8 @@ export class Harvest extends SharedContext {
|
|
|
264
250
|
cleanPayload() {
|
|
265
251
|
let payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
266
252
|
const clean = input => {
|
|
267
|
-
if (typeof Uint8Array !== 'undefined' && input instanceof Uint8Array)
|
|
268
|
-
|
|
269
|
-
}
|
|
253
|
+
if (typeof Uint8Array !== 'undefined' && input instanceof Uint8Array || Array.isArray(input)) return input;
|
|
254
|
+
if (typeof input === 'string') return input.length > 0 ? input : null;
|
|
270
255
|
return Object.entries(input || {}).reduce((accumulator, _ref2) => {
|
|
271
256
|
let [key, value] = _ref2;
|
|
272
257
|
if (typeof value === 'number' || typeof value === 'string' && value.length > 0 || typeof value === 'object' && Object.keys(value || {}).length > 0) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { isBrowserScope, supportsSendBeacon } from '../constants/runtime';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @typedef {xhr|
|
|
10
|
+
* @typedef {xhr|beacon} NetworkMethods
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -64,36 +64,6 @@ export function xhr(_ref) {
|
|
|
64
64
|
return request;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
* Send via fetch with keepalive true
|
|
69
|
-
* @param {Object} args - The args.
|
|
70
|
-
* @param {string} args.url - The URL to send to.
|
|
71
|
-
* @param {string=} args.body - The Stringified body.
|
|
72
|
-
* @param {string=} [args.method=POST] - The XHR method to use.
|
|
73
|
-
* @param {{key: string, value: string}[]} [args.headers] - The headers to attach.
|
|
74
|
-
* @returns {XMLHttpRequest}
|
|
75
|
-
*/
|
|
76
|
-
export function fetchKeepAlive(_ref2) {
|
|
77
|
-
let {
|
|
78
|
-
url,
|
|
79
|
-
body = null,
|
|
80
|
-
method = 'POST',
|
|
81
|
-
headers = [{
|
|
82
|
-
key: 'content-type',
|
|
83
|
-
value: 'text/plain'
|
|
84
|
-
}]
|
|
85
|
-
} = _ref2;
|
|
86
|
-
return fetch(url, {
|
|
87
|
-
method,
|
|
88
|
-
headers: headers.reduce((aggregator, header) => {
|
|
89
|
-
aggregator.push([header.key, header.value]);
|
|
90
|
-
return aggregator;
|
|
91
|
-
}, []),
|
|
92
|
-
body,
|
|
93
|
-
keepalive: true
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
67
|
/**
|
|
98
68
|
* Send via sendBeacon. Do NOT call this function outside of a guaranteed web window environment.
|
|
99
69
|
* @param {Object} args - The args
|
|
@@ -101,11 +71,11 @@ export function fetchKeepAlive(_ref2) {
|
|
|
101
71
|
* @param {string=} args.body - The Stringified body
|
|
102
72
|
* @returns {boolean}
|
|
103
73
|
*/
|
|
104
|
-
export function beacon(
|
|
74
|
+
export function beacon(_ref2) {
|
|
105
75
|
let {
|
|
106
76
|
url,
|
|
107
77
|
body
|
|
108
|
-
} =
|
|
78
|
+
} = _ref2;
|
|
109
79
|
try {
|
|
110
80
|
// Navigator has to be bound to ensure it does not error in some browsers
|
|
111
81
|
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
|
|
@@ -113,8 +83,7 @@ export function beacon(_ref3) {
|
|
|
113
83
|
return send(url, body);
|
|
114
84
|
} catch (err) {
|
|
115
85
|
// if sendBeacon still trys to throw an illegal invocation error,
|
|
116
|
-
// we can catch here and return
|
|
117
|
-
// fetchKeepAlive to try to send
|
|
86
|
+
// we can catch here and return
|
|
118
87
|
return false;
|
|
119
88
|
}
|
|
120
89
|
}
|
|
@@ -86,13 +86,19 @@ export function wrapJsonP(sharedEE) {
|
|
|
86
86
|
var matches = src.match(CALLBACK_REGEX);
|
|
87
87
|
return matches ? matches[1] : null;
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Traverse a nested object using a '.'-delimited string wherein each substring piece maps to each subsequent object property layer.
|
|
92
|
+
* @param {string} longKey
|
|
93
|
+
* @param {object} obj
|
|
94
|
+
* @returns The final nested object referred to by initial longKey.
|
|
95
|
+
*/
|
|
89
96
|
function discoverValue(longKey, obj) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
97
|
+
if (!longKey) return obj; // end of object recursion depth when no more key levels
|
|
98
|
+
const matches = longKey.match(VALUE_REGEX);
|
|
99
|
+
// if 'longKey' was not undefined, that is it at least had 1 level left, then the regexp would've at least matched 1st group
|
|
100
|
+
const key = matches[1];
|
|
101
|
+
const remaining = matches[3];
|
|
96
102
|
return discoverValue(remaining, obj[key]);
|
|
97
103
|
}
|
|
98
104
|
function discoverParent(key) {
|
|
@@ -6,7 +6,7 @@ import { registerHandler as register } from '../../../common/event-emitter/regis
|
|
|
6
6
|
import { stringify } from '../../../common/util/stringify';
|
|
7
7
|
import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer';
|
|
8
8
|
import { handle } from '../../../common/event-emitter/handle';
|
|
9
|
-
import {
|
|
9
|
+
import { getConfiguration, getInfo, getRuntime } from '../../../common/config/config';
|
|
10
10
|
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
11
11
|
import { setDenyList, shouldCollectEvent } from '../../../common/deny-list/deny-list';
|
|
12
12
|
import { FEATURE_NAME } from '../constants';
|
|
@@ -18,13 +18,22 @@ export class Aggregate extends AggregateBase {
|
|
|
18
18
|
static featureName = FEATURE_NAME;
|
|
19
19
|
constructor(agentIdentifier, aggregator) {
|
|
20
20
|
super(agentIdentifier, aggregator, FEATURE_NAME);
|
|
21
|
+
const agentInit = getConfiguration(agentIdentifier);
|
|
22
|
+
const allAjaxIsEnabled = agentInit.ajax.enabled !== false;
|
|
23
|
+
register('xhr', storeXhr, this.featureName, this.ee);
|
|
24
|
+
if (!allAjaxIsEnabled) {
|
|
25
|
+
drain(this.agentIdentifier, this.featureName);
|
|
26
|
+
return; // feature will only collect timeslice metrics & ajax trace nodes if it's not fully enabled
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const denyList = getRuntime(agentIdentifier).denyList;
|
|
30
|
+
setDenyList(denyList);
|
|
21
31
|
let ajaxEvents = [];
|
|
22
32
|
let spaAjaxEvents = {};
|
|
23
33
|
let sentAjaxEvents = [];
|
|
24
|
-
let scheduler;
|
|
25
34
|
const ee = this.ee;
|
|
26
|
-
const harvestTimeSeconds =
|
|
27
|
-
const MAX_PAYLOAD_SIZE =
|
|
35
|
+
const harvestTimeSeconds = agentInit.ajax.harvestTimeSeconds || 10;
|
|
36
|
+
const MAX_PAYLOAD_SIZE = agentInit.ajax.maxPayloadSize || 1000000;
|
|
28
37
|
|
|
29
38
|
// Exposes these methods to browser test files -- future TO DO: can be removed once these fns are extracted from the constructor into class func
|
|
30
39
|
this.storeXhr = storeXhr;
|
|
@@ -41,24 +50,22 @@ export class Aggregate extends AggregateBase {
|
|
|
41
50
|
delete spaAjaxEvents[interaction.id];
|
|
42
51
|
});
|
|
43
52
|
ee.on('interactionDiscarded', interaction => {
|
|
44
|
-
if (!spaAjaxEvents[interaction.id]
|
|
53
|
+
if (!spaAjaxEvents[interaction.id]) return;
|
|
45
54
|
spaAjaxEvents[interaction.id].forEach(function (item) {
|
|
46
55
|
// move it from the spaAjaxEvents buffer to the ajaxEvents buffer for harvesting here
|
|
47
56
|
ajaxEvents.push(item);
|
|
48
57
|
});
|
|
49
58
|
delete spaAjaxEvents[interaction.id];
|
|
50
59
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
61
|
-
}
|
|
60
|
+
const scheduler = new HarvestScheduler('events', {
|
|
61
|
+
onFinished: onEventsHarvestFinished,
|
|
62
|
+
getPayload: prepareHarvest
|
|
63
|
+
}, this);
|
|
64
|
+
ee.on("drain-".concat(this.featureName), () => {
|
|
65
|
+
scheduler.startTimer(harvestTimeSeconds);
|
|
66
|
+
});
|
|
67
|
+
drain(this.agentIdentifier, this.featureName);
|
|
68
|
+
return;
|
|
62
69
|
function storeXhr(params, metrics, startTime, endTime, type) {
|
|
63
70
|
metrics.time = startTime;
|
|
64
71
|
|
|
@@ -73,9 +80,7 @@ export class Aggregate extends AggregateBase {
|
|
|
73
80
|
|
|
74
81
|
// store as metric
|
|
75
82
|
aggregator.store('xhr', hash, params, metrics);
|
|
76
|
-
if (!allAjaxIsEnabled
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
83
|
+
if (!allAjaxIsEnabled) return;
|
|
79
84
|
if (!shouldCollectEvent(params)) {
|
|
80
85
|
if (params.hostname === getInfo(agentIdentifier).errorBeacon) {
|
|
81
86
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/Agent'], undefined, FEATURE_NAMES.metrics, ee);
|
|
@@ -157,7 +162,7 @@ export class Aggregate extends AggregateBase {
|
|
|
157
162
|
return tooBig ? getPayload(events, maxPayloadSize, ++chunks) : payload;
|
|
158
163
|
}
|
|
159
164
|
function onEventsHarvestFinished(result) {
|
|
160
|
-
if (result.retry && sentAjaxEvents.length > 0
|
|
165
|
+
if (result.retry && sentAjaxEvents.length > 0) {
|
|
161
166
|
ajaxEvents.unshift(...sentAjaxEvents);
|
|
162
167
|
sentAjaxEvents = [];
|
|
163
168
|
}
|
|
@@ -206,13 +211,5 @@ export class Aggregate extends AggregateBase {
|
|
|
206
211
|
return this.payload.length * 2 > maxPayloadSize;
|
|
207
212
|
};
|
|
208
213
|
}
|
|
209
|
-
function allAjaxIsEnabled() {
|
|
210
|
-
var enabled = getConfigurationValue(agentIdentifier, 'ajax.enabled');
|
|
211
|
-
if (enabled === false) {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
drain(this.agentIdentifier, this.featureName);
|
|
217
214
|
}
|
|
218
215
|
}
|
|
@@ -222,7 +222,7 @@ function computeStackTraceBySourceAndLine(ex) {
|
|
|
222
222
|
mode: 'sourceline',
|
|
223
223
|
name: className,
|
|
224
224
|
message: ex.message,
|
|
225
|
-
stackString:
|
|
225
|
+
stackString: className + ': ' + ex.message + '\n in evaluated code',
|
|
226
226
|
frames: [{
|
|
227
227
|
func: 'evaluated code'
|
|
228
228
|
}]
|
|
@@ -5,63 +5,53 @@
|
|
|
5
5
|
|
|
6
6
|
import { handle } from '../../../common/event-emitter/handle';
|
|
7
7
|
import { now } from '../../../common/timing/now';
|
|
8
|
-
import { getOrSet } from '../../../common/util/get-or-set';
|
|
9
|
-
import { wrapRaf, wrapTimer, wrapEvents, wrapXhr } from '../../../common/wrap';
|
|
10
|
-
import './debug';
|
|
11
8
|
import { InstrumentBase } from '../../utils/instrument-base';
|
|
12
|
-
import { FEATURE_NAME
|
|
9
|
+
import { FEATURE_NAME } from '../constants';
|
|
13
10
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
14
11
|
import { globalScope } from '../../../common/constants/runtime';
|
|
15
12
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
|
|
16
|
-
import { getRuntime } from '../../../common/config/config';
|
|
17
13
|
import { stringify } from '../../../common/util/stringify';
|
|
14
|
+
import { UncaughtError } from './uncaught-error';
|
|
18
15
|
export class Instrument extends InstrumentBase {
|
|
19
16
|
static featureName = FEATURE_NAME;
|
|
17
|
+
#seenErrors = new Set();
|
|
20
18
|
constructor(agentIdentifier, aggregator) {
|
|
21
19
|
let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
22
20
|
super(agentIdentifier, aggregator, FEATURE_NAME, auto);
|
|
23
|
-
// skipNext counter to keep track of uncaught
|
|
24
|
-
// errors that will be the same as caught errors.
|
|
25
|
-
this.skipNext = 0;
|
|
26
21
|
try {
|
|
27
22
|
// this try-catch can be removed when IE11 is completely unsupported & gone
|
|
28
23
|
this.removeOnAbort = new AbortController();
|
|
29
24
|
} catch (e) {}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
getOrSet(err, NR_ERR_PROP, function getVal() {
|
|
37
|
-
return true;
|
|
38
|
-
});
|
|
39
|
-
this.thrown = true;
|
|
40
|
-
handle('err', [err, now()], undefined, FEATURE_NAMES.jserrors, thisInstrument.ee);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
thisInstrument.ee.on('fn-end', function () {
|
|
44
|
-
if (!thisInstrument.abortHandler) return;
|
|
45
|
-
if (!this.thrown && thisInstrument.skipNext > 0) thisInstrument.skipNext -= 1;
|
|
25
|
+
|
|
26
|
+
// Capture function errors early in case the spa feature is loaded
|
|
27
|
+
this.ee.on('fn-err', (args, obj, error) => {
|
|
28
|
+
if (!this.abortHandler || this.#seenErrors.has(error)) return;
|
|
29
|
+
this.#seenErrors.add(error);
|
|
30
|
+
handle('err', [this.#castError(error), now()], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
46
31
|
});
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
this.ee.on('internal-error', error => {
|
|
33
|
+
if (!this.abortHandler) return;
|
|
34
|
+
handle('ierr', [this.#castError(error), now(), true], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
49
35
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
globalScope.onerror = this.onerrorHandler.bind(this);
|
|
54
|
-
globalScope.addEventListener('unhandledrejection', e => {
|
|
55
|
-
/** rejections can contain data of any type -- this is an effort to keep the message human readable */
|
|
56
|
-
const err = castReasonToError(e.reason);
|
|
57
|
-
handle('err', [err, now(), false, {
|
|
36
|
+
globalScope.addEventListener('unhandledrejection', promiseRejectionEvent => {
|
|
37
|
+
if (!this.abortHandler) return;
|
|
38
|
+
handle('err', [this.#castPromiseRejectionEvent(promiseRejectionEvent), now(), false, {
|
|
58
39
|
unhandledPromiseRejection: 1
|
|
59
40
|
}], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
60
41
|
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
globalScope.addEventListener('error', errorEvent => {
|
|
43
|
+
if (!this.abortHandler) return;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* If the spa feature is loaded, errors may already have been captured in the `fn-err` listener above.
|
|
47
|
+
* This ensures those errors are not captured twice.
|
|
48
|
+
*/
|
|
49
|
+
if (this.#seenErrors.has(errorEvent.error)) {
|
|
50
|
+
this.#seenErrors.delete(errorEvent.error);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
handle('err', [this.#castErrorEvent(errorEvent), now()], undefined, FEATURE_NAMES.jserrors, this.ee);
|
|
54
|
+
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
65
55
|
this.abortHandler = this.#abort; // we also use this as a flag to denote that the feature is active or on and handling errors
|
|
66
56
|
this.importAggregator();
|
|
67
57
|
}
|
|
@@ -69,65 +59,66 @@ export class Instrument extends InstrumentBase {
|
|
|
69
59
|
/** Restoration and resource release tasks to be done if JS error loader is being aborted. Unwind changes to globals. */
|
|
70
60
|
#abort() {
|
|
71
61
|
this.removeOnAbort?.abort();
|
|
62
|
+
this.#seenErrors.clear();
|
|
72
63
|
this.abortHandler = undefined; // weakly allow this abort op to run only once
|
|
73
64
|
}
|
|
74
65
|
|
|
75
66
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* @param {
|
|
79
|
-
* @
|
|
80
|
-
* @param {number} lineno
|
|
81
|
-
* @param {number} column
|
|
82
|
-
* @param {Error | *} errorObj
|
|
83
|
-
* @returns
|
|
67
|
+
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
68
|
+
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
69
|
+
* @param {any} error The value thrown
|
|
70
|
+
* @returns {Error|UncaughtError} The converted error instance
|
|
84
71
|
*/
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
72
|
+
#castError(error) {
|
|
73
|
+
if (error instanceof Error) {
|
|
74
|
+
return error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The thrown value may contain a message property. If it does, try to treat the thrown
|
|
79
|
+
* value as an Error-like object.
|
|
80
|
+
*/
|
|
81
|
+
if (typeof error?.message !== 'undefined') {
|
|
82
|
+
return new UncaughtError(error.message, error.filename || error.sourceURL, error.lineno || error.line, error.colno || error.col);
|
|
83
|
+
}
|
|
84
|
+
return new UncaughtError(typeof error === 'string' ? error : stringify(error));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
89
|
+
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
90
|
+
* @returns {Error} An Error object with the message as the casted reason
|
|
91
|
+
*/
|
|
92
|
+
#castPromiseRejectionEvent(promiseRejectionEvent) {
|
|
93
|
+
let prefix = 'Unhandled Promise Rejection: ';
|
|
94
|
+
if (promiseRejectionEvent?.reason instanceof Error) {
|
|
90
95
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
promiseRejectionEvent.reason.message = prefix + promiseRejectionEvent.reason.message;
|
|
97
|
+
return promiseRejectionEvent.reason;
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return promiseRejectionEvent.reason;
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
|
-
|
|
102
|
+
if (typeof promiseRejectionEvent.reason === 'undefined') return new UncaughtError(prefix);
|
|
103
|
+
const error = this.#castError(promiseRejectionEvent.reason);
|
|
104
|
+
error.message = prefix + error.message;
|
|
105
|
+
return error;
|
|
97
106
|
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
102
|
-
* @param {string} message
|
|
103
|
-
* @param {string} filename
|
|
104
|
-
* @param {number} lineno
|
|
105
|
-
*/
|
|
106
|
-
function UncaughtException(message, filename, lineno) {
|
|
107
|
-
this.message = message || 'Uncaught error with no additional information';
|
|
108
|
-
this.sourceURL = filename;
|
|
109
|
-
this.line = lineno;
|
|
110
|
-
}
|
|
111
107
|
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
reason.message = prefix + reason.message;
|
|
122
|
-
return reason;
|
|
123
|
-
} catch (e) {
|
|
124
|
-
return reason;
|
|
108
|
+
/**
|
|
109
|
+
* Attempts to convert an ErrorEvent object to an Error object
|
|
110
|
+
* @param {ErrorEvent} errorEvent The error event
|
|
111
|
+
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
112
|
+
*/
|
|
113
|
+
#castErrorEvent(errorEvent) {
|
|
114
|
+
if (errorEvent.error instanceof Error) {
|
|
115
|
+
return errorEvent.error;
|
|
125
116
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return new
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Older browsers do not contain the `error` property on the ErrorEvent instance.
|
|
120
|
+
* https://caniuse.com/mdn-api_errorevent_error
|
|
121
|
+
*/
|
|
122
|
+
return new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
|
|
132
123
|
}
|
|
133
124
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents an uncaught non Error type error. This class does
|
|
3
|
+
* not extend the Error class to prevent an invalid stack trace
|
|
4
|
+
* from being created. Use this class to cast thrown errors that
|
|
5
|
+
* do not use the Error class (strings, etc) to an object.
|
|
6
|
+
*/
|
|
7
|
+
export class UncaughtError {
|
|
8
|
+
constructor(message, filename, lineno, colno) {
|
|
9
|
+
this.name = 'UncaughtError';
|
|
10
|
+
this.message = message;
|
|
11
|
+
this.sourceURL = filename;
|
|
12
|
+
this.line = lineno;
|
|
13
|
+
this.column = colno;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -148,6 +148,14 @@ export class Aggregate extends AggregateBase {
|
|
|
148
148
|
// Capture metrics for size of custom attributes
|
|
149
149
|
const jsAttributes = stringify(info.jsAttributes);
|
|
150
150
|
this.storeSupportabilityMetrics('PageSession/Feature/CustomData/Bytes', jsAttributes === '{}' ? 0 : jsAttributes.length);
|
|
151
|
+
|
|
152
|
+
// Capture metrics for performance markers and measures
|
|
153
|
+
if (typeof performance !== 'undefined') {
|
|
154
|
+
const markers = performance.getEntriesByType('mark');
|
|
155
|
+
const measures = performance.getEntriesByType('measure');
|
|
156
|
+
this.storeSupportabilityMetrics('Generic/Performance/Mark/Seen', markers.length);
|
|
157
|
+
this.storeSupportabilityMetrics('Generic/Performance/Measure/Seen', measures.length);
|
|
158
|
+
}
|
|
151
159
|
} catch (e) {
|
|
152
160
|
// do nothing
|
|
153
161
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
2
|
+
import { gosNREUM } from '../../../common/window/nreum';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Get an array of flags required by downstream (NR UI) based on the features initialized in this agent
|
|
@@ -8,25 +9,28 @@ import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
|
8
9
|
*/
|
|
9
10
|
export function getActivatedFeaturesFlags(agentId) {
|
|
10
11
|
const flagArr = [];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
12
|
+
const newrelic = gosNREUM();
|
|
13
|
+
try {
|
|
14
|
+
Object.keys(newrelic.initializedAgents[agentId].features).forEach(featName => {
|
|
15
|
+
switch (featName) {
|
|
16
|
+
case FEATURE_NAMES.ajax:
|
|
17
|
+
flagArr.push('xhr');
|
|
18
|
+
break;
|
|
19
|
+
case FEATURE_NAMES.jserrors:
|
|
20
|
+
flagArr.push('err');
|
|
21
|
+
break;
|
|
22
|
+
case FEATURE_NAMES.pageAction:
|
|
23
|
+
flagArr.push('ins');
|
|
24
|
+
break;
|
|
25
|
+
case FEATURE_NAMES.sessionTrace:
|
|
26
|
+
flagArr.push('stn');
|
|
27
|
+
break;
|
|
28
|
+
case FEATURE_NAMES.spa:
|
|
29
|
+
flagArr.push('spa');
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
} catch (e) {}
|
|
30
34
|
return flagArr;
|
|
31
35
|
}
|
|
32
36
|
|