@newrelic/browser-agent 1.290.1 → 1.291.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/harvest/harvester.js +0 -7
- package/dist/cjs/common/session/session-entity.js +5 -5
- package/dist/cjs/common/util/console.js +12 -0
- package/dist/cjs/features/generic_events/aggregate/index.js +18 -2
- package/dist/cjs/features/generic_events/instrument/index.js +2 -0
- package/dist/cjs/features/session_replay/aggregate/index.js +3 -0
- package/dist/cjs/features/session_replay/shared/recorder.js +0 -4
- package/dist/cjs/features/session_trace/aggregate/index.js +7 -3
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +36 -23
- package/dist/cjs/loaders/api/addToTrace.js +8 -0
- package/dist/cjs/loaders/api/constants.js +3 -2
- package/dist/cjs/loaders/api/measure.js +60 -0
- package/dist/cjs/loaders/api-base.js +11 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/harvest/harvester.js +0 -7
- package/dist/esm/common/session/session-entity.js +5 -5
- package/dist/esm/common/util/console.js +13 -0
- package/dist/esm/features/generic_events/aggregate/index.js +18 -2
- package/dist/esm/features/generic_events/instrument/index.js +2 -0
- package/dist/esm/features/session_replay/aggregate/index.js +3 -0
- package/dist/esm/features/session_replay/shared/recorder.js +0 -4
- package/dist/esm/features/session_trace/aggregate/index.js +7 -3
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +36 -23
- package/dist/esm/loaders/api/addToTrace.js +8 -0
- package/dist/esm/loaders/api/constants.js +2 -1
- package/dist/esm/loaders/api/measure.js +53 -0
- package/dist/esm/loaders/api-base.js +12 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts +0 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/util/console.d.ts +0 -4
- package/dist/types/common/util/console.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +1 -3
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/loaders/api/addToTrace.d.ts.map +1 -1
- package/dist/types/loaders/api/constants.d.ts +1 -0
- package/dist/types/loaders/api/constants.d.ts.map +1 -1
- package/dist/types/loaders/api/measure.d.ts +2 -0
- package/dist/types/loaders/api/measure.d.ts.map +1 -0
- package/dist/types/loaders/api-base.d.ts +13 -0
- package/dist/types/loaders/api-base.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/harvest/harvester.js +0 -5
- package/src/common/session/session-entity.js +5 -6
- package/src/common/util/console.js +13 -0
- package/src/features/generic_events/aggregate/index.js +17 -2
- package/src/features/generic_events/instrument/index.js +2 -0
- package/src/features/session_replay/aggregate/index.js +4 -0
- package/src/features/session_replay/shared/recorder.js +0 -4
- package/src/features/session_trace/aggregate/index.js +7 -3
- package/src/features/session_trace/aggregate/trace/storage.js +37 -23
- package/src/loaders/api/addToTrace.js +6 -0
- package/src/loaders/api/constants.js +1 -0
- package/src/loaders/api/measure.js +53 -0
- package/src/loaders/api-base.js +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.291.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.291.0...v1.291.1) (2025-06-06)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Add safeguards for addToTrace ([#1490](https://github.com/newrelic/newrelic-browser-agent/issues/1490)) ([e94a36e](https://github.com/newrelic/newrelic-browser-agent/commit/e94a36efaf37f69dfdb8134bc27ee0df0a734e83))
|
|
12
|
+
* Clean BrowserPerformance entryName for resources ([#1493](https://github.com/newrelic/newrelic-browser-agent/issues/1493)) ([09ff0ad](https://github.com/newrelic/newrelic-browser-agent/commit/09ff0adb27c12e99c02c81462aeb921af8686ce8))
|
|
13
|
+
* Prevent ST from holding onto Event refs in memory when aborted ([#1491](https://github.com/newrelic/newrelic-browser-agent/issues/1491)) ([a1d15cb](https://github.com/newrelic/newrelic-browser-agent/commit/a1d15cb9d972a2fa421a648b83e150489799d437))
|
|
14
|
+
|
|
15
|
+
## [1.291.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.290.1...v1.291.0) (2025-05-30)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* Create the measure API ([#1476](https://github.com/newrelic/newrelic-browser-agent/issues/1476)) ([f944b76](https://github.com/newrelic/newrelic-browser-agent/commit/f944b76c2137e6d75e47e692ded4ba5f04bb4b6d))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* Fix race between end of session and features aborting ([#1487](https://github.com/newrelic/newrelic-browser-agent/issues/1487)) ([531f8d4](https://github.com/newrelic/newrelic-browser-agent/commit/531f8d4228f6c7ead8e2342c8a8dd25c651e9ec6))
|
|
26
|
+
* Harvest first session trace payload immediately ([#1483](https://github.com/newrelic/newrelic-browser-agent/issues/1483)) ([50f4ace](https://github.com/newrelic/newrelic-browser-agent/commit/50f4acea3e27c6b5e65d02ffb46af02b351e4500))
|
|
27
|
+
|
|
6
28
|
## [1.290.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.290.0...v1.290.1) (2025-05-21)
|
|
7
29
|
|
|
8
30
|
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.291.1";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.291.1";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -10,7 +10,6 @@ var _env = require("../constants/env.npm");
|
|
|
10
10
|
var _runtime = require("../constants/runtime");
|
|
11
11
|
var _handle = require("../event-emitter/handle");
|
|
12
12
|
var _eventListenerOpts = require("../event-listener/event-listener-opts");
|
|
13
|
-
var _constants2 = require("../session/constants");
|
|
14
13
|
var _now = require("../timing/now");
|
|
15
14
|
var _eol = require("../unload/eol");
|
|
16
15
|
var _cleanUrl = require("../url/clean-url");
|
|
@@ -43,12 +42,6 @@ class Harvester {
|
|
|
43
42
|
}));
|
|
44
43
|
/* This callback should run in bubble phase, so that that CWV api, like "onLCP", is called before the final harvest so that emitted timings are part of last outgoing. */
|
|
45
44
|
}, false);
|
|
46
|
-
|
|
47
|
-
/* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
|
|
48
|
-
Since session-reset generates a new session ID and the ID is grabbed at send-time, any delays or retries would cause the payload to be sent under the wrong session ID. */
|
|
49
|
-
agentRef.ee.on(_constants2.SESSION_EVENTS.RESET, () => this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
|
|
50
|
-
forceNoRetry: true
|
|
51
|
-
})));
|
|
52
45
|
}
|
|
53
46
|
startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
|
|
54
47
|
if (this.#started) return;
|
|
@@ -240,7 +240,10 @@ class SessionEntity {
|
|
|
240
240
|
// * stop recording (stn and sr)...
|
|
241
241
|
// * delete the session and start over
|
|
242
242
|
try {
|
|
243
|
-
if (this.initialized)
|
|
243
|
+
if (this.initialized) {
|
|
244
|
+
this.ee.emit(_constants.SESSION_EVENTS.RESET);
|
|
245
|
+
this.state.numOfResets++;
|
|
246
|
+
}
|
|
244
247
|
this.storage.remove(this.lookupKey);
|
|
245
248
|
this.inactiveTimer?.abort?.();
|
|
246
249
|
this.expiresTimer?.clear?.();
|
|
@@ -251,7 +254,7 @@ class SessionEntity {
|
|
|
251
254
|
storage: this.storage,
|
|
252
255
|
expiresMs: this.expiresMs,
|
|
253
256
|
inactiveMs: this.inactiveMs,
|
|
254
|
-
numOfResets:
|
|
257
|
+
numOfResets: this.state.numOfResets
|
|
255
258
|
});
|
|
256
259
|
return this.read();
|
|
257
260
|
} catch (e) {
|
|
@@ -270,9 +273,6 @@ class SessionEntity {
|
|
|
270
273
|
inactiveAt: this.getFutureTimestamp(this.inactiveMs)
|
|
271
274
|
});
|
|
272
275
|
}
|
|
273
|
-
isAfterSessionExpiry(timestamp) {
|
|
274
|
-
return this.state.numOfResets > 0 || typeof timestamp === 'number' && typeof this.state.expiresAt === 'number' && timestamp >= this.state.expiresAt;
|
|
275
|
-
}
|
|
276
276
|
|
|
277
277
|
/**
|
|
278
278
|
* @param {number} timestamp
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.warn = warn;
|
|
7
|
+
var _globalEvent = require("../dispatch/global-event");
|
|
7
8
|
/**
|
|
8
9
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
9
10
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -20,4 +21,15 @@ exports.warn = warn;
|
|
|
20
21
|
function warn(code, secondary) {
|
|
21
22
|
if (typeof console.debug !== 'function') return;
|
|
22
23
|
console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(code), secondary);
|
|
24
|
+
(0, _globalEvent.dispatchGlobalEvent)({
|
|
25
|
+
agentIdentifier: null,
|
|
26
|
+
drained: null,
|
|
27
|
+
type: 'data',
|
|
28
|
+
name: 'warn',
|
|
29
|
+
feature: 'warn',
|
|
30
|
+
data: {
|
|
31
|
+
code,
|
|
32
|
+
secondary
|
|
33
|
+
}
|
|
34
|
+
});
|
|
23
35
|
}
|
|
@@ -148,7 +148,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
148
148
|
...detailObj,
|
|
149
149
|
eventType: 'BrowserPerformance',
|
|
150
150
|
timestamp: this.toEpoch(entry.startTime),
|
|
151
|
-
entryName:
|
|
151
|
+
entryName: entry.name,
|
|
152
152
|
entryDuration: entry.duration,
|
|
153
153
|
entryType: type
|
|
154
154
|
});
|
|
@@ -214,7 +214,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
214
214
|
...entryObject,
|
|
215
215
|
eventType: 'BrowserPerformance',
|
|
216
216
|
timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entryObject.startTime)),
|
|
217
|
-
entryName: name,
|
|
217
|
+
entryName: (0, _cleanUrl.cleanURL)(name),
|
|
218
218
|
entryDuration: duration,
|
|
219
219
|
firstParty
|
|
220
220
|
};
|
|
@@ -224,6 +224,22 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
224
224
|
}
|
|
225
225
|
}, this.featureName, this.ee);
|
|
226
226
|
}
|
|
227
|
+
(0, _registerHandler.registerHandler)('api-measure', (args, n) => {
|
|
228
|
+
const {
|
|
229
|
+
start,
|
|
230
|
+
duration,
|
|
231
|
+
customAttributes
|
|
232
|
+
} = args;
|
|
233
|
+
const event = {
|
|
234
|
+
...customAttributes,
|
|
235
|
+
eventType: 'BrowserPerformance',
|
|
236
|
+
timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(start)),
|
|
237
|
+
entryName: n,
|
|
238
|
+
entryDuration: duration,
|
|
239
|
+
entryType: 'measure'
|
|
240
|
+
};
|
|
241
|
+
this.addEvent(event);
|
|
242
|
+
}, this.featureName, this.ee);
|
|
227
243
|
agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
228
244
|
this.drain();
|
|
229
245
|
});
|
|
@@ -12,6 +12,7 @@ var _addPageAction = require("../../../loaders/api/addPageAction");
|
|
|
12
12
|
var _finished = require("../../../loaders/api/finished");
|
|
13
13
|
var _recordCustomEvent = require("../../../loaders/api/recordCustomEvent");
|
|
14
14
|
var _register = require("../../../loaders/api/register");
|
|
15
|
+
var _measure = require("../../../loaders/api/measure");
|
|
15
16
|
var _instrumentBase = require("../../utils/instrument-base");
|
|
16
17
|
var _constants = require("../constants");
|
|
17
18
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
@@ -31,6 +32,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
31
32
|
(0, _recordCustomEvent.setupRecordCustomEventAPI)(agentRef);
|
|
32
33
|
(0, _finished.setupFinishedAPI)(agentRef);
|
|
33
34
|
(0, _register.setupRegisterAPI)(agentRef);
|
|
35
|
+
(0, _measure.setupMeasureAPI)(agentRef);
|
|
34
36
|
if (_runtime.isBrowserScope) {
|
|
35
37
|
if (agentRef.init.user_actions.enabled) {
|
|
36
38
|
_constants.OBSERVED_EVENTS.forEach(eventType => (0, _eventListenerOpts.windowAddEventListener)(eventType, evt => (0, _handle.handle)('ua', [evt], undefined, this.featureName, this.ee), true));
|
|
@@ -281,6 +281,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
281
281
|
this.recorder.clearBuffer();
|
|
282
282
|
if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
283
283
|
payloadOutput.payload = payload;
|
|
284
|
+
if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
|
|
285
|
+
(0, _console.warn)(59, JSON.stringify(this.agentRef.runtime.session.state));
|
|
286
|
+
}
|
|
284
287
|
return [payloadOutput];
|
|
285
288
|
}
|
|
286
289
|
getCorrectedTimestamp(node) {
|
|
@@ -168,10 +168,6 @@ class Recorder {
|
|
|
168
168
|
/** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
|
|
169
169
|
store(event, isCheckout) {
|
|
170
170
|
if (!event) return;
|
|
171
|
-
if (this.parent.agentRef.runtime.session?.isAfterSessionExpiry(event.timestamp)) {
|
|
172
|
-
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['Session/Expired/SessionReplay/Seen'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
171
|
if (!(this.parent instanceof _aggregateBase.AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
|
|
176
172
|
if (this.parent.blocked) return;
|
|
177
173
|
if (this.parent.timeKeeper?.ready && !event.__newrelic) {
|
|
@@ -13,6 +13,7 @@ var _runtime = require("../../../common/constants/runtime");
|
|
|
13
13
|
var _constants2 = require("../../../common/session/constants");
|
|
14
14
|
var _traverse = require("../../../common/util/traverse");
|
|
15
15
|
var _cleanUrl = require("../../../common/url/clean-url");
|
|
16
|
+
var _console = require("../../../common/util/console");
|
|
16
17
|
/**
|
|
17
18
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
18
19
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -85,7 +86,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
85
86
|
(0, _registerHandler.registerHandler)('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee);
|
|
86
87
|
(0, _registerHandler.registerHandler)('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee);
|
|
87
88
|
(0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee);
|
|
88
|
-
(0, _registerHandler.registerHandler)('bstApi', (...args) => this.events.
|
|
89
|
+
(0, _registerHandler.registerHandler)('bstApi', (...args) => this.events.storeNode(...args), this.featureName, this.ee);
|
|
89
90
|
(0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee);
|
|
90
91
|
(0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee);
|
|
91
92
|
if (this.mode !== _constants2.MODE.FULL) {
|
|
@@ -98,9 +99,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
98
99
|
sessionTraceMode: this.mode
|
|
99
100
|
});
|
|
100
101
|
this.drain();
|
|
102
|
+
/** try to harvest immediately. This will not send if the trace is not running in FULL mode due to the pre-harvest checks. */
|
|
103
|
+
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
101
104
|
}
|
|
102
105
|
preHarvestChecks() {
|
|
103
|
-
if (this.mode !== _constants2.MODE.FULL) return; // only allow harvest if running in full mode
|
|
106
|
+
if (this.blocked || this.mode !== _constants2.MODE.FULL) return; // only allow harvest if running in full mode
|
|
104
107
|
if (!this.timeKeeper?.ready) return; // this should likely never happen, but just to be safe, we should never harvest if we cant correct time
|
|
105
108
|
if (!this.agentRef.runtime.session) return; // session entity is required for trace to run and continue running
|
|
106
109
|
if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
|
|
@@ -194,7 +197,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
/** Stop running for the remainder of the page lifecycle */
|
|
197
|
-
abort() {
|
|
200
|
+
abort(code) {
|
|
201
|
+
(0, _console.warn)(60, code);
|
|
198
202
|
this.blocked = true;
|
|
199
203
|
this.mode = _constants2.MODE.OFF;
|
|
200
204
|
this.agentRef.runtime.session.write({
|
|
@@ -53,23 +53,19 @@ class TraceStorage {
|
|
|
53
53
|
constructor(parent) {
|
|
54
54
|
this.parent = parent;
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Central function called by all the other store__ & addToTrace API to append a trace node. */
|
|
61
|
-
storeSTN(stn) {
|
|
62
|
-
if (this.parent.blocked) return;
|
|
56
|
+
#canStoreNewNode() {
|
|
57
|
+
if (this.parent.blocked) return false;
|
|
63
58
|
if (this.nodeCount >= _constants2.MAX_NODES_PER_HARVEST) {
|
|
64
59
|
// limit the amount of pending data awaiting next harvest
|
|
65
|
-
if (this.parent.mode !== _constants.MODE.ERROR) return;
|
|
60
|
+
if (this.parent.mode !== _constants.MODE.ERROR) return false;
|
|
66
61
|
const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
|
|
67
|
-
if (openedSpace === 0) return;
|
|
68
|
-
}
|
|
69
|
-
if (this.isAfterSessionExpiry(stn.s)) {
|
|
70
|
-
this.parent.reportSupportabilityMetric('Session/Expired/SessionTrace/Seen');
|
|
71
|
-
return;
|
|
62
|
+
if (openedSpace === 0) return false;
|
|
72
63
|
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Central internal function called by all the other store__ & addToTrace API to append a trace node. They MUST all have checked #canStoreNewNode before calling this func!! */
|
|
68
|
+
#storeSTN(stn) {
|
|
73
69
|
if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
|
|
74
70
|
if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
|
|
75
71
|
if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
|
|
@@ -149,6 +145,10 @@ class TraceStorage {
|
|
|
149
145
|
return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
|
|
150
146
|
}
|
|
151
147
|
}
|
|
148
|
+
storeNode(node) {
|
|
149
|
+
if (!this.#canStoreNewNode()) return;
|
|
150
|
+
this.#storeSTN(node);
|
|
151
|
+
}
|
|
152
152
|
processPVT(name, value, attrs) {
|
|
153
153
|
this.storeTiming({
|
|
154
154
|
[name]: value
|
|
@@ -172,13 +172,16 @@ class TraceStorage {
|
|
|
172
172
|
if (this.parent.timeKeeper && this.parent.timeKeeper.ready && isAbsoluteTimestamp) {
|
|
173
173
|
val = this.parent.timeKeeper.convertAbsoluteTimestamp(Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val)));
|
|
174
174
|
}
|
|
175
|
-
this
|
|
175
|
+
if (!this.#canStoreNewNode()) return; // at any point when no new nodes can be stored, there's no point in processing the rest of the timing entries
|
|
176
|
+
this.#storeSTN(new _node.TraceNode(key, val, val, 'document', 'timing'));
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
// Tracks the events and their listener's duration on objects wrapped by wrap-events.
|
|
180
181
|
storeEvent(currentEvent, target, start, end) {
|
|
181
182
|
if (this.shouldIgnoreEvent(currentEvent, target)) return;
|
|
183
|
+
if (!this.#canStoreNewNode()) return; // need to check if adding node will succeed BEFORE storing event ref below (*cli Jun'25 - addressing memory leak in aborted ST issue #NR-420780)
|
|
184
|
+
|
|
182
185
|
if (this.prevStoredEvents.has(currentEvent)) return; // prevent multiple listeners of an event from creating duplicate trace nodes per occurrence. Cleared every harvest. near-zero chance for re-duplication after clearing per harvest since the timestamps of the event are considered for uniqueness.
|
|
183
186
|
this.prevStoredEvents.add(currentEvent);
|
|
184
187
|
const evt = new _node.TraceNode(this.evtName(currentEvent.type), start, end, undefined, 'event');
|
|
@@ -189,7 +192,7 @@ class TraceStorage {
|
|
|
189
192
|
} catch (e) {
|
|
190
193
|
evt.o = (0, _eventOrigin.eventOrigin)(null, target, this.parent.ee);
|
|
191
194
|
}
|
|
192
|
-
this
|
|
195
|
+
this.#storeSTN(evt);
|
|
193
196
|
}
|
|
194
197
|
shouldIgnoreEvent(event, target) {
|
|
195
198
|
if (event.type in ignoredEvents.global) return true;
|
|
@@ -225,14 +228,17 @@ class TraceStorage {
|
|
|
225
228
|
|
|
226
229
|
// Tracks when the window history API specified by wrap-history is used.
|
|
227
230
|
storeHist(path, old, time) {
|
|
228
|
-
this
|
|
231
|
+
if (!this.#canStoreNewNode()) return;
|
|
232
|
+
this.#storeSTN(new _node.TraceNode('history.pushState', time, time, path, old));
|
|
229
233
|
}
|
|
230
234
|
#laststart = 0;
|
|
231
235
|
// Processes all the PerformanceResourceTiming entries captured (by observer).
|
|
232
236
|
storeResources(resources) {
|
|
233
237
|
if (!resources || resources.length === 0) return;
|
|
234
|
-
resources.
|
|
235
|
-
|
|
238
|
+
for (let i = 0; i < resources.length; i++) {
|
|
239
|
+
const currentResource = resources[i];
|
|
240
|
+
if ((currentResource.fetchStart | 0) <= this.#laststart) continue; // don't recollect already-seen resources
|
|
241
|
+
if (!this.#canStoreNewNode()) break; // stop processing if we can't store any more resource nodes anyways
|
|
236
242
|
|
|
237
243
|
const {
|
|
238
244
|
initiatorType,
|
|
@@ -247,21 +253,23 @@ class TraceStorage {
|
|
|
247
253
|
pathname
|
|
248
254
|
} = (0, _parseUrl.parseUrl)(currentResource.name);
|
|
249
255
|
const res = new _node.TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, "".concat(protocol, "://").concat(hostname, ":").concat(port).concat(pathname), entryType);
|
|
250
|
-
this
|
|
251
|
-
}
|
|
256
|
+
this.#storeSTN(res);
|
|
257
|
+
}
|
|
252
258
|
this.#laststart = resources[resources.length - 1].fetchStart | 0;
|
|
253
259
|
}
|
|
254
260
|
|
|
255
261
|
// JavascriptError (FEATURE) events pipes into ST here.
|
|
256
262
|
storeErrorAgg(type, name, params, metrics) {
|
|
257
263
|
if (type !== 'err') return; // internal errors are purposefully ignored
|
|
258
|
-
this
|
|
264
|
+
if (!this.#canStoreNewNode()) return;
|
|
265
|
+
this.#storeSTN(new _node.TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
|
|
259
266
|
}
|
|
260
267
|
|
|
261
268
|
// Ajax (FEATURE) events--XML & fetches--pipes into ST here.
|
|
262
269
|
storeXhrAgg(type, name, params, metrics) {
|
|
263
270
|
if (type !== 'xhr') return;
|
|
264
|
-
|
|
271
|
+
if (!this.#canStoreNewNode()) return;
|
|
272
|
+
this.#storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
|
|
265
273
|
}
|
|
266
274
|
|
|
267
275
|
/* Below are the interface expected & required of whatever storage is used across all features on an individual basis. This allows a common `.events` property on Trace shared with AggregateBase.
|
|
@@ -286,7 +294,12 @@ class TraceStorage {
|
|
|
286
294
|
this.latestTimeStamp = 0;
|
|
287
295
|
}
|
|
288
296
|
reloadSave() {
|
|
289
|
-
Object.values(this.#backupTrace)
|
|
297
|
+
for (const stnsArray of Object.values(this.#backupTrace)) {
|
|
298
|
+
for (const stn of stnsArray) {
|
|
299
|
+
if (!this.#canStoreNewNode()) return; // stop attempting to re-store nodes
|
|
300
|
+
this.#storeSTN(stn);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
290
303
|
}
|
|
291
304
|
clearSave() {
|
|
292
305
|
this.#backupTrace = undefined;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.setupAddToTraceAPI = setupAddToTraceAPI;
|
|
7
7
|
var _runtime = require("../../common/constants/runtime");
|
|
8
8
|
var _handle = require("../../common/event-emitter/handle");
|
|
9
|
+
var _console = require("../../common/util/console");
|
|
9
10
|
var _features = require("../features/features");
|
|
10
11
|
var _constants = require("./constants");
|
|
11
12
|
var _sharedHandlers = require("./sharedHandlers");
|
|
@@ -24,6 +25,13 @@ function setupAddToTraceAPI(agent) {
|
|
|
24
25
|
o: evt.origin || '',
|
|
25
26
|
t: 'api'
|
|
26
27
|
};
|
|
28
|
+
if (report.s < 0 || report.e < 0 || report.e < report.s) {
|
|
29
|
+
(0, _console.warn)(61, {
|
|
30
|
+
start: report.s,
|
|
31
|
+
end: report.e
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
27
35
|
(0, _handle.handle)('bstApi', [report], undefined, _features.FEATURE_NAMES.sessionTrace, agent.ee);
|
|
28
36
|
}, agent);
|
|
29
37
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.spaPrefix = exports.prefix = exports.WRAP_LOGGER = exports.START = exports.SET_USER_ID = exports.SET_PAGE_VIEW_NAME = exports.SET_ERROR_HANDLER = exports.SET_CUSTOM_ATTRIBUTE = exports.SET_CURRENT_ROUTE_NAME = exports.SET_APPLICATION_VERSION = exports.REGISTER = exports.RECORD_REPLAY = exports.RECORD_CUSTOM_EVENT = exports.PAUSE_REPLAY = exports.NOTICE_ERROR = exports.LOG = exports.INTERACTION = exports.FINISHED = exports.ADD_TO_TRACE = exports.ADD_RELEASE = exports.ADD_PAGE_ACTION = void 0;
|
|
6
|
+
exports.spaPrefix = exports.prefix = exports.WRAP_LOGGER = exports.START = exports.SET_USER_ID = exports.SET_PAGE_VIEW_NAME = exports.SET_ERROR_HANDLER = exports.SET_CUSTOM_ATTRIBUTE = exports.SET_CURRENT_ROUTE_NAME = exports.SET_APPLICATION_VERSION = exports.REGISTER = exports.RECORD_REPLAY = exports.RECORD_CUSTOM_EVENT = exports.PAUSE_REPLAY = exports.NOTICE_ERROR = exports.MEASURE = exports.LOG = exports.INTERACTION = exports.FINISHED = exports.ADD_TO_TRACE = exports.ADD_RELEASE = exports.ADD_PAGE_ACTION = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
9
9
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -28,4 +28,5 @@ const SET_ERROR_HANDLER = exports.SET_ERROR_HANDLER = 'setErrorHandler';
|
|
|
28
28
|
const SET_PAGE_VIEW_NAME = exports.SET_PAGE_VIEW_NAME = 'setPageViewName';
|
|
29
29
|
const SET_USER_ID = exports.SET_USER_ID = 'setUserId';
|
|
30
30
|
const START = exports.START = 'start';
|
|
31
|
-
const WRAP_LOGGER = exports.WRAP_LOGGER = 'wrapLogger';
|
|
31
|
+
const WRAP_LOGGER = exports.WRAP_LOGGER = 'wrapLogger';
|
|
32
|
+
const MEASURE = exports.MEASURE = 'measure';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.setupMeasureAPI = setupMeasureAPI;
|
|
7
|
+
var _handle = require("../../common/event-emitter/handle");
|
|
8
|
+
var _now = require("../../common/timing/now");
|
|
9
|
+
var _console = require("../../common/util/console");
|
|
10
|
+
var _features = require("../features/features");
|
|
11
|
+
var _constants = require("./constants");
|
|
12
|
+
var _sharedHandlers = require("./sharedHandlers");
|
|
13
|
+
/**
|
|
14
|
+
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function setupMeasureAPI(agent) {
|
|
19
|
+
(0, _sharedHandlers.setupAPI)(_constants.MEASURE, function (name, options) {
|
|
20
|
+
const n = (0, _now.now)();
|
|
21
|
+
const {
|
|
22
|
+
start,
|
|
23
|
+
end,
|
|
24
|
+
customAttributes
|
|
25
|
+
} = options || {};
|
|
26
|
+
const returnObj = {
|
|
27
|
+
customAttributes: customAttributes || {}
|
|
28
|
+
};
|
|
29
|
+
if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
|
|
30
|
+
(0, _console.warn)(57);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* getValueFromTiming - Helper function to extract a numeric value from a supplied option.
|
|
36
|
+
* @param {Number|PerformanceMark} [timing] The timing value
|
|
37
|
+
* @param {Number} [d] The default value to return if timing is invalid
|
|
38
|
+
* @returns {Number} The timing value or the default value
|
|
39
|
+
*/
|
|
40
|
+
const getValueFromTiming = (timing, d) => {
|
|
41
|
+
if (timing == null) return d;
|
|
42
|
+
if (typeof timing === 'number') return timing;
|
|
43
|
+
if (timing instanceof PerformanceMark) return timing.startTime;
|
|
44
|
+
return Number.NaN;
|
|
45
|
+
};
|
|
46
|
+
returnObj.start = getValueFromTiming(start, 0);
|
|
47
|
+
returnObj.end = getValueFromTiming(end, n);
|
|
48
|
+
if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
|
|
49
|
+
(0, _console.warn)(57);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
returnObj.duration = returnObj.end - returnObj.start;
|
|
53
|
+
if (returnObj.duration < 0) {
|
|
54
|
+
(0, _console.warn)(58);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
(0, _handle.handle)(_constants.prefix + _constants.MEASURE, [returnObj, name], undefined, _features.FEATURE_NAMES.genericEvents, agent.ee);
|
|
58
|
+
return returnObj;
|
|
59
|
+
}, agent);
|
|
60
|
+
}
|
|
@@ -220,5 +220,16 @@ class ApiBase {
|
|
|
220
220
|
wrapLogger(parent, functionName, options) {
|
|
221
221
|
return this.#callMethod(_constants.WRAP_LOGGER, parent, functionName, options);
|
|
222
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Measures a task that is recorded as a BrowserPerformance event.
|
|
226
|
+
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
|
|
227
|
+
* @param {string} name The name of the task
|
|
228
|
+
* @param {object?} options An object used to control the way the measure API operates
|
|
229
|
+
* @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
|
|
230
|
+
*/
|
|
231
|
+
measure(name, options) {
|
|
232
|
+
return this.#callMethod(_constants.MEASURE, name, options);
|
|
233
|
+
}
|
|
223
234
|
}
|
|
224
235
|
exports.ApiBase = ApiBase;
|
|
@@ -8,7 +8,6 @@ import { VERSION } from "../constants/env.npm";
|
|
|
8
8
|
import { globalScope, isWorkerScope } from '../constants/runtime';
|
|
9
9
|
import { handle } from '../event-emitter/handle';
|
|
10
10
|
import { eventListenerOpts } from '../event-listener/event-listener-opts';
|
|
11
|
-
import { SESSION_EVENTS } from '../session/constants';
|
|
12
11
|
import { now } from '../timing/now';
|
|
13
12
|
import { subscribeToEOL } from '../unload/eol';
|
|
14
13
|
import { cleanURL } from '../url/clean-url';
|
|
@@ -36,12 +35,6 @@ export class Harvester {
|
|
|
36
35
|
}));
|
|
37
36
|
/* This callback should run in bubble phase, so that that CWV api, like "onLCP", is called before the final harvest so that emitted timings are part of last outgoing. */
|
|
38
37
|
}, false);
|
|
39
|
-
|
|
40
|
-
/* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
|
|
41
|
-
Since session-reset generates a new session ID and the ID is grabbed at send-time, any delays or retries would cause the payload to be sent under the wrong session ID. */
|
|
42
|
-
agentRef.ee.on(SESSION_EVENTS.RESET, () => this.initializedAggregates.forEach(aggregateInst => this.triggerHarvestFor(aggregateInst, {
|
|
43
|
-
forceNoRetry: true
|
|
44
|
-
})));
|
|
45
38
|
}
|
|
46
39
|
startTimer(harvestInterval = this.agentRef.init.harvest.interval) {
|
|
47
40
|
if (this.#started) return;
|
|
@@ -234,7 +234,10 @@ export class SessionEntity {
|
|
|
234
234
|
// * stop recording (stn and sr)...
|
|
235
235
|
// * delete the session and start over
|
|
236
236
|
try {
|
|
237
|
-
if (this.initialized)
|
|
237
|
+
if (this.initialized) {
|
|
238
|
+
this.ee.emit(SESSION_EVENTS.RESET);
|
|
239
|
+
this.state.numOfResets++;
|
|
240
|
+
}
|
|
238
241
|
this.storage.remove(this.lookupKey);
|
|
239
242
|
this.inactiveTimer?.abort?.();
|
|
240
243
|
this.expiresTimer?.clear?.();
|
|
@@ -245,7 +248,7 @@ export class SessionEntity {
|
|
|
245
248
|
storage: this.storage,
|
|
246
249
|
expiresMs: this.expiresMs,
|
|
247
250
|
inactiveMs: this.inactiveMs,
|
|
248
|
-
numOfResets:
|
|
251
|
+
numOfResets: this.state.numOfResets
|
|
249
252
|
});
|
|
250
253
|
return this.read();
|
|
251
254
|
} catch (e) {
|
|
@@ -264,9 +267,6 @@ export class SessionEntity {
|
|
|
264
267
|
inactiveAt: this.getFutureTimestamp(this.inactiveMs)
|
|
265
268
|
});
|
|
266
269
|
}
|
|
267
|
-
isAfterSessionExpiry(timestamp) {
|
|
268
|
-
return this.state.numOfResets > 0 || typeof timestamp === 'number' && typeof this.state.expiresAt === 'number' && timestamp >= this.state.expiresAt;
|
|
269
|
-
}
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
272
|
* @param {number} timestamp
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { dispatchGlobalEvent } from '../dispatch/global-event';
|
|
7
|
+
|
|
6
8
|
/* eslint no-console: ["error", { allow: ["debug"] }] */
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -14,4 +16,15 @@
|
|
|
14
16
|
export function warn(code, secondary) {
|
|
15
17
|
if (typeof console.debug !== 'function') return;
|
|
16
18
|
console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(code), secondary);
|
|
19
|
+
dispatchGlobalEvent({
|
|
20
|
+
agentIdentifier: null,
|
|
21
|
+
drained: null,
|
|
22
|
+
type: 'data',
|
|
23
|
+
name: 'warn',
|
|
24
|
+
feature: 'warn',
|
|
25
|
+
data: {
|
|
26
|
+
code,
|
|
27
|
+
secondary
|
|
28
|
+
}
|
|
29
|
+
});
|
|
17
30
|
}
|
|
@@ -141,7 +141,7 @@ export class Aggregate extends AggregateBase {
|
|
|
141
141
|
...detailObj,
|
|
142
142
|
eventType: 'BrowserPerformance',
|
|
143
143
|
timestamp: this.toEpoch(entry.startTime),
|
|
144
|
-
entryName:
|
|
144
|
+
entryName: entry.name,
|
|
145
145
|
entryDuration: entry.duration,
|
|
146
146
|
entryType: type
|
|
147
147
|
});
|
|
@@ -207,7 +207,7 @@ export class Aggregate extends AggregateBase {
|
|
|
207
207
|
...entryObject,
|
|
208
208
|
eventType: 'BrowserPerformance',
|
|
209
209
|
timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entryObject.startTime)),
|
|
210
|
-
entryName: name,
|
|
210
|
+
entryName: cleanURL(name),
|
|
211
211
|
entryDuration: duration,
|
|
212
212
|
firstParty
|
|
213
213
|
};
|
|
@@ -217,6 +217,22 @@ export class Aggregate extends AggregateBase {
|
|
|
217
217
|
}
|
|
218
218
|
}, this.featureName, this.ee);
|
|
219
219
|
}
|
|
220
|
+
registerHandler('api-measure', (args, n) => {
|
|
221
|
+
const {
|
|
222
|
+
start,
|
|
223
|
+
duration,
|
|
224
|
+
customAttributes
|
|
225
|
+
} = args;
|
|
226
|
+
const event = {
|
|
227
|
+
...customAttributes,
|
|
228
|
+
eventType: 'BrowserPerformance',
|
|
229
|
+
timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(start)),
|
|
230
|
+
entryName: n,
|
|
231
|
+
entryDuration: duration,
|
|
232
|
+
entryType: 'measure'
|
|
233
|
+
};
|
|
234
|
+
this.addEvent(event);
|
|
235
|
+
}, this.featureName, this.ee);
|
|
220
236
|
agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
221
237
|
this.drain();
|
|
222
238
|
});
|