@newrelic/browser-agent 1.310.1 → 1.311.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/configurable.js +2 -2
- package/dist/cjs/common/config/runtime.js +8 -2
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/constants/runtime.js +17 -4
- package/dist/cjs/common/deny-list/deny-list.js +2 -2
- package/dist/cjs/common/drain/drain.js +27 -37
- package/dist/cjs/common/harvest/harvester.js +1 -3
- package/dist/cjs/common/session/session-entity.js +7 -8
- package/dist/cjs/common/util/console.js +1 -2
- package/dist/cjs/common/util/feature-flags.js +5 -16
- package/dist/cjs/common/util/script-tracker.js +22 -4
- package/dist/cjs/common/util/stringify.js +22 -7
- package/dist/cjs/common/vitals/largest-contentful-paint.js +0 -1
- package/dist/cjs/common/vitals/load-time.js +3 -2
- package/dist/cjs/common/vitals/time-to-first-byte.js +2 -2
- package/dist/cjs/features/jserrors/aggregate/index.js +0 -4
- package/dist/cjs/features/page_view_event/aggregate/index.js +3 -3
- package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +3 -5
- package/dist/cjs/features/page_view_event/instrument/index.js +3 -5
- package/dist/cjs/features/page_view_timing/aggregate/index.js +2 -0
- package/dist/cjs/features/session_trace/aggregate/index.js +4 -3
- package/dist/cjs/features/utils/agent-session.js +3 -4
- package/dist/cjs/features/utils/aggregate-base.js +4 -6
- package/dist/cjs/features/utils/feature-base.js +6 -7
- package/dist/cjs/features/utils/instrument-base.js +7 -8
- package/dist/cjs/loaders/api/register-api-types.js +1 -1
- package/dist/cjs/loaders/api/register.js +1 -1
- package/dist/cjs/loaders/api/sharedHandlers.js +2 -4
- package/dist/cjs/loaders/configure/configure.js +23 -21
- package/dist/esm/common/config/configurable.js +2 -2
- package/dist/esm/common/config/runtime.js +8 -2
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/constants/runtime.js +15 -2
- package/dist/esm/common/deny-list/deny-list.js +2 -2
- package/dist/esm/common/drain/drain.js +27 -36
- package/dist/esm/common/harvest/harvester.js +1 -3
- package/dist/esm/common/session/session-entity.js +7 -8
- package/dist/esm/common/util/console.js +1 -2
- package/dist/esm/common/util/feature-flags.js +5 -14
- package/dist/esm/common/util/script-tracker.js +22 -5
- package/dist/esm/common/util/stringify.js +22 -7
- package/dist/esm/common/vitals/largest-contentful-paint.js +0 -1
- package/dist/esm/common/vitals/load-time.js +4 -3
- package/dist/esm/common/vitals/time-to-first-byte.js +3 -3
- package/dist/esm/features/jserrors/aggregate/index.js +0 -4
- package/dist/esm/features/page_view_event/aggregate/index.js +4 -4
- package/dist/esm/features/page_view_event/aggregate/initialized-features.js +3 -5
- package/dist/esm/features/page_view_event/instrument/index.js +3 -5
- package/dist/esm/features/page_view_timing/aggregate/index.js +3 -1
- package/dist/esm/features/session_trace/aggregate/index.js +5 -4
- package/dist/esm/features/utils/agent-session.js +3 -4
- package/dist/esm/features/utils/aggregate-base.js +4 -6
- package/dist/esm/features/utils/feature-base.js +6 -7
- package/dist/esm/features/utils/instrument-base.js +7 -8
- package/dist/esm/loaders/api/register-api-types.js +1 -1
- package/dist/esm/loaders/api/register.js +1 -1
- package/dist/esm/loaders/api/sharedHandlers.js +2 -4
- package/dist/esm/loaders/configure/configure.js +24 -21
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/configurable.d.ts.map +1 -1
- package/dist/types/common/config/runtime.d.ts.map +1 -1
- package/dist/types/common/constants/runtime.d.ts +1 -1
- package/dist/types/common/constants/runtime.d.ts.map +1 -1
- package/dist/types/common/drain/drain.d.ts +6 -6
- package/dist/types/common/drain/drain.d.ts.map +1 -1
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts +2 -2
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/util/console.d.ts.map +1 -1
- package/dist/types/common/util/feature-flags.d.ts +3 -5
- package/dist/types/common/util/feature-flags.d.ts.map +1 -1
- package/dist/types/common/util/script-tracker.d.ts +5 -0
- package/dist/types/common/util/script-tracker.d.ts.map +1 -1
- package/dist/types/common/util/stringify.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts +2 -2
- package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
- package/dist/types/features/page_view_event/instrument/index.d.ts +1 -1
- package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/agent-session.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +0 -1
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/feature-base.d.ts +3 -3
- package/dist/types/features/utils/feature-base.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts +2 -3
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/api/register-api-types.d.ts +1 -1
- package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
- package/dist/types/loaders/api/sharedHandlers.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/config/configurable.js +2 -1
- package/src/common/config/runtime.js +8 -2
- package/src/common/constants/runtime.js +16 -2
- package/src/common/deny-list/deny-list.js +2 -2
- package/src/common/drain/__mocks__/drain.js +2 -1
- package/src/common/drain/drain.js +27 -37
- package/src/common/harvest/harvester.js +1 -3
- package/src/common/session/session-entity.js +7 -8
- package/src/common/util/console.js +1 -2
- package/src/common/util/feature-flags.js +5 -17
- package/src/common/util/script-tracker.js +23 -5
- package/src/common/util/stringify.js +24 -7
- package/src/common/vitals/largest-contentful-paint.js +0 -1
- package/src/common/vitals/load-time.js +4 -3
- package/src/common/vitals/time-to-first-byte.js +3 -3
- package/src/features/jserrors/aggregate/index.js +0 -4
- package/src/features/page_view_event/aggregate/index.js +4 -4
- package/src/features/page_view_event/aggregate/initialized-features.js +3 -5
- package/src/features/page_view_event/instrument/index.js +3 -5
- package/src/features/page_view_timing/aggregate/index.js +4 -1
- package/src/features/session_trace/aggregate/index.js +5 -4
- package/src/features/utils/__mocks__/feature-base.js +3 -3
- package/src/features/utils/agent-session.js +3 -4
- package/src/features/utils/aggregate-base.js +4 -6
- package/src/features/utils/feature-base.js +6 -7
- package/src/features/utils/instrument-base.js +7 -9
- package/src/loaders/api/register-api-types.js +1 -1
- package/src/loaders/api/register.js +1 -1
- package/src/loaders/api/sharedHandlers.js +2 -4
- package/src/loaders/configure/configure.js +30 -26
- package/dist/cjs/features/utils/nr1-debugger.js +0 -30
- package/dist/esm/features/utils/nr1-debugger.js +0 -23
- package/dist/types/features/utils/nr1-debugger.d.ts +0 -2
- package/dist/types/features/utils/nr1-debugger.d.ts.map +0 -1
- package/src/features/utils/nr1-debugger.js +0 -26
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import { generateRandomHexString } from '../ids/unique-id';
|
|
6
6
|
import { warn } from '../util/console';
|
|
7
7
|
import { stringify } from '../util/stringify';
|
|
8
|
-
import { ee } from '../event-emitter/contextual-ee';
|
|
9
8
|
import { Timer } from '../timer/timer';
|
|
10
9
|
import { isBrowserScope } from '../constants/runtime';
|
|
11
10
|
import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, MODE, PREFIX, SESSION_EVENTS, SESSION_EVENT_TYPES } from './constants';
|
|
@@ -39,27 +38,27 @@ const model = {
|
|
|
39
38
|
};
|
|
40
39
|
export class SessionEntity {
|
|
41
40
|
/**
|
|
42
|
-
* Create a self-managing Session Entity. This entity is scoped to the agent
|
|
41
|
+
* Create a self-managing Session Entity. This entity is scoped to the agent which triggered it, allowing for multiple simultaneous session objects to exist.
|
|
43
42
|
* There is one "namespace" an agent can store data in LS -- NRBA_{key}. If there are two agents on one page, and they both use the same key, they could overwrite each other since they would both use the same namespace in LS by default.
|
|
44
43
|
* The value can be overridden in the constructor, but will default to a unique 16 character hex string
|
|
45
44
|
* expiresMs and inactiveMs are used to "expire" the session, but can be overridden in the constructor. Pass 0 to disable expiration timers.
|
|
46
45
|
*/
|
|
47
46
|
constructor(opts) {
|
|
48
47
|
const {
|
|
49
|
-
|
|
48
|
+
agentRef,
|
|
50
49
|
key,
|
|
51
50
|
storage
|
|
52
51
|
} = opts;
|
|
53
|
-
if (!
|
|
54
|
-
throw new Error("Missing required field(s):".concat(!
|
|
52
|
+
if (!agentRef || !key || !storage) {
|
|
53
|
+
throw new Error("Missing required field(s):".concat(!agentRef ? ' agentRef' : '').concat(!key ? ' key' : '').concat(!storage ? ' storage' : ''));
|
|
55
54
|
}
|
|
56
|
-
this.
|
|
55
|
+
this.agentRef = agentRef;
|
|
57
56
|
this.storage = storage;
|
|
58
57
|
this.state = {};
|
|
59
58
|
|
|
60
59
|
// key is intended to act as the k=v pair
|
|
61
60
|
this.key = key;
|
|
62
|
-
this.ee = ee
|
|
61
|
+
this.ee = agentRef.ee;
|
|
63
62
|
wrapEvents(this.ee);
|
|
64
63
|
this.setup(opts);
|
|
65
64
|
|
|
@@ -247,7 +246,7 @@ export class SessionEntity {
|
|
|
247
246
|
this.expiresTimer?.clear?.();
|
|
248
247
|
delete this.isNew;
|
|
249
248
|
this.setup({
|
|
250
|
-
|
|
249
|
+
agentRef: this.agentRef,
|
|
251
250
|
key: this.key,
|
|
252
251
|
storage: this.storage,
|
|
253
252
|
expiresMs: this.expiresMs,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -17,7 +17,6 @@ export function warn(code, secondary) {
|
|
|
17
17
|
if (typeof console.debug !== 'function') return;
|
|
18
18
|
console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(code), secondary);
|
|
19
19
|
dispatchGlobalEvent({
|
|
20
|
-
agentIdentifier: null,
|
|
21
20
|
drained: null,
|
|
22
21
|
type: 'data',
|
|
23
22
|
name: 'warn',
|
|
@@ -1,32 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { dispatchGlobalEvent } from '../dispatch/global-event';
|
|
6
|
-
const sentIds = new Set();
|
|
7
|
-
|
|
8
|
-
/** A map of feature flags and their values as provided by the rum call -- scoped by agent ID */
|
|
9
|
-
export const activatedFeatures = {};
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
|
-
* Sets the activatedFeatures
|
|
8
|
+
* Sets the activatedFeatures on the agentRef, dispatches the global loaded event,
|
|
13
9
|
* and emits the rumresp flag to features
|
|
14
10
|
* @param {{[key:string]:number}} flags key-val pair of flag names and numeric
|
|
15
|
-
* @param {
|
|
11
|
+
* @param {Object} agentRef agent reference
|
|
16
12
|
* @returns {void}
|
|
17
13
|
*/
|
|
18
14
|
export function activateFeatures(flags, agentRef) {
|
|
19
|
-
|
|
20
|
-
activatedFeatures[agentIdentifier] ??= {};
|
|
21
|
-
if (!flags || typeof flags !== 'object') return;
|
|
22
|
-
if (sentIds.has(agentIdentifier)) return;
|
|
15
|
+
if (!flags || typeof flags !== 'object' || !!agentRef.runtime.activatedFeatures) return;
|
|
23
16
|
agentRef.ee.emit('rumresp', [flags]);
|
|
24
|
-
activatedFeatures
|
|
25
|
-
sentIds.add(agentIdentifier);
|
|
17
|
+
agentRef.runtime.activatedFeatures = flags;
|
|
26
18
|
|
|
27
19
|
// let any window level subscribers know that the agent is running, per install docs
|
|
28
20
|
dispatchGlobalEvent({
|
|
29
|
-
agentIdentifier,
|
|
30
21
|
loaded: true,
|
|
31
22
|
drained: true,
|
|
32
23
|
type: 'lifecycle',
|
|
@@ -6,14 +6,22 @@
|
|
|
6
6
|
import { globalScope } from '../constants/runtime';
|
|
7
7
|
import { now } from '../timing/now';
|
|
8
8
|
import { cleanURL } from '../url/clean-url';
|
|
9
|
-
import { chrome, gecko } from './browser-stack-matchers';
|
|
9
|
+
import { chrome, chromeEval, gecko } from './browser-stack-matchers';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @typedef {import('./register-api-types').RegisterAPITimings} RegisterAPITimings
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
/** export for testing purposes */
|
|
16
|
+
export let thisFile;
|
|
17
|
+
try {
|
|
18
|
+
thisFile = extractUrlsFromStack(getDeepStackTrace()).at(0);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
thisFile = extractUrlsFromStack(err).at(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
/** @type {(entry: PerformanceEntry) => boolean} - A shared function to determine if a performance entry is a valid script or link resource for evaluation */
|
|
16
|
-
const validEntryCriteria = entry => entry.initiatorType === 'script' || entry.initiatorType
|
|
24
|
+
const validEntryCriteria = entry => entry.initiatorType === 'script' || ['link', 'fetch'].includes(entry.initiatorType) && entry.name.endsWith('.js');
|
|
17
25
|
|
|
18
26
|
/** @type {Set<PerformanceResourceTiming>} - A set of resource timing objects that are "valid" -- see "validEntryCriteria" */
|
|
19
27
|
const scripts = new Set();
|
|
@@ -57,9 +65,15 @@ function extractUrlsFromStack(stack) {
|
|
|
57
65
|
const lines = stack.split('\n');
|
|
58
66
|
for (const line of lines) {
|
|
59
67
|
// Try gecko format first, then chrome
|
|
60
|
-
const parts = line.match(gecko) || line.match(chrome);
|
|
68
|
+
const parts = line.match(gecko) || line.match(chrome) || line.match(chromeEval);
|
|
61
69
|
if (parts && parts[2]) {
|
|
62
70
|
urls.add(cleanURL(parts[2]));
|
|
71
|
+
} else {
|
|
72
|
+
// Fallback: match URLs using a generic .js pattern (non-greedy to handle ports and query params)
|
|
73
|
+
const fallbackMatch = line.match(/\(([^)]+\.js):\d+:\d+\)/) || line.match(/^\s+at\s+([^\s(]+\.js):\d+:\d+/);
|
|
74
|
+
if (fallbackMatch && fallbackMatch[1]) {
|
|
75
|
+
urls.add(cleanURL(fallbackMatch[1]));
|
|
76
|
+
}
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
return [...urls];
|
|
@@ -116,9 +130,11 @@ export function findScriptTimings() {
|
|
|
116
130
|
};
|
|
117
131
|
const stack = getDeepStackTrace();
|
|
118
132
|
if (!stack) return timings;
|
|
119
|
-
const navUrl = globalScope.performance?.getEntriesByType('navigation')?.
|
|
133
|
+
const navUrl = globalScope.performance?.getEntriesByType('navigation')?.[0]?.name || '';
|
|
120
134
|
try {
|
|
121
|
-
const
|
|
135
|
+
const urls = extractUrlsFromStack(stack);
|
|
136
|
+
/** if there is exactly one url, this means the MFE script is running in the same file as the agent. Otherwise, lets strip away the known agent file from any other file lines */
|
|
137
|
+
const mfeScriptUrl = (urls.length > 1 ? urls.filter(line => !thisFile.endsWith(line) && !line.endsWith(thisFile)) : urls).at(0);
|
|
122
138
|
if (!mfeScriptUrl) return timings;
|
|
123
139
|
if (navUrl.includes(mfeScriptUrl)) {
|
|
124
140
|
// this means the stack is indicating that the registration came from an inline script or eval, so we won't find a matching script resource - return early with just the URL
|
|
@@ -138,6 +154,7 @@ export function findScriptTimings() {
|
|
|
138
154
|
if (wasPreloaded(mfeScriptUrl)) {
|
|
139
155
|
timings.asset = mfeScriptUrl;
|
|
140
156
|
timings.type = 'preload';
|
|
157
|
+
|
|
141
158
|
// wait for a late PO callback... The timings object can be mutated after the fact since we return a pointer and not a cloned object
|
|
142
159
|
poSubscribers.push({
|
|
143
160
|
addedAt: now(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -7,17 +7,32 @@ import { ee } from '../event-emitter/contextual-ee';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Returns a function for use as a replacer parameter in JSON.stringify() to handle circular references.
|
|
10
|
+
* Uses an array to track the current ancestor chain, allowing the same object to appear
|
|
11
|
+
* multiple times in the structure as long as it's not a circular reference.
|
|
10
12
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value MDN - Cyclical object value}
|
|
11
|
-
* @returns {Function} A function that filters out
|
|
13
|
+
* @returns {Function} A function that filters out circular references while allowing duplicate references.
|
|
12
14
|
*/
|
|
13
15
|
const getCircularReplacer = () => {
|
|
14
|
-
const
|
|
15
|
-
return (key, value)
|
|
16
|
-
if (
|
|
17
|
-
|
|
16
|
+
const stack = [];
|
|
17
|
+
return function (key, value) {
|
|
18
|
+
if (stack.length > 0) {
|
|
19
|
+
// Find where we are in the stack
|
|
20
|
+
const thisPos = stack.indexOf(this);
|
|
21
|
+
if (~thisPos) {
|
|
22
|
+
// We're still in the stack, trim it
|
|
23
|
+
stack.splice(thisPos + 1);
|
|
24
|
+
} else {
|
|
25
|
+
// We're not in the stack, add ourselves
|
|
26
|
+
stack.push(this);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check if value is in the current ancestor chain
|
|
30
|
+
if (~stack.indexOf(value)) {
|
|
18
31
|
return;
|
|
19
32
|
}
|
|
20
|
-
|
|
33
|
+
} else {
|
|
34
|
+
// First call, initialize with root
|
|
35
|
+
stack.push(value);
|
|
21
36
|
}
|
|
22
37
|
return value;
|
|
23
38
|
};
|
|
@@ -30,7 +30,6 @@ if (isBrowserScope) {
|
|
|
30
30
|
if (lcpEntry.element?.tagName) attrs.elTag = lcpEntry.element.tagName;
|
|
31
31
|
}
|
|
32
32
|
if (attribution.element) attrs.element = attribution.element;
|
|
33
|
-
if (attribution.navigationEntry) attrs.pageUrl = cleanURL(attribution.navigationEntry.name); // used to ensure the LCP gets the correct URL at harvest time if a soft nav has occurred before page load
|
|
34
33
|
if (attribution.url) attrs.elUrl = cleanURL(attribution.url);
|
|
35
34
|
largestContentfulPaint.update({
|
|
36
35
|
value,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { globalScope, isBrowserScope, originTime,
|
|
5
|
+
import { globalScope, isBrowserScope, originTime, getNavigationEntry } from '../constants/runtime';
|
|
6
6
|
import { onWindowLoad } from '../window/load';
|
|
7
7
|
import { VITAL_NAMES } from './constants';
|
|
8
8
|
import { VitalMetric } from './vital-metric';
|
|
@@ -11,8 +11,9 @@ if (isBrowserScope) {
|
|
|
11
11
|
const perf = globalScope.performance;
|
|
12
12
|
const handler = () => {
|
|
13
13
|
if (!loadTime.isValid && perf) {
|
|
14
|
+
const navEntry = getNavigationEntry();
|
|
14
15
|
loadTime.update({
|
|
15
|
-
value:
|
|
16
|
+
value: navEntry ? navEntry.loadEventEnd : perf.timing?.loadEventEnd - originTime
|
|
16
17
|
});
|
|
17
18
|
}
|
|
18
19
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { globalScope, isBrowserScope, isiOS, originTime,
|
|
5
|
+
import { globalScope, isBrowserScope, isiOS, originTime, getNavigationEntry } from '../constants/runtime';
|
|
6
6
|
import { VITAL_NAMES } from './constants';
|
|
7
7
|
import { VitalMetric } from './vital-metric';
|
|
8
8
|
import { onTTFB } from 'web-vitals/attribution';
|
|
@@ -16,7 +16,7 @@ export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
|
|
|
16
16
|
* - cross-origin iframes specifically in firefox and safari
|
|
17
17
|
* - onTTFB relies on a truthy `responseStart` value, should ensure that exists before relying on it (seen to be falsy in certain Electron.js cases for instance)
|
|
18
18
|
*/
|
|
19
|
-
if (isBrowserScope &&
|
|
19
|
+
if (isBrowserScope && getNavigationEntry() && !isiOS && window === window.parent) {
|
|
20
20
|
onTTFB(({
|
|
21
21
|
value,
|
|
22
22
|
attribution
|
|
@@ -175,10 +175,6 @@ export class Aggregate extends AggregateBase {
|
|
|
175
175
|
if (!target) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
|
|
176
176
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
177
177
|
if (this.blocked) return;
|
|
178
|
-
if (err.__newrelic?.[this.agentIdentifier]) {
|
|
179
|
-
params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
|
|
180
|
-
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
|
|
181
|
-
}
|
|
182
178
|
if (err.__newrelic?.socketId) {
|
|
183
179
|
customAttributes.socketId = err.__newrelic.socketId;
|
|
184
180
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { globalScope, isBrowserScope, originTime,
|
|
5
|
+
import { globalScope, isBrowserScope, originTime, getNavigationEntry } from '../../../common/constants/runtime';
|
|
6
6
|
import { addPT, addPN } from '../../../common/timing/nav-timing';
|
|
7
7
|
import { stringify } from '../../../common/util/stringify';
|
|
8
8
|
import { isValid } from '../../../common/config/info';
|
|
@@ -75,7 +75,7 @@ export class Aggregate extends AggregateBase {
|
|
|
75
75
|
us: info.user,
|
|
76
76
|
ac: info.account,
|
|
77
77
|
pr: info.product,
|
|
78
|
-
af: getActivatedFeaturesFlags(this.
|
|
78
|
+
af: getActivatedFeaturesFlags(this.agentRef).join(','),
|
|
79
79
|
...measures,
|
|
80
80
|
xx: info.extra,
|
|
81
81
|
ua: info.userAttributes,
|
|
@@ -90,9 +90,9 @@ export class Aggregate extends AggregateBase {
|
|
|
90
90
|
}
|
|
91
91
|
}, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
|
|
92
92
|
if (globalScope.performance) {
|
|
93
|
-
|
|
93
|
+
const navTimingEntry = getNavigationEntry();
|
|
94
|
+
if (navTimingEntry) {
|
|
94
95
|
// Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
|
|
95
|
-
const navTimingEntry = globalScope?.performance?.getEntriesByType('navigation')?.[0];
|
|
96
96
|
const perf = {
|
|
97
97
|
timing: addPT(originTime, navTimingEntry, {}),
|
|
98
98
|
navigation: addPN(navTimingEntry, {})
|
|
@@ -3,19 +3,17 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
6
|
-
import { gosNREUM } from '../../../common/window/nreum';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Get an array of flags required by downstream (NR UI) based on the features initialized in this agent
|
|
10
9
|
* (aka what is running on the page).
|
|
11
|
-
* @param {
|
|
10
|
+
* @param {Object} agentRef - the agent reference object
|
|
12
11
|
* @returns {String[]} Up to 5 short strings corresponding to ingest mapping of features.
|
|
13
12
|
*/
|
|
14
|
-
export function getActivatedFeaturesFlags(
|
|
13
|
+
export function getActivatedFeaturesFlags(agentRef) {
|
|
15
14
|
const flagArr = [];
|
|
16
|
-
const newrelic = gosNREUM();
|
|
17
15
|
try {
|
|
18
|
-
Object.keys(
|
|
16
|
+
Object.keys(agentRef.features || {}).forEach(featName => {
|
|
19
17
|
switch (featName) {
|
|
20
18
|
case FEATURE_NAMES.ajax:
|
|
21
19
|
flagArr.push('xhr');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { setupSetPageViewNameAPI } from '../../../loaders/api/setPageViewName';
|
|
@@ -14,17 +14,16 @@ export class Instrument extends InstrumentBase {
|
|
|
14
14
|
super(agentRef, CONSTANTS.FEATURE_NAME);
|
|
15
15
|
|
|
16
16
|
/** setup inspection events for window lifecycle */
|
|
17
|
-
this.setupInspectionEvents(
|
|
17
|
+
this.setupInspectionEvents();
|
|
18
18
|
|
|
19
19
|
/** feature specific APIs */
|
|
20
20
|
setupSetPageViewNameAPI(agentRef);
|
|
21
21
|
this.importAggregator(agentRef, () => import(/* webpackChunkName: "page_view_event-aggregate" */'../aggregate'));
|
|
22
22
|
}
|
|
23
|
-
setupInspectionEvents(
|
|
23
|
+
setupInspectionEvents() {
|
|
24
24
|
const dispatch = (evt, name) => {
|
|
25
25
|
if (!evt) return;
|
|
26
26
|
dispatchGlobalEvent({
|
|
27
|
-
agentIdentifier,
|
|
28
27
|
timeStamp: evt.timeStamp,
|
|
29
28
|
loaded: evt.target.readyState === 'complete',
|
|
30
29
|
type: 'window',
|
|
@@ -43,7 +42,6 @@ export class Instrument extends InstrumentBase {
|
|
|
43
42
|
});
|
|
44
43
|
this.ee.on(SESSION_EVENTS.UPDATE, (_, data) => {
|
|
45
44
|
dispatchGlobalEvent({
|
|
46
|
-
agentIdentifier,
|
|
47
45
|
type: 'lifecycle',
|
|
48
46
|
name: 'session',
|
|
49
47
|
data
|
|
@@ -16,10 +16,11 @@ import { interactionToNextPaint } from '../../../common/vitals/interaction-to-ne
|
|
|
16
16
|
import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint';
|
|
17
17
|
import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
|
|
18
18
|
import { VITAL_NAMES } from '../../../common/vitals/constants';
|
|
19
|
-
import { initiallyHidden } from '../../../common/constants/runtime';
|
|
19
|
+
import { initiallyHidden, getNavigationEntry, initialLocation } from '../../../common/constants/runtime';
|
|
20
20
|
import { eventOrigin } from '../../../common/util/event-origin';
|
|
21
21
|
import { loadTime } from '../../../common/vitals/load-time';
|
|
22
22
|
import { webdriverDetected } from '../../../common/util/webdriver-detection';
|
|
23
|
+
import { cleanURL } from '../../../common/url/clean-url';
|
|
23
24
|
export class Aggregate extends AggregateBase {
|
|
24
25
|
static featureName = FEATURE_NAME;
|
|
25
26
|
#handleVitalMetric = ({
|
|
@@ -78,6 +79,7 @@ export class Aggregate extends AggregateBase {
|
|
|
78
79
|
}
|
|
79
80
|
addTiming(name, value, attrs) {
|
|
80
81
|
attrs = attrs || {};
|
|
82
|
+
attrs.pageUrl = cleanURL(getNavigationEntry()?.name || initialLocation);
|
|
81
83
|
addConnectionAttributes(attrs); // network conditions may differ from the actual for VitalMetrics when they were captured
|
|
82
84
|
|
|
83
85
|
// If cls was set to another value by `onCLS`, then it's supported and is attached onto any timing but is omitted until such time.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
@@ -7,7 +7,7 @@ import { FEATURE_NAME } from '../constants';
|
|
|
7
7
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
8
8
|
import { TraceStorage } from './trace/storage';
|
|
9
9
|
import { obj as encodeObj } from '../../../common/url/encode';
|
|
10
|
-
import { globalScope,
|
|
10
|
+
import { globalScope, getNavigationEntry } from '../../../common/constants/runtime';
|
|
11
11
|
import { MODE, SESSION_EVENTS } from '../../../common/session/constants';
|
|
12
12
|
import { applyFnToProps } from '../../../common/util/traverse';
|
|
13
13
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
@@ -58,8 +58,9 @@ export class Aggregate extends AggregateBase {
|
|
|
58
58
|
// if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
|
|
59
59
|
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF) this.abort(2);
|
|
60
60
|
});
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const navEntry = getNavigationEntry();
|
|
62
|
+
if (navEntry) {
|
|
63
|
+
this.traceStorage.storeTiming(navEntry);
|
|
63
64
|
} else {
|
|
64
65
|
this.traceStorage.storeTiming(globalScope.performance?.timing, true);
|
|
65
66
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { drain } from '../../common/drain/drain';
|
|
6
|
-
import { ee } from '../../common/event-emitter/contextual-ee';
|
|
7
6
|
import { registerHandler } from '../../common/event-emitter/register-handler';
|
|
8
7
|
import { SessionEntity } from '../../common/session/session-entity';
|
|
9
8
|
import { LocalStorage } from '../../common/storage/local-storage.js';
|
|
@@ -20,7 +19,7 @@ export function setupAgentSession(agentRef) {
|
|
|
20
19
|
|
|
21
20
|
const sessionInit = agentRef.init.session;
|
|
22
21
|
agentRef.runtime.session = new SessionEntity({
|
|
23
|
-
|
|
22
|
+
agentRef,
|
|
24
23
|
key: DEFAULT_KEY,
|
|
25
24
|
storage: new LocalStorage(),
|
|
26
25
|
expiresMs: sessionInit?.expiresMs,
|
|
@@ -42,7 +41,7 @@ export function setupAgentSession(agentRef) {
|
|
|
42
41
|
|
|
43
42
|
/** track changes to the jsAttributes field over time for aiding with harvest mechanics */
|
|
44
43
|
agentRef.runtime.jsAttributesMetadata = trackObjectAttributeSize(agentRef.info, 'jsAttributes');
|
|
45
|
-
const sharedEE =
|
|
44
|
+
const sharedEE = agentRef.ee;
|
|
46
45
|
|
|
47
46
|
// any calls to newrelic.setCustomAttribute(<persisted>) will need to be added to:
|
|
48
47
|
// local info.jsAttributes {}
|
|
@@ -67,6 +66,6 @@ export function setupAgentSession(agentRef) {
|
|
|
67
66
|
consent: accept === undefined ? true : accept
|
|
68
67
|
});
|
|
69
68
|
}, 'session', sharedEE);
|
|
70
|
-
drain(agentRef
|
|
69
|
+
drain(agentRef, 'session');
|
|
71
70
|
return agentRef.runtime.session;
|
|
72
71
|
}
|
|
@@ -7,7 +7,6 @@ import { isValid } from '../../common/config/info';
|
|
|
7
7
|
import { configure } from '../../loaders/configure/configure';
|
|
8
8
|
import { gosCDN } from '../../common/window/nreum';
|
|
9
9
|
import { drain } from '../../common/drain/drain';
|
|
10
|
-
import { activatedFeatures } from '../../common/util/feature-flags';
|
|
11
10
|
import { Obfuscator } from '../../common/util/obfuscate';
|
|
12
11
|
import { FEATURE_NAMES } from '../../loaders/features/features';
|
|
13
12
|
import { Harvester } from '../../common/harvest/harvester';
|
|
@@ -23,8 +22,7 @@ export class AggregateBase extends FeatureBase {
|
|
|
23
22
|
* @param {string} featureName The name of the feature creating the instance.
|
|
24
23
|
*/
|
|
25
24
|
constructor(agentRef, featureName) {
|
|
26
|
-
super(agentRef
|
|
27
|
-
this.agentRef = agentRef;
|
|
25
|
+
super(agentRef, featureName);
|
|
28
26
|
this.checkConfiguration(agentRef);
|
|
29
27
|
this.doOnceForAllAggregate(agentRef);
|
|
30
28
|
|
|
@@ -108,8 +106,8 @@ export class AggregateBase extends FeatureBase {
|
|
|
108
106
|
*/
|
|
109
107
|
waitForFlags(flagNames = []) {
|
|
110
108
|
const flagsPromise = new Promise((resolve, reject) => {
|
|
111
|
-
if (
|
|
112
|
-
resolve(buildOutput(
|
|
109
|
+
if (this.agentRef.runtime?.activatedFeatures) {
|
|
110
|
+
resolve(buildOutput(this.agentRef.runtime.activatedFeatures));
|
|
113
111
|
} else {
|
|
114
112
|
this.ee.on('rumresp', (resp = {}) => {
|
|
115
113
|
resolve(buildOutput(resp));
|
|
@@ -133,7 +131,7 @@ export class AggregateBase extends FeatureBase {
|
|
|
133
131
|
* Stages the feature to be drained
|
|
134
132
|
*/
|
|
135
133
|
drain() {
|
|
136
|
-
drain(this.
|
|
134
|
+
drain(this.agentRef, this.featureName);
|
|
137
135
|
}
|
|
138
136
|
preHarvestChecks(opts) {
|
|
139
137
|
return !this.blocked && !this.ee.aborted;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { ee } from '../../common/event-emitter/contextual-ee';
|
|
6
5
|
import { deregisterDrain } from '../../common/drain/drain';
|
|
7
6
|
export class FeatureBase {
|
|
8
|
-
constructor(
|
|
9
|
-
/** @type {
|
|
10
|
-
this.
|
|
7
|
+
constructor(agentRef, featureName) {
|
|
8
|
+
/** @type {Object} */
|
|
9
|
+
this.agentRef = agentRef;
|
|
11
10
|
/** @type {import('../../common/event-emitter/contextual-ee').ee} */
|
|
12
|
-
this.ee = ee
|
|
11
|
+
this.ee = agentRef?.ee;
|
|
13
12
|
/** @type {string} */
|
|
14
13
|
this.featureName = featureName;
|
|
15
14
|
/**
|
|
@@ -20,6 +19,6 @@ export class FeatureBase {
|
|
|
20
19
|
this.blocked = false;
|
|
21
20
|
}
|
|
22
21
|
deregisterDrain() {
|
|
23
|
-
deregisterDrain(this.
|
|
22
|
+
deregisterDrain(this.agentRef, this.featureName);
|
|
24
23
|
}
|
|
25
24
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -28,12 +28,11 @@ import { handle } from '../../common/event-emitter/handle';
|
|
|
28
28
|
export class InstrumentBase extends FeatureBase {
|
|
29
29
|
/**
|
|
30
30
|
* Instantiate InstrumentBase.
|
|
31
|
-
* @param {
|
|
31
|
+
* @param {Object} agentRef - The agent reference object.
|
|
32
32
|
* @param {string} featureName - The name of the feature module (used to construct file path).
|
|
33
33
|
*/
|
|
34
34
|
constructor(agentRef, featureName) {
|
|
35
|
-
super(agentRef
|
|
36
|
-
this.agentRef = agentRef;
|
|
35
|
+
super(agentRef, featureName);
|
|
37
36
|
|
|
38
37
|
/** @type {Function | undefined} This should be set by any derived Instrument class if it has things to do when feature fails or is killed. */
|
|
39
38
|
this.abortHandler = undefined;
|
|
@@ -63,13 +62,13 @@ export class InstrumentBase extends FeatureBase {
|
|
|
63
62
|
this.ee.on('manual-start-all', single(() => {
|
|
64
63
|
// register the feature to drain only once the API has been called, it will drain when importAggregator finishes for all the features
|
|
65
64
|
// called by the api in that cycle
|
|
66
|
-
registerDrain(agentRef
|
|
65
|
+
registerDrain(agentRef, this.featureName);
|
|
67
66
|
resolve();
|
|
68
67
|
}));
|
|
69
68
|
});
|
|
70
69
|
} else {
|
|
71
70
|
/** if the feature requires opt-in (!auto-start), it will get registered once the api has been called */
|
|
72
|
-
registerDrain(agentRef
|
|
71
|
+
registerDrain(agentRef, featureName);
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -109,7 +108,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
109
108
|
*/
|
|
110
109
|
try {
|
|
111
110
|
if (!this.#shouldImportAgg(this.featureName, session, agentRef.init)) {
|
|
112
|
-
drain(this.
|
|
111
|
+
drain(this.agentRef, this.featureName);
|
|
113
112
|
this.loadedSuccessfully(false); // aggregate module isn't loaded at all
|
|
114
113
|
return;
|
|
115
114
|
}
|
|
@@ -123,7 +122,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
123
122
|
warn(34, e);
|
|
124
123
|
this.abortHandler?.(); // undo any important alterations made to the page
|
|
125
124
|
// not supported yet but nice to do: "abort" this agent's EE for this feature specifically
|
|
126
|
-
drain(this.
|
|
125
|
+
drain(this.agentRef, this.featureName, true);
|
|
127
126
|
this.loadedSuccessfully(false);
|
|
128
127
|
if (this.ee) this.ee.abort();
|
|
129
128
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* @typedef {Object} RegisterAPIConstructor
|
|
23
|
-
* @property {string
|
|
23
|
+
* @property {string} id - The unique id for the registered entity. This will be assigned to any synthesized entities.
|
|
24
24
|
* @property {string} name - The readable name for the registered entity. This will be assigned to any synthesized entities.
|
|
25
25
|
* @property {{[key: string]: any}} [tags] - The tags for the registered entity as key-value pairs. This will be assigned to any synthesized entities. Tags are converted to source.* attributes (e.g., {environment: 'production'} becomes source.environment: 'production').
|
|
26
26
|
* @property {boolean} [isolated] - When true, each registration creates an isolated instance. When false, multiple registrations with the same id and isolated: false will share a single instance, including all custom attributes, ids, names, and metadata. Calling deregister on a shared instance will deregister it for all entities using the instance. Defaults to true.
|
|
@@ -88,7 +88,7 @@ function register(agentRef, target, parent) {
|
|
|
88
88
|
invalidApiResponse = warning;
|
|
89
89
|
};
|
|
90
90
|
function hasValidValue(val) {
|
|
91
|
-
return typeof val === 'string' && !!val.trim() && val.trim().length < 501
|
|
91
|
+
return typeof val === 'string' && !!val.trim() && val.trim().length < 501;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** primary cases that can block the register API from working at init time */
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { dispatchGlobalEvent } from '../../common/dispatch/global-event';
|
|
6
6
|
import { handle } from '../../common/event-emitter/handle';
|
|
7
7
|
import { now } from '../../common/timing/now';
|
|
8
8
|
import { warn } from '../../common/util/console';
|
|
9
|
-
import { activatedFeatures } from '../../common/util/feature-flags';
|
|
10
9
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
|
|
11
10
|
import { AgentBase } from '../agent-base';
|
|
12
11
|
import { FEATURE_NAMES } from '../features/features';
|
|
@@ -28,8 +27,7 @@ export function setupAPI(name, fn, agent, obj) {
|
|
|
28
27
|
api[name] = function () {
|
|
29
28
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/' + name + '/called'], undefined, FEATURE_NAMES.metrics, agent.ee);
|
|
30
29
|
dispatchGlobalEvent({
|
|
31
|
-
|
|
32
|
-
drained: !!activatedFeatures?.[agent.agentIdentifier],
|
|
30
|
+
drained: !!agent.runtime?.activatedFeatures,
|
|
33
31
|
type: 'data',
|
|
34
32
|
name: 'api',
|
|
35
33
|
feature: prefix + name,
|