@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/common/config/init-types.js +3 -2
  3. package/dist/cjs/common/config/init.js +10 -8
  4. package/dist/cjs/common/constants/env.cdn.js +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/timing/time-keeper.js +28 -1
  7. package/dist/cjs/common/util/short-circuit.js +13 -0
  8. package/dist/cjs/common/util/submit-data.js +2 -9
  9. package/dist/cjs/common/v2/script-correlation.js +50 -0
  10. package/dist/cjs/common/v2/script-tracker.js +278 -0
  11. package/dist/cjs/common/{util/v2.js → v2/utils.js} +4 -4
  12. package/dist/cjs/common/wrap/wrap-fetch.js +2 -2
  13. package/dist/cjs/common/wrap/wrap-function.js +2 -2
  14. package/dist/cjs/common/wrap/wrap-xhr.js +2 -2
  15. package/dist/cjs/features/ajax/aggregate/index.js +4 -4
  16. package/dist/cjs/features/generic_events/aggregate/index.js +21 -2
  17. package/dist/cjs/features/generic_events/instrument/index.js +24 -21
  18. package/dist/cjs/features/jserrors/aggregate/index.js +206 -40
  19. package/dist/cjs/features/logging/aggregate/index.js +4 -4
  20. package/dist/cjs/features/metrics/instrument/index.js +1 -8
  21. package/dist/cjs/features/session_trace/aggregate/index.js +3 -4
  22. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +2 -2
  23. package/dist/cjs/interfaces/registered-entity.js +7 -20
  24. package/dist/cjs/loaders/api/register-api-types.js +8 -8
  25. package/dist/cjs/loaders/api/register.js +49 -43
  26. package/dist/esm/common/config/init-types.js +3 -2
  27. package/dist/esm/common/config/init.js +10 -8
  28. package/dist/esm/common/constants/env.cdn.js +1 -1
  29. package/dist/esm/common/constants/env.npm.js +1 -1
  30. package/dist/esm/common/timing/time-keeper.js +28 -1
  31. package/dist/esm/common/util/short-circuit.js +6 -0
  32. package/dist/esm/common/util/submit-data.js +2 -9
  33. package/dist/esm/common/v2/script-correlation.js +43 -0
  34. package/dist/esm/common/v2/script-tracker.js +270 -0
  35. package/dist/esm/common/{util/v2.js → v2/utils.js} +4 -4
  36. package/dist/esm/common/wrap/wrap-fetch.js +1 -1
  37. package/dist/esm/common/wrap/wrap-function.js +1 -1
  38. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  39. package/dist/esm/features/ajax/aggregate/index.js +1 -1
  40. package/dist/esm/features/generic_events/aggregate/index.js +20 -1
  41. package/dist/esm/features/generic_events/instrument/index.js +24 -21
  42. package/dist/esm/features/jserrors/aggregate/index.js +205 -39
  43. package/dist/esm/features/logging/aggregate/index.js +1 -1
  44. package/dist/esm/features/metrics/instrument/index.js +2 -9
  45. package/dist/esm/features/session_trace/aggregate/index.js +4 -5
  46. package/dist/esm/features/session_trace/aggregate/trace/storage.js +2 -2
  47. package/dist/esm/interfaces/registered-entity.js +7 -20
  48. package/dist/esm/loaders/api/register-api-types.js +8 -8
  49. package/dist/esm/loaders/api/register.js +46 -41
  50. package/dist/tsconfig.tsbuildinfo +1 -1
  51. package/dist/types/common/config/init-types.d.ts +10 -8
  52. package/dist/types/common/config/init-types.d.ts.map +1 -1
  53. package/dist/types/common/config/init.d.ts.map +1 -1
  54. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  55. package/dist/types/common/util/short-circuit.d.ts +8 -0
  56. package/dist/types/common/util/short-circuit.d.ts.map +1 -0
  57. package/dist/types/common/util/submit-data.d.ts.map +1 -1
  58. package/dist/types/common/v2/script-correlation.d.ts +38 -0
  59. package/dist/types/common/v2/script-correlation.d.ts.map +1 -0
  60. package/dist/types/common/{util → v2}/script-tracker.d.ts +3 -0
  61. package/dist/types/common/v2/script-tracker.d.ts.map +1 -0
  62. package/dist/types/common/{util/v2.d.ts → v2/utils.d.ts} +1 -1
  63. package/dist/types/common/v2/utils.d.ts.map +1 -0
  64. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  65. package/dist/types/features/generic_events/instrument/index.d.ts +1 -1
  66. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  67. package/dist/types/features/jserrors/aggregate/index.d.ts +25 -16
  68. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
  70. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/interfaces/registered-entity.d.ts +0 -10
  72. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  73. package/dist/types/loaders/api/register-api-types.d.ts +15 -15
  74. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  75. package/dist/types/loaders/api/register.d.ts +6 -0
  76. package/dist/types/loaders/api/register.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/src/common/config/init-types.js +3 -2
  79. package/src/common/config/init.js +6 -4
  80. package/src/common/timing/time-keeper.js +30 -1
  81. package/src/common/util/short-circuit.js +6 -0
  82. package/src/common/util/submit-data.js +2 -8
  83. package/src/common/v2/script-correlation.js +44 -0
  84. package/src/common/v2/script-tracker.js +260 -0
  85. package/src/common/{util/v2.js → v2/utils.js} +4 -4
  86. package/src/common/wrap/wrap-fetch.js +1 -1
  87. package/src/common/wrap/wrap-function.js +1 -1
  88. package/src/common/wrap/wrap-xhr.js +1 -1
  89. package/src/features/ajax/aggregate/index.js +1 -1
  90. package/src/features/generic_events/aggregate/index.js +20 -1
  91. package/src/features/generic_events/instrument/index.js +25 -22
  92. package/src/features/jserrors/aggregate/index.js +200 -43
  93. package/src/features/logging/aggregate/index.js +1 -1
  94. package/src/features/metrics/instrument/index.js +2 -13
  95. package/src/features/session_trace/aggregate/index.js +4 -6
  96. package/src/features/session_trace/aggregate/trace/storage.js +2 -2
  97. package/src/interfaces/registered-entity.js +8 -20
  98. package/src/loaders/api/register-api-types.js +8 -8
  99. package/src/loaders/api/register.js +42 -34
  100. package/dist/cjs/common/util/script-tracker.js +0 -204
  101. package/dist/esm/common/util/script-tracker.js +0 -196
  102. package/dist/types/common/util/script-tracker.d.ts.map +0 -1
  103. package/dist/types/common/util/v2.d.ts.map +0 -1
  104. 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.allow_registered_children) return [];
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.allow_registered_children) return [];
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.duplicate_registered_data;
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.allow_registered_children) return [undefined];
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 '../util/v2';
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 '../util/v2';
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 '../util/v2';
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/util/v2';
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/util/v2';
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
- let historyEE, websocketsEE;
40
- if (websocketsEnabled) websocketsEE = wrapWebSocket(this.ee);
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?.signal));
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?.signal));
82
- window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
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
  }