@newrelic/browser-agent 1.312.1 → 1.313.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 +16 -0
- package/dist/cjs/common/config/init-types.js +3 -2
- package/dist/cjs/common/config/init.js +10 -8
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/timing/time-keeper.js +28 -1
- package/dist/cjs/common/util/short-circuit.js +13 -0
- package/dist/cjs/common/util/submit-data.js +2 -9
- package/dist/cjs/common/v2/script-correlation.js +50 -0
- package/dist/cjs/common/v2/script-tracker.js +278 -0
- package/dist/cjs/common/{util/v2.js → v2/utils.js} +4 -4
- package/dist/cjs/common/wrap/wrap-fetch.js +2 -2
- package/dist/cjs/common/wrap/wrap-function.js +2 -2
- package/dist/cjs/common/wrap/wrap-xhr.js +2 -2
- package/dist/cjs/features/ajax/aggregate/index.js +4 -4
- package/dist/cjs/features/generic_events/aggregate/index.js +21 -2
- package/dist/cjs/features/generic_events/instrument/index.js +24 -21
- package/dist/cjs/features/jserrors/aggregate/index.js +206 -40
- package/dist/cjs/features/logging/aggregate/index.js +4 -4
- package/dist/cjs/features/metrics/instrument/index.js +1 -8
- package/dist/cjs/features/session_trace/aggregate/index.js +3 -4
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +2 -2
- package/dist/cjs/interfaces/registered-entity.js +7 -20
- package/dist/cjs/loaders/api/register-api-types.js +8 -8
- package/dist/cjs/loaders/api/register.js +49 -43
- package/dist/esm/common/config/init-types.js +3 -2
- package/dist/esm/common/config/init.js +10 -8
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/timing/time-keeper.js +28 -1
- package/dist/esm/common/util/short-circuit.js +6 -0
- package/dist/esm/common/util/submit-data.js +2 -9
- package/dist/esm/common/v2/script-correlation.js +43 -0
- package/dist/esm/common/v2/script-tracker.js +270 -0
- package/dist/esm/common/{util/v2.js → v2/utils.js} +4 -4
- package/dist/esm/common/wrap/wrap-fetch.js +1 -1
- package/dist/esm/common/wrap/wrap-function.js +1 -1
- package/dist/esm/common/wrap/wrap-xhr.js +1 -1
- package/dist/esm/features/ajax/aggregate/index.js +1 -1
- package/dist/esm/features/generic_events/aggregate/index.js +20 -1
- package/dist/esm/features/generic_events/instrument/index.js +24 -21
- package/dist/esm/features/jserrors/aggregate/index.js +205 -39
- package/dist/esm/features/logging/aggregate/index.js +1 -1
- package/dist/esm/features/metrics/instrument/index.js +2 -9
- package/dist/esm/features/session_trace/aggregate/index.js +4 -5
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +2 -2
- package/dist/esm/interfaces/registered-entity.js +7 -20
- package/dist/esm/loaders/api/register-api-types.js +8 -8
- package/dist/esm/loaders/api/register.js +46 -41
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init-types.d.ts +10 -8
- package/dist/types/common/config/init-types.d.ts.map +1 -1
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
- package/dist/types/common/util/short-circuit.d.ts +8 -0
- package/dist/types/common/util/short-circuit.d.ts.map +1 -0
- package/dist/types/common/util/submit-data.d.ts.map +1 -1
- package/dist/types/common/v2/script-correlation.d.ts +38 -0
- package/dist/types/common/v2/script-correlation.d.ts.map +1 -0
- package/dist/types/common/{util → v2}/script-tracker.d.ts +3 -0
- package/dist/types/common/v2/script-tracker.d.ts.map +1 -0
- package/dist/types/common/{util/v2.d.ts → v2/utils.d.ts} +1 -1
- package/dist/types/common/v2/utils.d.ts.map +1 -0
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +25 -16
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/interfaces/registered-entity.d.ts +0 -10
- package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
- package/dist/types/loaders/api/register-api-types.d.ts +15 -15
- package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts +6 -0
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/config/init-types.js +3 -2
- package/src/common/config/init.js +6 -4
- package/src/common/timing/time-keeper.js +30 -1
- package/src/common/util/short-circuit.js +6 -0
- package/src/common/util/submit-data.js +2 -8
- package/src/common/v2/script-correlation.js +44 -0
- package/src/common/v2/script-tracker.js +260 -0
- package/src/common/{util/v2.js → v2/utils.js} +4 -4
- package/src/common/wrap/wrap-fetch.js +1 -1
- package/src/common/wrap/wrap-function.js +1 -1
- package/src/common/wrap/wrap-xhr.js +1 -1
- package/src/features/ajax/aggregate/index.js +1 -1
- package/src/features/generic_events/aggregate/index.js +20 -1
- package/src/features/generic_events/instrument/index.js +25 -22
- package/src/features/jserrors/aggregate/index.js +200 -43
- package/src/features/logging/aggregate/index.js +1 -1
- package/src/features/metrics/instrument/index.js +2 -13
- package/src/features/session_trace/aggregate/index.js +4 -6
- package/src/features/session_trace/aggregate/trace/storage.js +2 -2
- package/src/interfaces/registered-entity.js +8 -20
- package/src/loaders/api/register-api-types.js +8 -8
- package/src/loaders/api/register.js +42 -34
- package/dist/cjs/common/util/script-tracker.js +0 -204
- package/dist/esm/common/util/script-tracker.js +0 -196
- package/dist/types/common/util/script-tracker.d.ts.map +0 -1
- package/dist/types/common/util/v2.d.ts.map +0 -1
- package/src/common/util/script-tracker.js +0 -189
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { globalScope } from '../constants/runtime';
|
|
7
|
+
import { now } from '../timing/now';
|
|
8
|
+
import { cleanURL } from '../url/clean-url';
|
|
9
|
+
import { chrome, chromeEval, gecko } from '../util/browser-stack-matchers';
|
|
10
|
+
import { ScriptCorrelation } from './script-correlation';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** export for testing purposes */
|
|
17
|
+
export let thisFile;
|
|
18
|
+
try {
|
|
19
|
+
thisFile = extractUrlsFromStack(getDeepStackTrace())[0];
|
|
20
|
+
} catch (err) {
|
|
21
|
+
thisFile = extractUrlsFromStack(err)[0];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @type {(entry: PerformanceEntry) => boolean} - A shared function to determine if a performance entry is a valid script or link resource for evaluation */
|
|
25
|
+
const validEntryCriteria = entry => entry.initiatorType === 'script' || ['link', 'fetch'].includes(entry.initiatorType) && entry.name.endsWith('.js');
|
|
26
|
+
|
|
27
|
+
/** @type {Map<string, ScriptCorrelation>} - Central registry for script correlations containing both DOM and Performance data */
|
|
28
|
+
export const scriptCorrelations = new Map();
|
|
29
|
+
/** @type {Array<{ test: (entry: PerformanceEntry) => boolean, addedAt: number }>} an array of PerformanceObserver subscribers to check for late emissions */
|
|
30
|
+
let poSubscribers = [];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves a script correlation by URL using exact matching
|
|
34
|
+
* @param {string} targetUrl - The URL to find
|
|
35
|
+
* @returns {ScriptCorrelation | undefined} - The correlation object if found
|
|
36
|
+
*/
|
|
37
|
+
function findCorrelation(targetUrl) {
|
|
38
|
+
return scriptCorrelations.get(targetUrl);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets or creates a script correlation entry
|
|
43
|
+
* @param {string} url - The cleaned URL
|
|
44
|
+
* @returns {ScriptCorrelation} - The correlation object
|
|
45
|
+
*/
|
|
46
|
+
function getOrCreateCorrelation(url) {
|
|
47
|
+
const existing = findCorrelation(url);
|
|
48
|
+
if (existing) return existing;
|
|
49
|
+
const correlation = new ScriptCorrelation(url);
|
|
50
|
+
scriptCorrelations.set(url, correlation);
|
|
51
|
+
|
|
52
|
+
// Keep size under control
|
|
53
|
+
if (scriptCorrelations.size > 1000) {
|
|
54
|
+
const firstKey = scriptCorrelations.keys().next().value;
|
|
55
|
+
scriptCorrelations.delete(firstKey);
|
|
56
|
+
}
|
|
57
|
+
return correlation;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Set up a MutationObserver to detect script elements being added to the DOM */
|
|
61
|
+
if (globalScope.MutationObserver && globalScope.document) {
|
|
62
|
+
const scriptMutationObserver = new MutationObserver(mutations => {
|
|
63
|
+
mutations.forEach(mutation => {
|
|
64
|
+
mutation.addedNodes.forEach(node => {
|
|
65
|
+
if (node.nodeName === 'SCRIPT' && node.src) {
|
|
66
|
+
const cleanedSrc = cleanURL(node.src);
|
|
67
|
+
const correlation = getOrCreateCorrelation(cleanedSrc);
|
|
68
|
+
correlation.dom.start = now();
|
|
69
|
+
correlation.dom.value = node;
|
|
70
|
+
const setEnd = () => {
|
|
71
|
+
correlation.dom.end = now();
|
|
72
|
+
};
|
|
73
|
+
['load', 'error'].forEach(event => node.addEventListener(event, setEnd, {
|
|
74
|
+
once: true
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
scriptMutationObserver.observe(globalScope.document, {
|
|
81
|
+
childList: true,
|
|
82
|
+
subtree: true
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
|
|
86
|
+
/** We must track the script assets this way, because the performance buffer can fill up and when it does that
|
|
87
|
+
* it stops accepting new entries (instead of dropping old entries), which means if the register API is called
|
|
88
|
+
* after the buffer fills up we won't be able to get the script timing information from the resource timing API
|
|
89
|
+
*/
|
|
90
|
+
const scriptObserver = new PerformanceObserver(list => {
|
|
91
|
+
list.getEntries().filter(validEntryCriteria).forEach(entry => {
|
|
92
|
+
// Update correlation with performance data (creates entry if needed)
|
|
93
|
+
const entryUrl = cleanURL(entry.name);
|
|
94
|
+
const correlation = getOrCreateCorrelation(entryUrl);
|
|
95
|
+
correlation.performance.start = Math.floor(entry.startTime);
|
|
96
|
+
correlation.performance.end = Math.floor(entry.responseEnd);
|
|
97
|
+
correlation.performance.value = entry;
|
|
98
|
+
|
|
99
|
+
// Clear resolved or expired subscribers
|
|
100
|
+
const canClear = [];
|
|
101
|
+
poSubscribers.forEach(({
|
|
102
|
+
test,
|
|
103
|
+
addedAt
|
|
104
|
+
}, idx) => {
|
|
105
|
+
if (test(entry) || now() - addedAt > 10000) canClear.push(idx);
|
|
106
|
+
});
|
|
107
|
+
poSubscribers = poSubscribers.filter((_, idx) => !canClear.includes(idx));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
scriptObserver.observe({
|
|
111
|
+
type: 'resource',
|
|
112
|
+
buffered: true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Extracts URLs from stack traces using the same logic as compute-stack-trace.js
|
|
118
|
+
* @param {string} stack The error stack trace
|
|
119
|
+
* @returns {string[]} Array of cleaned URLs found in the stack trace
|
|
120
|
+
*/
|
|
121
|
+
export function extractUrlsFromStack(stack) {
|
|
122
|
+
if (!stack || typeof stack !== 'string') return [];
|
|
123
|
+
const urls = new Set();
|
|
124
|
+
const lines = stack.split('\n');
|
|
125
|
+
for (const line of lines) {
|
|
126
|
+
// Try gecko format first, then chrome
|
|
127
|
+
const parts = line.match(gecko) || line.match(chrome) || line.match(chromeEval);
|
|
128
|
+
if (parts && parts[2]) {
|
|
129
|
+
urls.add(cleanURL(parts[2]));
|
|
130
|
+
} else {
|
|
131
|
+
// Fallback: match URLs using a generic .js pattern (non-greedy to handle ports and query params)
|
|
132
|
+
const fallbackMatch = line.match(/\(([^)]+\.js):\d+:\d+\)/) || line.match(/^\s+at\s+([^\s(]+\.js):\d+:\d+/);
|
|
133
|
+
if (fallbackMatch && fallbackMatch[1]) {
|
|
134
|
+
urls.add(cleanURL(fallbackMatch[1]));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [...urls];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Returns a deep stack trace by temporarily increasing the stack trace limit.
|
|
143
|
+
* @returns {Error.stack | undefined}
|
|
144
|
+
*/
|
|
145
|
+
export function getDeepStackTrace() {
|
|
146
|
+
let stack;
|
|
147
|
+
try {
|
|
148
|
+
const originalStackLimit = Error.stackTraceLimit;
|
|
149
|
+
Error.stackTraceLimit = 50;
|
|
150
|
+
stack = new Error().stack;
|
|
151
|
+
Error.stackTraceLimit = originalStackLimit;
|
|
152
|
+
} catch (e) {
|
|
153
|
+
stack = new Error().stack;
|
|
154
|
+
}
|
|
155
|
+
return stack;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Indicates whether the provided URL matches any script preload link tags in the document.
|
|
160
|
+
* @param {string} targetUrl The URL to match against preload tags
|
|
161
|
+
* @returns {boolean} True if a matching preload link is found, false otherwise
|
|
162
|
+
*/
|
|
163
|
+
function wasPreloaded(targetUrl) {
|
|
164
|
+
if (!targetUrl || !globalScope.document) return false;
|
|
165
|
+
try {
|
|
166
|
+
const linkTags = globalScope.document.querySelectorAll('link[rel="preload"][as="script"]');
|
|
167
|
+
for (const link of linkTags) {
|
|
168
|
+
// link.href is resolved to an absolute URL by the browser (even if supplied as relative), so we can match exactly against the cleaned target URL
|
|
169
|
+
if (cleanURL(link.href) === targetUrl) return true;
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// Don't let DOM parsing errors break anything
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Checks if a performance entry matches the target MFE script URL using exact matching
|
|
179
|
+
* @param {PerformanceResourceTiming} entry - The resource timing entry
|
|
180
|
+
* @param {string} targetUrl - The MFE script URL to match
|
|
181
|
+
* @returns {boolean} True if the entry matches
|
|
182
|
+
*/
|
|
183
|
+
function entryMatchesUrl(entry, targetUrl) {
|
|
184
|
+
const entryUrl = cleanURL(entry.name);
|
|
185
|
+
return entryUrl === targetUrl;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Applies performance entry timing data to a timings object
|
|
190
|
+
* @param {RegisterAPITimings} timings - The timings object to update
|
|
191
|
+
* @param {PerformanceResourceTiming} entry - The performance entry
|
|
192
|
+
*/
|
|
193
|
+
function applyPerformanceEntry(timings, entry) {
|
|
194
|
+
timings.fetchStart = Math.floor(entry.startTime);
|
|
195
|
+
timings.fetchEnd = Math.floor(entry.responseEnd);
|
|
196
|
+
timings.asset = entry.name;
|
|
197
|
+
timings.type = entry.initiatorType;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Uses the stack of the initiator function, returns script timing information if a script can be found with the resource timing API matching the URL found in the stack.
|
|
202
|
+
* @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
|
|
203
|
+
*/
|
|
204
|
+
export function findScriptTimings() {
|
|
205
|
+
const timings = {
|
|
206
|
+
registeredAt: now(),
|
|
207
|
+
reportedAt: undefined,
|
|
208
|
+
fetchStart: 0,
|
|
209
|
+
fetchEnd: 0,
|
|
210
|
+
scriptStart: 0,
|
|
211
|
+
scriptEnd: 0,
|
|
212
|
+
asset: undefined,
|
|
213
|
+
type: 'unknown'
|
|
214
|
+
};
|
|
215
|
+
const stack = getDeepStackTrace();
|
|
216
|
+
if (!stack) return timings;
|
|
217
|
+
const navUrl = globalScope.performance?.getEntriesByType('navigation')?.[0]?.name || '';
|
|
218
|
+
try {
|
|
219
|
+
const urls = extractUrlsFromStack(stack);
|
|
220
|
+
// Filter out agent file from URLs (unless it's the only one)
|
|
221
|
+
const mfeScriptUrl = (urls.length > 1 ? urls.filter(line => thisFile !== line) : urls)[0];
|
|
222
|
+
if (!mfeScriptUrl) return timings;
|
|
223
|
+
|
|
224
|
+
// Check for inline script
|
|
225
|
+
if (navUrl.includes(mfeScriptUrl)) {
|
|
226
|
+
timings.asset = cleanURL(navUrl);
|
|
227
|
+
timings.type = 'inline';
|
|
228
|
+
return timings;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Get correlation data
|
|
232
|
+
timings.correlation = findCorrelation(mfeScriptUrl);
|
|
233
|
+
|
|
234
|
+
// Use correlation's performance entry if available, otherwise check live performance API
|
|
235
|
+
const performanceEntry = timings.correlation?.performance.value || performance.getEntriesByType('resource').find(e => entryMatchesUrl(e, mfeScriptUrl));
|
|
236
|
+
if (performanceEntry) {
|
|
237
|
+
applyPerformanceEntry(timings, performanceEntry);
|
|
238
|
+
} else if (wasPreloaded(mfeScriptUrl)) {
|
|
239
|
+
// Handle preloaded scripts that may report late
|
|
240
|
+
timings.asset = mfeScriptUrl;
|
|
241
|
+
timings.type = 'preload';
|
|
242
|
+
|
|
243
|
+
// Subscribe to late performance observer callbacks
|
|
244
|
+
poSubscribers.push({
|
|
245
|
+
addedAt: now(),
|
|
246
|
+
test: entry => {
|
|
247
|
+
if (entryMatchesUrl(entry, mfeScriptUrl)) {
|
|
248
|
+
applyPerformanceEntry(timings, entry);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* Use getters here because the correlation data may arrive after this function returns the timing object, and we want to provide the most up-to-date timing information possible when the getters are accessed at harvest time.
|
|
258
|
+
* The getters will fall back to fetchEnd if correlation data isn't available yet, which is our best approximation for script execution start when actual script timings can not be determined.
|
|
259
|
+
*/
|
|
260
|
+
Object.defineProperty(timings, 'scriptStart', {
|
|
261
|
+
get: () => timings.correlation?.script.start || timings.fetchEnd
|
|
262
|
+
});
|
|
263
|
+
Object.defineProperty(timings, 'scriptEnd', {
|
|
264
|
+
get: () => timings.correlation?.script.end || timings.registeredAt
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
// Don't let stack parsing errors break anything
|
|
268
|
+
}
|
|
269
|
+
return timings;
|
|
270
|
+
}
|
|
@@ -23,7 +23,7 @@ export const V2_TYPES = {
|
|
|
23
23
|
* @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
|
|
24
24
|
*/
|
|
25
25
|
export function getRegisteredTargetsFromId(id, agentRef) {
|
|
26
|
-
if (!id || !agentRef?.init.api.
|
|
26
|
+
if (!id || !agentRef?.init.api.register.enabled) return [];
|
|
27
27
|
const registeredEntities = agentRef.runtime.registeredEntities;
|
|
28
28
|
return registeredEntities?.filter(entity => String(entity.metadata.target.id) === String(id)).map(entity => entity.metadata.target) || [];
|
|
29
29
|
}
|
|
@@ -35,7 +35,7 @@ export function getRegisteredTargetsFromId(id, agentRef) {
|
|
|
35
35
|
* @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
|
|
36
36
|
*/
|
|
37
37
|
export function getRegisteredTargetsFromFilename(filename, agentRef) {
|
|
38
|
-
if (!filename || !agentRef?.init.api.
|
|
38
|
+
if (!filename || !agentRef?.init.api.register.enabled) return [];
|
|
39
39
|
const registeredEntities = agentRef.runtime.registeredEntities;
|
|
40
40
|
return registeredEntities?.filter(entity => entity.metadata.timings?.asset?.endsWith(filename)).map(entity => entity.metadata.target) || [];
|
|
41
41
|
}
|
|
@@ -87,7 +87,7 @@ export function getVersion2DuplicationAttributes(target, aggregateInstance) {
|
|
|
87
87
|
* @returns {boolean} returns true if the event should be duplicated for the target, false otherwise
|
|
88
88
|
*/
|
|
89
89
|
export function shouldDuplicate(target, aggregateInstance) {
|
|
90
|
-
return !!target && !!supportsV2(aggregateInstance) && aggregateInstance.agentRef.init.api.
|
|
90
|
+
return !!target && !!supportsV2(aggregateInstance) && aggregateInstance.agentRef.init.api.register.duplicate_data_to_container;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
@@ -96,7 +96,7 @@ export function shouldDuplicate(target, aggregateInstance) {
|
|
|
96
96
|
* @returns {Array} An array of targets found from the stack trace. If no targets are found or allowed, returns an array with undefined.
|
|
97
97
|
*/
|
|
98
98
|
export function findTargetsFromStackTrace(agentRef) {
|
|
99
|
-
if (!agentRef?.init.api.
|
|
99
|
+
if (!agentRef?.init.api.register.enabled) return [undefined];
|
|
100
100
|
const targets = [];
|
|
101
101
|
try {
|
|
102
102
|
var urls = extractUrlsFromStack(getDeepStackTrace());
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { ee as baseEE, contextId } from '../event-emitter/contextual-ee';
|
|
11
11
|
import { globalScope } from '../constants/runtime';
|
|
12
|
-
import { findTargetsFromStackTrace } from '../
|
|
12
|
+
import { findTargetsFromStackTrace } from '../v2/utils';
|
|
13
13
|
var prefix = 'fetch-';
|
|
14
14
|
var bodyPrefix = prefix + 'body-';
|
|
15
15
|
var bodyMethods = ['arrayBuffer', 'blob', 'json', 'text', 'formData'];
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { ee } from '../event-emitter/contextual-ee';
|
|
11
11
|
import { bundleId } from '../ids/bundle-id';
|
|
12
|
-
import { findTargetsFromStackTrace } from '../
|
|
12
|
+
import { findTargetsFromStackTrace } from '../v2/utils';
|
|
13
13
|
export const flag = "nr@original:".concat(bundleId);
|
|
14
14
|
const LONG_TASK_THRESHOLD = 50;
|
|
15
15
|
|
|
@@ -14,7 +14,7 @@ import { eventListenerOpts } from '../event-listener/event-listener-opts';
|
|
|
14
14
|
import { createWrapperWithEmitter as wfn } from './wrap-function';
|
|
15
15
|
import { globalScope } from '../constants/runtime';
|
|
16
16
|
import { warn } from '../util/console';
|
|
17
|
-
import { findTargetsFromStackTrace } from '../
|
|
17
|
+
import { findTargetsFromStackTrace } from '../v2/utils';
|
|
18
18
|
const wrapped = {};
|
|
19
19
|
const XHR_PROPS = ['open', 'send']; // these are the specific funcs being wrapped on all XMLHttpRequests(.prototype)
|
|
20
20
|
|
|
@@ -12,7 +12,7 @@ import { AggregateBase } from '../../utils/aggregate-base';
|
|
|
12
12
|
import { parseGQL } from './gql';
|
|
13
13
|
import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer';
|
|
14
14
|
import { gosNREUMOriginals } from '../../../common/window/nreum';
|
|
15
|
-
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/
|
|
15
|
+
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
|
|
16
16
|
export class Aggregate extends AggregateBase {
|
|
17
17
|
static featureName = FEATURE_NAME;
|
|
18
18
|
constructor(agentRef) {
|
|
@@ -14,7 +14,7 @@ import { applyFnToProps } from '../../../common/util/traverse';
|
|
|
14
14
|
import { UserActionsAggregator } from './user-actions/user-actions-aggregator';
|
|
15
15
|
import { isIFrameWindow } from '../../../common/dom/iframe';
|
|
16
16
|
import { isPureObject } from '../../../common/util/type-check';
|
|
17
|
-
import { getVersion2Attributes } from '../../../common/
|
|
17
|
+
import { getVersion2Attributes } from '../../../common/v2/utils';
|
|
18
18
|
export class Aggregate extends AggregateBase {
|
|
19
19
|
static featureName = FEATURE_NAME;
|
|
20
20
|
#userActionAggregator;
|
|
@@ -263,6 +263,25 @@ export class Aggregate extends AggregateBase {
|
|
|
263
263
|
this.addEvent(event);
|
|
264
264
|
}, this.featureName, this.ee);
|
|
265
265
|
}
|
|
266
|
+
if (!agentRef.init.feature_flags.includes('no_spv')) {
|
|
267
|
+
registerHandler('spv', evt => {
|
|
268
|
+
this.addEvent({
|
|
269
|
+
eventType: 'SecurityPolicyViolation',
|
|
270
|
+
timestamp: this.#toEpoch(evt.timeStamp),
|
|
271
|
+
blockedUri: evt.blockedURI,
|
|
272
|
+
documentUri: evt.documentURI,
|
|
273
|
+
effectiveDirective: evt.effectiveDirective,
|
|
274
|
+
originalPolicy: evt.originalPolicy,
|
|
275
|
+
sourceFile: evt.sourceFile,
|
|
276
|
+
statusCode: evt.statusCode,
|
|
277
|
+
lineNumber: evt.lineNumber,
|
|
278
|
+
columnNumber: evt.columnNumber,
|
|
279
|
+
disposition: evt.disposition,
|
|
280
|
+
sample: evt.sample,
|
|
281
|
+
referrer: evt.referrer
|
|
282
|
+
});
|
|
283
|
+
}, this.featureName, this.ee);
|
|
284
|
+
}
|
|
266
285
|
this.drain();
|
|
267
286
|
});
|
|
268
287
|
}
|
|
@@ -26,9 +26,10 @@ export class Instrument extends InstrumentBase {
|
|
|
26
26
|
constructor(agentRef) {
|
|
27
27
|
super(agentRef, FEATURE_NAME);
|
|
28
28
|
const websocketsEnabled = agentRef.init.feature_flags.includes('websockets');
|
|
29
|
+
const securityPolicyViolationEnabled = !agentRef.init.feature_flags.includes('no_spv');
|
|
29
30
|
|
|
30
31
|
/** config values that gate whether the generic events aggregator should be imported at all */
|
|
31
|
-
const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.performance.resources.enabled, agentRef.init.user_actions.enabled, websocketsEnabled];
|
|
32
|
+
const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.performance.resources.enabled, agentRef.init.user_actions.enabled, websocketsEnabled, securityPolicyViolationEnabled];
|
|
32
33
|
|
|
33
34
|
/** feature specific APIs */
|
|
34
35
|
setupAddPageActionAPI(agentRef);
|
|
@@ -36,8 +37,24 @@ export class Instrument extends InstrumentBase {
|
|
|
36
37
|
setupFinishedAPI(agentRef);
|
|
37
38
|
setupRegisterAPI(agentRef);
|
|
38
39
|
setupMeasureAPI(agentRef);
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
this.removeOnAbort = new AbortController();
|
|
41
|
+
this.abortHandler = () => {
|
|
42
|
+
this.removeOnAbort.abort();
|
|
43
|
+
this.abortHandler = undefined; // weakly allow this abort op to run only once
|
|
44
|
+
};
|
|
45
|
+
let historyEE;
|
|
46
|
+
if (websocketsEnabled) {
|
|
47
|
+
// this can apply outside browser scope such as in worker
|
|
48
|
+
const websocketsEE = wrapWebSocket(this.ee);
|
|
49
|
+
websocketsEE.on('ws', nrData => {
|
|
50
|
+
handle('ws-complete', [nrData], undefined, this.featureName, this.ee);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (securityPolicyViolationEnabled) {
|
|
54
|
+
globalScope.addEventListener('securitypolicyviolation', evt => {
|
|
55
|
+
handle('spv', [evt], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
56
|
+
}, eventListenerOpts(false, this.removeOnAbort.signal));
|
|
57
|
+
}
|
|
41
58
|
if (isBrowserScope) {
|
|
42
59
|
wrapFetch(this.ee, agentRef);
|
|
43
60
|
wrapXhr(this.ee, agentRef);
|
|
@@ -56,7 +73,7 @@ export class Instrument extends InstrumentBase {
|
|
|
56
73
|
);
|
|
57
74
|
globalScope.addEventListener('error', () => {
|
|
58
75
|
handle('uaErr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
59
|
-
}, eventListenerOpts(false, this.removeOnAbort
|
|
76
|
+
}, eventListenerOpts(false, this.removeOnAbort.signal));
|
|
60
77
|
this.ee.on('open-xhr-start', (args, xhr) => {
|
|
61
78
|
if (!isInternalTraffic(args[1])) {
|
|
62
79
|
xhr.addEventListener('readystatechange', () => {
|
|
@@ -64,7 +81,7 @@ export class Instrument extends InstrumentBase {
|
|
|
64
81
|
// HEADERS_RECEIVED
|
|
65
82
|
handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
66
83
|
}
|
|
67
|
-
});
|
|
84
|
+
}, eventListenerOpts(undefined, this.removeOnAbort.signal));
|
|
68
85
|
}
|
|
69
86
|
});
|
|
70
87
|
this.ee.on('fetch-start', fetchArguments => {
|
|
@@ -78,8 +95,8 @@ export class Instrument extends InstrumentBase {
|
|
|
78
95
|
}
|
|
79
96
|
historyEE.on('pushState-end', navigationChange);
|
|
80
97
|
historyEE.on('replaceState-end', navigationChange);
|
|
81
|
-
window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort
|
|
82
|
-
window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort
|
|
98
|
+
window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort.signal));
|
|
99
|
+
window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort.signal));
|
|
83
100
|
function navigationChange() {
|
|
84
101
|
historyEE.emit('navChange');
|
|
85
102
|
}
|
|
@@ -96,20 +113,6 @@ export class Instrument extends InstrumentBase {
|
|
|
96
113
|
});
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
|
-
if (websocketsEnabled) {
|
|
100
|
-
// this can apply outside browser scope such as in worker
|
|
101
|
-
websocketsEE.on('ws', nrData => {
|
|
102
|
-
handle('ws-complete', [nrData], undefined, this.featureName, this.ee);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
this.removeOnAbort = new AbortController();
|
|
107
|
-
} catch (e) {}
|
|
108
|
-
this.abortHandler = () => {
|
|
109
|
-
this.removeOnAbort?.abort();
|
|
110
|
-
this.abortHandler = undefined; // weakly allow this abort op to run only once
|
|
111
|
-
};
|
|
112
|
-
|
|
113
116
|
/** If any of the sources are active, import the aggregator. otherwise deregister */
|
|
114
117
|
if (genericEventSourceConfigs.some(x => x)) this.importAggregator(agentRef, () => import(/* webpackChunkName: "generic_events-aggregate" */'../aggregate'));else this.deregisterDrain();
|
|
115
118
|
}
|