@newrelic/browser-agent 1.322.0 → 1.323.0-rc.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 +14 -0
- package/README.md +21 -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/connector.js +2 -0
- package/dist/cjs/features/ajax/aggregate/index.js +7 -1
- package/dist/cjs/features/ajax/instrument/distributed-tracing.js +12 -4
- package/dist/cjs/features/session_replay/aggregate/index.js +8 -3
- package/dist/cjs/features/session_trace/aggregate/index.js +6 -6
- package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +12 -5
- 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/connector.js +2 -0
- package/dist/esm/features/ajax/aggregate/index.js +7 -1
- package/dist/esm/features/ajax/instrument/distributed-tracing.js +12 -4
- package/dist/esm/features/session_replay/aggregate/index.js +8 -3
- package/dist/esm/features/session_trace/aggregate/index.js +6 -6
- package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +7 -0
- package/dist/types/common/harvest/connector.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/distributed-tracing.d.ts +1 -0
- package/dist/types/features/ajax/instrument/distributed-tracing.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/common/harvest/connector.js +2 -0
- package/src/features/ajax/aggregate/index.js +7 -3
- package/src/features/ajax/instrument/distributed-tracing.js +12 -4
- package/src/features/session_replay/aggregate/index.js +6 -1
- package/src/features/session_trace/aggregate/index.js +6 -6
- package/src/features/soft_navigations/aggregate/ajax-node.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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.323.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.322.0...v1.323.0) (2026-09-30)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add internal adoption metrics for AJAX Payloads ([#1842](https://github.com/newrelic/newrelic-browser-agent/issues/1842)) ([055b917](https://github.com/newrelic/newrelic-browser-agent/commit/055b9171ef6393da18ee64cc156c51af4ac1498e))
|
|
12
|
+
* Harvest SR on switch from ERROR to FULL mode ([#1864](https://github.com/newrelic/newrelic-browser-agent/issues/1864)) ([b95f9cd](https://github.com/newrelic/newrelic-browser-agent/commit/b95f9cd065225ae4386a75bfa5e7acc4adb528d9))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **ajax:** correct DT header timestamp to server time ([#1867](https://github.com/newrelic/newrelic-browser-agent/issues/1867)) ([5e86506](https://github.com/newrelic/newrelic-browser-agent/commit/5e865062e5162acc7c4ee2924cf7b1db9d8f5685))
|
|
18
|
+
* Ensure timeKeeper readiness on Connector race condition ([#1875](https://github.com/newrelic/newrelic-browser-agent/issues/1875)) ([45de2d8](https://github.com/newrelic/newrelic-browser-agent/commit/45de2d85c730a0dd189ed6f14c7166e89ef3e1a0))
|
|
19
|
+
|
|
6
20
|
## [1.322.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.321.0...v1.322.0) (2026-09-11)
|
|
7
21
|
|
|
8
22
|
|
package/README.md
CHANGED
|
@@ -142,6 +142,27 @@ const options = {
|
|
|
142
142
|
}
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
## AJAX deny list
|
|
146
|
+
|
|
147
|
+
The browser agent can be configured to exclude specific domains or paths from `AjaxRequest` event tracking using the `ajax.deny_list` setting in the `init` configuration.
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const options = {
|
|
151
|
+
info: { ... },
|
|
152
|
+
loader_config: { ... },
|
|
153
|
+
init: {
|
|
154
|
+
ajax: {
|
|
155
|
+
deny_list: ["mywebsite.com/photos"]
|
|
156
|
+
},
|
|
157
|
+
...
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
<b>Important:</b> Updating the deny list via the app settings page or NerdGraph only updates the `NREUM.init` configuration shown in the copy/paste snippet on the app's *Application settings* page. It doesn't take effect automatically for an agent installed via NPM. If you configure the deny list using one of those methods, you must manually copy over the updated configuration, then rebuild and redeploy your application for the change to take effect.
|
|
163
|
+
|
|
164
|
+
See [Configure AjaxRequest events](https://docs.newrelic.com/docs/browser/new-relic-browser/configuration/configure-ajax-request-events/) for more information.
|
|
165
|
+
|
|
145
166
|
## Options Parameter
|
|
146
167
|
|
|
147
168
|
The `options` parameter used, or passed in, when instantiating the `BrowserAgent` class can include the following arguments:
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.323.0-rc.1";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.323.0-rc.1";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -90,6 +90,8 @@ class Connector {
|
|
|
90
90
|
if (cachedResp) {
|
|
91
91
|
// Report that we've seen a race condition on updating session state
|
|
92
92
|
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['Session/RaceCondition/Seen'], undefined, _features.FEATURE_NAMES.metrics, this.#agentRef.ee);
|
|
93
|
+
// The other tab/agent that wrote this cached response also wrote its own serverTimeDiff to the session around the same time. TimeKeeper was constructed before that happened, so re-check the session now to pick it up.
|
|
94
|
+
this.#agentRef.runtime.timeKeeper.processStoredDiff();
|
|
93
95
|
// `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
|
|
94
96
|
const {
|
|
95
97
|
app,
|
|
@@ -121,7 +121,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
121
121
|
if (ctx.dt) {
|
|
122
122
|
event.spanId = ctx.dt.spanId;
|
|
123
123
|
event.traceId = ctx.dt.traceId;
|
|
124
|
-
|
|
124
|
+
// ctx.dt.timestampCorrected holds the already NR-server-time-corrected value (see distributed-tracing.js) if
|
|
125
|
+
// timeKeeper was ready when the request started -- reuse it if present, otherwise correct it here now, to avoid double-correcting.
|
|
126
|
+
event.spanTimestamp = ctx.dt.timestampCorrected ?? Math.floor(this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(ctx.dt.timestamp));
|
|
125
127
|
}
|
|
126
128
|
if (event.gql) this.reportSupportabilityMetric('Ajax/Events/GraphQL/Bytes-Added', (0, _stringify.stringify)(event.gql).length);
|
|
127
129
|
|
|
@@ -217,6 +219,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
217
219
|
addKey: addStringRaw,
|
|
218
220
|
addVal: addStringWithTruncation
|
|
219
221
|
});
|
|
222
|
+
|
|
223
|
+
// .length is UTF-16 code units, not true UTF-8 byte size, so this undercounts multi-byte characters -- but it reuses the
|
|
224
|
+
// already-serialized payloadAttrs strings instead of re-stringifying/byte-counting, which is worth the imprecision here.
|
|
225
|
+
if (payloadAttrs.length) this.reportSupportabilityMetric('Ajax/Events/Payload/Bytes-Added', payloadAttrs.join(';').length);
|
|
220
226
|
const attrParts = [...regularAttrs, ...payloadAttrs];
|
|
221
227
|
fields.unshift((0, _belSerializer.numeric)(attrParts.length));
|
|
222
228
|
insert += fields.join(',');
|
|
@@ -9,7 +9,7 @@ var _parseUrl = require("../../../common/url/parse-url");
|
|
|
9
9
|
var _runtime = require("../../../common/constants/runtime");
|
|
10
10
|
var _stringify = require("../../../common/util/stringify");
|
|
11
11
|
/**
|
|
12
|
-
* Copyright 2020-
|
|
12
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
13
13
|
* SPDX-License-Identifier: Apache-2.0
|
|
14
14
|
*/
|
|
15
15
|
|
|
@@ -31,17 +31,25 @@ class DT {
|
|
|
31
31
|
var spanId = (0, _uniqueId.generateSpanId)();
|
|
32
32
|
var traceId = (0, _uniqueId.generateTraceId)();
|
|
33
33
|
var timestamp = Date.now();
|
|
34
|
+
// Correct to NR server time when timeKeeper is available/ready; if it isn't connected yet (e.g. RUM hasn't
|
|
35
|
+
// completed), accept the uncorrected local timestamp. This only affects the receiving APM agent's computed
|
|
36
|
+
// transportDuration value on its Span -- it is not used for anything else in this payload.
|
|
37
|
+
var timestampCorrected;
|
|
38
|
+
if (this.agentRef.runtime?.timeKeeper?.ready) {
|
|
39
|
+
timestampCorrected = Math.floor(this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(timestamp));
|
|
40
|
+
}
|
|
34
41
|
var payload = {
|
|
35
42
|
spanId,
|
|
36
43
|
traceId,
|
|
37
|
-
timestamp
|
|
44
|
+
timestamp,
|
|
45
|
+
timestampCorrected
|
|
38
46
|
};
|
|
39
47
|
if (parsedOrigin.sameOrigin || this.isAllowedOrigin(parsedOrigin) && this.useTraceContextHeadersForCors()) {
|
|
40
48
|
payload.traceContextParentHeader = this.generateTraceContextParentHeader(spanId, traceId);
|
|
41
|
-
payload.traceContextStateHeader = this.generateTraceContextStateHeader(spanId, timestamp, accountId, agentId, trustKey);
|
|
49
|
+
payload.traceContextStateHeader = this.generateTraceContextStateHeader(spanId, timestampCorrected ?? timestamp, accountId, agentId, trustKey);
|
|
42
50
|
}
|
|
43
51
|
if (parsedOrigin.sameOrigin && !this.excludeNewrelicHeader() || !parsedOrigin.sameOrigin && this.isAllowedOrigin(parsedOrigin) && this.useNewrelicHeaderForCors()) {
|
|
44
|
-
payload.newrelicHeader = this.generateTraceHeader(spanId, traceId, timestamp, accountId, agentId, trustKey);
|
|
52
|
+
payload.newrelicHeader = this.generateTraceHeader(spanId, traceId, timestampCorrected ?? timestamp, accountId, agentId, trustKey);
|
|
45
53
|
}
|
|
46
54
|
return payload;
|
|
47
55
|
}
|
|
@@ -146,13 +146,18 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
146
146
|
}
|
|
147
147
|
switchToFull() {
|
|
148
148
|
if (!this.entitled || this.blocked) return;
|
|
149
|
+
const prevMode = this.mode;
|
|
149
150
|
this.mode = _constants2.MODE.FULL;
|
|
151
|
+
this.#writeToStorage({
|
|
152
|
+
sessionReplayMode: this.mode
|
|
153
|
+
});
|
|
154
|
+
|
|
150
155
|
// if the error was noticed AFTER the recorder was already imported....
|
|
151
156
|
if (this.recorder && this.initialized) {
|
|
157
|
+
if (prevMode === _constants2.MODE.ERROR) {
|
|
158
|
+
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
159
|
+
}
|
|
152
160
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(_constants.TRIGGERS.SWITCH_TO_FULL, this.mode); // off --> full
|
|
153
|
-
this.#writeToStorage({
|
|
154
|
-
sessionReplayMode: this.mode
|
|
155
|
-
});
|
|
156
161
|
} else {
|
|
157
162
|
this.initializeRecording(_constants2.MODE.FULL, true, _constants.TRIGGERS.SWITCH_TO_FULL);
|
|
158
163
|
}
|
|
@@ -90,12 +90,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
90
90
|
(0, _registerHandler.registerHandler)('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee);
|
|
91
91
|
(0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
|
|
92
92
|
(0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
93
|
+
}
|
|
94
|
+
if (this.mode === _constants2.MODE.ERROR) {
|
|
95
|
+
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
96
|
+
(0, _registerHandler.registerHandler)('trace-jserror', () => {
|
|
97
|
+
if (this.mode === _constants2.MODE.ERROR) this.switchToFull();
|
|
98
|
+
}, this.featureName, this.ee);
|
|
99
99
|
}
|
|
100
100
|
this.agentRef.runtime.session.write({
|
|
101
101
|
sessionTraceMode: this.mode
|
|
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.AjaxNode = void 0;
|
|
7
7
|
var _belSerializer = require("../../../common/serialize/bel-serializer");
|
|
8
8
|
var _payloads = require("../../../common/payloads/payloads");
|
|
9
|
-
var
|
|
10
|
-
var
|
|
9
|
+
var _handle = require("../../../common/event-emitter/handle");
|
|
10
|
+
var _features = require("../../../loaders/features/features");
|
|
11
|
+
var _constants = require("../../metrics/constants");
|
|
12
|
+
var _constants2 = require("../../ajax/constants");
|
|
13
|
+
var _constants3 = require("../constants");
|
|
11
14
|
var _belNode = require("./bel-node");
|
|
12
15
|
/**
|
|
13
16
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
@@ -17,7 +20,7 @@ var _belNode = require("./bel-node");
|
|
|
17
20
|
class AjaxNode extends _belNode.BelNode {
|
|
18
21
|
constructor(ajaxEvent, ajaxContext) {
|
|
19
22
|
super();
|
|
20
|
-
this.belType =
|
|
23
|
+
this.belType = _constants3.NODE_TYPE.AJAX;
|
|
21
24
|
this.method = ajaxEvent.method;
|
|
22
25
|
this.status = ajaxEvent.status;
|
|
23
26
|
this.domain = ajaxEvent.domain;
|
|
@@ -30,7 +33,7 @@ class AjaxNode extends _belNode.BelNode {
|
|
|
30
33
|
this.spanTimestamp = ajaxEvent.spanTimestamp;
|
|
31
34
|
this.gql = ajaxEvent.gql;
|
|
32
35
|
this.targetAttributes = ajaxEvent.targetAttributes;
|
|
33
|
-
this[
|
|
36
|
+
this[_constants2.AJAX_ID] = ajaxEvent[_constants2.AJAX_ID]; // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
|
|
34
37
|
|
|
35
38
|
// optional payload metadata attributes
|
|
36
39
|
this.requestBody = ajaxEvent.requestBody;
|
|
@@ -74,7 +77,7 @@ class AjaxNode extends _belNode.BelNode {
|
|
|
74
77
|
addVal: addString
|
|
75
78
|
});
|
|
76
79
|
regularAttrs.push(...(0, _belSerializer.addCustomAttributes)({
|
|
77
|
-
[
|
|
80
|
+
[_constants2.AJAX_ID]: this[_constants2.AJAX_ID]
|
|
78
81
|
}, {
|
|
79
82
|
addKey: addStringRaw,
|
|
80
83
|
addVal: addStringRaw
|
|
@@ -101,6 +104,10 @@ class AjaxNode extends _belNode.BelNode {
|
|
|
101
104
|
addKey: addStringRaw,
|
|
102
105
|
addVal: addStringWithTruncation
|
|
103
106
|
});
|
|
107
|
+
|
|
108
|
+
// .length is UTF-16 code units, not true UTF-8 byte size, so this undercounts multi-byte characters -- but it reuses the
|
|
109
|
+
// already-serialized payloadAttrs strings instead of re-stringifying/byte-counting, which is worth the imprecision here.
|
|
110
|
+
if (payloadAttrs.length) (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Payload/Bytes-Added', payloadAttrs.join(';').length], undefined, _features.FEATURE_NAMES.metrics, agentRef.ee);
|
|
104
111
|
let allAttachedNodes = [...regularAttrs, ...payloadAttrs];
|
|
105
112
|
this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
|
|
106
113
|
|
|
@@ -84,6 +84,8 @@ export class Connector {
|
|
|
84
84
|
if (cachedResp) {
|
|
85
85
|
// Report that we've seen a race condition on updating session state
|
|
86
86
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Session/RaceCondition/Seen'], undefined, FEATURE_NAMES.metrics, this.#agentRef.ee);
|
|
87
|
+
// The other tab/agent that wrote this cached response also wrote its own serverTimeDiff to the session around the same time. TimeKeeper was constructed before that happened, so re-check the session now to pick it up.
|
|
88
|
+
this.#agentRef.runtime.timeKeeper.processStoredDiff();
|
|
87
89
|
// `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
|
|
88
90
|
const {
|
|
89
91
|
app,
|
|
@@ -114,7 +114,9 @@ export class Aggregate extends AggregateBase {
|
|
|
114
114
|
if (ctx.dt) {
|
|
115
115
|
event.spanId = ctx.dt.spanId;
|
|
116
116
|
event.traceId = ctx.dt.traceId;
|
|
117
|
-
|
|
117
|
+
// ctx.dt.timestampCorrected holds the already NR-server-time-corrected value (see distributed-tracing.js) if
|
|
118
|
+
// timeKeeper was ready when the request started -- reuse it if present, otherwise correct it here now, to avoid double-correcting.
|
|
119
|
+
event.spanTimestamp = ctx.dt.timestampCorrected ?? Math.floor(this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(ctx.dt.timestamp));
|
|
118
120
|
}
|
|
119
121
|
if (event.gql) this.reportSupportabilityMetric('Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length);
|
|
120
122
|
|
|
@@ -210,6 +212,10 @@ export class Aggregate extends AggregateBase {
|
|
|
210
212
|
addKey: addStringRaw,
|
|
211
213
|
addVal: addStringWithTruncation
|
|
212
214
|
});
|
|
215
|
+
|
|
216
|
+
// .length is UTF-16 code units, not true UTF-8 byte size, so this undercounts multi-byte characters -- but it reuses the
|
|
217
|
+
// already-serialized payloadAttrs strings instead of re-stringifying/byte-counting, which is worth the imprecision here.
|
|
218
|
+
if (payloadAttrs.length) this.reportSupportabilityMetric('Ajax/Events/Payload/Bytes-Added', payloadAttrs.join(';').length);
|
|
213
219
|
const attrParts = [...regularAttrs, ...payloadAttrs];
|
|
214
220
|
fields.unshift(numeric(attrParts.length));
|
|
215
221
|
insert += fields.join(',');
|
|
@@ -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 { generateSpanId, generateTraceId } from '../../../common/ids/unique-id';
|
|
@@ -24,17 +24,25 @@ export class DT {
|
|
|
24
24
|
var spanId = generateSpanId();
|
|
25
25
|
var traceId = generateTraceId();
|
|
26
26
|
var timestamp = Date.now();
|
|
27
|
+
// Correct to NR server time when timeKeeper is available/ready; if it isn't connected yet (e.g. RUM hasn't
|
|
28
|
+
// completed), accept the uncorrected local timestamp. This only affects the receiving APM agent's computed
|
|
29
|
+
// transportDuration value on its Span -- it is not used for anything else in this payload.
|
|
30
|
+
var timestampCorrected;
|
|
31
|
+
if (this.agentRef.runtime?.timeKeeper?.ready) {
|
|
32
|
+
timestampCorrected = Math.floor(this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(timestamp));
|
|
33
|
+
}
|
|
27
34
|
var payload = {
|
|
28
35
|
spanId,
|
|
29
36
|
traceId,
|
|
30
|
-
timestamp
|
|
37
|
+
timestamp,
|
|
38
|
+
timestampCorrected
|
|
31
39
|
};
|
|
32
40
|
if (parsedOrigin.sameOrigin || this.isAllowedOrigin(parsedOrigin) && this.useTraceContextHeadersForCors()) {
|
|
33
41
|
payload.traceContextParentHeader = this.generateTraceContextParentHeader(spanId, traceId);
|
|
34
|
-
payload.traceContextStateHeader = this.generateTraceContextStateHeader(spanId, timestamp, accountId, agentId, trustKey);
|
|
42
|
+
payload.traceContextStateHeader = this.generateTraceContextStateHeader(spanId, timestampCorrected ?? timestamp, accountId, agentId, trustKey);
|
|
35
43
|
}
|
|
36
44
|
if (parsedOrigin.sameOrigin && !this.excludeNewrelicHeader() || !parsedOrigin.sameOrigin && this.isAllowedOrigin(parsedOrigin) && this.useNewrelicHeaderForCors()) {
|
|
37
|
-
payload.newrelicHeader = this.generateTraceHeader(spanId, traceId, timestamp, accountId, agentId, trustKey);
|
|
45
|
+
payload.newrelicHeader = this.generateTraceHeader(spanId, traceId, timestampCorrected ?? timestamp, accountId, agentId, trustKey);
|
|
38
46
|
}
|
|
39
47
|
return payload;
|
|
40
48
|
}
|
|
@@ -142,13 +142,18 @@ export class Aggregate extends AggregateBase {
|
|
|
142
142
|
}
|
|
143
143
|
switchToFull() {
|
|
144
144
|
if (!this.entitled || this.blocked) return;
|
|
145
|
+
const prevMode = this.mode;
|
|
145
146
|
this.mode = MODE.FULL;
|
|
147
|
+
this.#writeToStorage({
|
|
148
|
+
sessionReplayMode: this.mode
|
|
149
|
+
});
|
|
150
|
+
|
|
146
151
|
// if the error was noticed AFTER the recorder was already imported....
|
|
147
152
|
if (this.recorder && this.initialized) {
|
|
153
|
+
if (prevMode === MODE.ERROR) {
|
|
154
|
+
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
155
|
+
}
|
|
148
156
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(TRIGGERS.SWITCH_TO_FULL, this.mode); // off --> full
|
|
149
|
-
this.#writeToStorage({
|
|
150
|
-
sessionReplayMode: this.mode
|
|
151
|
-
});
|
|
152
157
|
} else {
|
|
153
158
|
this.initializeRecording(MODE.FULL, true, TRIGGERS.SWITCH_TO_FULL);
|
|
154
159
|
}
|
|
@@ -84,12 +84,12 @@ export class Aggregate extends AggregateBase {
|
|
|
84
84
|
registerHandler('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee);
|
|
85
85
|
registerHandler('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
|
|
86
86
|
registerHandler('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
87
|
+
}
|
|
88
|
+
if (this.mode === MODE.ERROR) {
|
|
89
|
+
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
90
|
+
registerHandler('trace-jserror', () => {
|
|
91
|
+
if (this.mode === MODE.ERROR) this.switchToFull();
|
|
92
|
+
}, this.featureName, this.ee);
|
|
93
93
|
}
|
|
94
94
|
this.agentRef.runtime.session.write({
|
|
95
95
|
sessionTraceMode: this.mode
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
|
|
6
6
|
import { createStringAdders } from '../../../common/payloads/payloads';
|
|
7
|
+
import { handle } from '../../../common/event-emitter/handle';
|
|
8
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
9
|
+
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
7
10
|
import { AJAX_ID } from '../../ajax/constants';
|
|
8
11
|
import { NODE_TYPE } from '../constants';
|
|
9
12
|
import { BelNode } from './bel-node';
|
|
@@ -94,6 +97,10 @@ export class AjaxNode extends BelNode {
|
|
|
94
97
|
addKey: addStringRaw,
|
|
95
98
|
addVal: addStringWithTruncation
|
|
96
99
|
});
|
|
100
|
+
|
|
101
|
+
// .length is UTF-16 code units, not true UTF-8 byte size, so this undercounts multi-byte characters -- but it reuses the
|
|
102
|
+
// already-serialized payloadAttrs strings instead of re-stringifying/byte-counting, which is worth the imprecision here.
|
|
103
|
+
if (payloadAttrs.length) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Payload/Bytes-Added', payloadAttrs.join(';').length], undefined, FEATURE_NAMES.metrics, agentRef.ee);
|
|
97
104
|
let allAttachedNodes = [...regularAttrs, ...payloadAttrs];
|
|
98
105
|
this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
|
|
99
106
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/connector.js"],"names":[],"mappings":"AAmBA;IAKE,2BAeC;IAED,+CAiBC;;
|
|
1
|
+
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/connector.js"],"names":[],"mappings":"AAmBA;IAKE,2BAeC;IAED,+CAiBC;;CAiHF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IAEjC,2BA6BC;IAvBC,uBAA4D;IAyB9D,0GA2FC;IAED,+CAOC;IAED,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IAEjC,2BA6BC;IAvBC,uBAA4D;IAyB9D,0GA2FC;IAED,+CAOC;IAED,iDA2EC;CACF;8BA9N6B,4BAA4B;2BAK/B,gCAAgC"}
|
|
@@ -5,6 +5,7 @@ export class DT {
|
|
|
5
5
|
spanId: string;
|
|
6
6
|
traceId: string;
|
|
7
7
|
timestamp: number;
|
|
8
|
+
timestampCorrected: number | undefined;
|
|
8
9
|
} | null;
|
|
9
10
|
generateTraceContextParentHeader(spanId: any, traceId: any): string;
|
|
10
11
|
generateTraceContextStateHeader(spanId: any, timestamp: any, accountId: any, appId: any, trustKey: any): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distributed-tracing.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/distributed-tracing.js"],"names":[],"mappings":"AASA;IACE,2BAEC;IADC,cAAwB;IAG1B
|
|
1
|
+
{"version":3,"file":"distributed-tracing.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/distributed-tracing.js"],"names":[],"mappings":"AASA;IACE,2BAEC;IADC,cAAwB;IAG1B;;;;;aA8CC;IAED,oEAEC;IAED,gHASC;IAED,yHAsBC;IAID,4CAEC;IAED,4CAkBC;IAGD,iCAMC;IAED,oCAMC;IAED,yCAMC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA2BA;IACE,2BAAiC;IAIjC,sCA+FC;IAlGD,WAAW;IAOT,uBAA0D;IAE1D,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IACnB,+DAA+D;IAC/D,wBAA0B;IAE1B,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,qBAA2B;IAE3B,cAA8C;IAI9C,kCAAqG;IAqEvG,0BAEC;IAED,0BAMC;IAED,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA2BA;IACE,2BAAiC;IAIjC,sCA+FC;IAlGD,WAAW;IAOT,uBAA0D;IAE1D,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IACnB,+DAA+D;IAC/D,wBAA0B;IAE1B,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,qBAA2B;IAE3B,cAA8C;IAI9C,kCAAqG;IAqEvG,0BAEC;IAED,0BAMC;IAED,qBAeC;IAED;;;;;;OAMG;IACH,4BALW,OAAO,iBACP,OAAO;;;;;;QAEL,IAAI,CA+ChB;IAED,2BAUC;IAED,0BA0CC;IAED;;;;OAIG;IACH,6BAHW,MAAM,EAAE,GACN;QAAE,UAAU,EAAE,MAAM,GAAC,SAAS,CAAC;QAAC,SAAS,EAAE,MAAM,GAAC,SAAS,CAAA;KAAE,CAUzE;IAED;;;;;;;;;;MAsEC;IAED,sCAcC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;;CAOF;8BAtY6B,4BAA4B;2BAe/B,gCAAgC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AAaA;IACE,8CA8BC;IA5BC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IACxB,sBAAkD;IAIlD,iBAAwC;IACxC,oBAA8C;IAC9C,kBAA0C;IAC1C,kBAA0C;IAC1C,qBAAgD;IAK9C,iBAAoE;IAKxE,iFAsDC;IAvEC,sBAAkC;CAwErC;wBA1FuB,YAAY"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.323.0-rc.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -206,7 +206,10 @@
|
|
|
206
206
|
"test-server": "node ./tools/wdio/bin/server",
|
|
207
207
|
"lt:update-browsers": "node ./tools/browsers-lists/lt-update-supported.mjs",
|
|
208
208
|
"lt:upload-webview-assets": "node ./tools/lambda-test/upload-webview-assets.mjs",
|
|
209
|
+
"build:frameworks": "npm run build:npm && npm --prefix ./tools/test-builds run build-frameworks",
|
|
209
210
|
"tools:test-builds": "npm --prefix ./tools/test-builds run build-all",
|
|
211
|
+
"test-builds:bump-latest": "node ./tools/test-builds/bump-latest.mjs",
|
|
212
|
+
"test-builds:framework-report": "node ./tools/test-builds/generate-framework-report.mjs",
|
|
210
213
|
"third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps",
|
|
211
214
|
"prepare": "husky install",
|
|
212
215
|
"npm:build:esm": "npx babel --env-name npm-esm --out-dir dist/esm --out-file-extension .js ./src",
|
|
@@ -294,4 +297,4 @@
|
|
|
294
297
|
"README.md",
|
|
295
298
|
"CHANGELOG.md"
|
|
296
299
|
]
|
|
297
|
-
}
|
|
300
|
+
}
|
|
@@ -73,6 +73,8 @@ export class Connector {
|
|
|
73
73
|
if (cachedResp) {
|
|
74
74
|
// Report that we've seen a race condition on updating session state
|
|
75
75
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Session/RaceCondition/Seen'], undefined, FEATURE_NAMES.metrics, this.#agentRef.ee)
|
|
76
|
+
// The other tab/agent that wrote this cached response also wrote its own serverTimeDiff to the session around the same time. TimeKeeper was constructed before that happened, so re-check the session now to pick it up.
|
|
77
|
+
this.#agentRef.runtime.timeKeeper.processStoredDiff()
|
|
76
78
|
// `cachedRumResponse` is stored flat (`{ app, ...flags }`, matching the old v1 RUM response), so re-nest it before applying.
|
|
77
79
|
const { app, ...config } = cachedResp
|
|
78
80
|
this.#applyConnectResponse({ app, config })
|
|
@@ -129,9 +129,9 @@ export class Aggregate extends AggregateBase {
|
|
|
129
129
|
if (ctx.dt) {
|
|
130
130
|
event.spanId = ctx.dt.spanId
|
|
131
131
|
event.traceId = ctx.dt.traceId
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
)
|
|
132
|
+
// ctx.dt.timestampCorrected holds the already NR-server-time-corrected value (see distributed-tracing.js) if
|
|
133
|
+
// timeKeeper was ready when the request started -- reuse it if present, otherwise correct it here now, to avoid double-correcting.
|
|
134
|
+
event.spanTimestamp = ctx.dt.timestampCorrected ?? Math.floor(this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(ctx.dt.timestamp))
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
if (event.gql) this.reportSupportabilityMetric('Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length)
|
|
@@ -212,6 +212,10 @@ export class Aggregate extends AggregateBase {
|
|
|
212
212
|
...(event.responseHeaders ? { responseHeaders: event.responseHeaders } : {})
|
|
213
213
|
}, { addKey: addStringRaw, addVal: addStringWithTruncation })
|
|
214
214
|
|
|
215
|
+
// .length is UTF-16 code units, not true UTF-8 byte size, so this undercounts multi-byte characters -- but it reuses the
|
|
216
|
+
// already-serialized payloadAttrs strings instead of re-stringifying/byte-counting, which is worth the imprecision here.
|
|
217
|
+
if (payloadAttrs.length) this.reportSupportabilityMetric('Ajax/Events/Payload/Bytes-Added', payloadAttrs.join(';').length)
|
|
218
|
+
|
|
215
219
|
const attrParts = [...regularAttrs, ...payloadAttrs]
|
|
216
220
|
fields.unshift(numeric(attrParts.length))
|
|
217
221
|
|
|
@@ -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 { generateSpanId, generateTraceId } from '../../../common/ids/unique-id'
|
|
@@ -29,23 +29,31 @@ export class DT {
|
|
|
29
29
|
var spanId = generateSpanId()
|
|
30
30
|
var traceId = generateTraceId()
|
|
31
31
|
var timestamp = Date.now()
|
|
32
|
+
// Correct to NR server time when timeKeeper is available/ready; if it isn't connected yet (e.g. RUM hasn't
|
|
33
|
+
// completed), accept the uncorrected local timestamp. This only affects the receiving APM agent's computed
|
|
34
|
+
// transportDuration value on its Span -- it is not used for anything else in this payload.
|
|
35
|
+
var timestampCorrected
|
|
36
|
+
if (this.agentRef.runtime?.timeKeeper?.ready) {
|
|
37
|
+
timestampCorrected = Math.floor(this.agentRef.runtime.timeKeeper.correctAbsoluteTimestamp(timestamp))
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
var payload = {
|
|
34
41
|
spanId,
|
|
35
42
|
traceId,
|
|
36
|
-
timestamp
|
|
43
|
+
timestamp,
|
|
44
|
+
timestampCorrected
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
if (parsedOrigin.sameOrigin ||
|
|
40
48
|
(this.isAllowedOrigin(parsedOrigin) && this.useTraceContextHeadersForCors())) {
|
|
41
49
|
payload.traceContextParentHeader = this.generateTraceContextParentHeader(spanId, traceId)
|
|
42
|
-
payload.traceContextStateHeader = this.generateTraceContextStateHeader(spanId, timestamp,
|
|
50
|
+
payload.traceContextStateHeader = this.generateTraceContextStateHeader(spanId, timestampCorrected ?? timestamp,
|
|
43
51
|
accountId, agentId, trustKey)
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
if ((parsedOrigin.sameOrigin && !this.excludeNewrelicHeader()) ||
|
|
47
55
|
(!parsedOrigin.sameOrigin && this.isAllowedOrigin(parsedOrigin) && this.useNewrelicHeaderForCors())) {
|
|
48
|
-
payload.newrelicHeader = this.generateTraceHeader(spanId, traceId, timestamp, accountId,
|
|
56
|
+
payload.newrelicHeader = this.generateTraceHeader(spanId, traceId, timestampCorrected ?? timestamp, accountId,
|
|
49
57
|
agentId, trustKey)
|
|
50
58
|
}
|
|
51
59
|
|
|
@@ -141,11 +141,16 @@ export class Aggregate extends AggregateBase {
|
|
|
141
141
|
|
|
142
142
|
switchToFull () {
|
|
143
143
|
if (!this.entitled || this.blocked) return
|
|
144
|
+
const prevMode = this.mode
|
|
144
145
|
this.mode = MODE.FULL
|
|
146
|
+
this.#writeToStorage({ sessionReplayMode: this.mode })
|
|
147
|
+
|
|
145
148
|
// if the error was noticed AFTER the recorder was already imported....
|
|
146
149
|
if (this.recorder && this.initialized) {
|
|
150
|
+
if (prevMode === MODE.ERROR) {
|
|
151
|
+
this.agentRef.runtime.harvester.triggerHarvestFor(this)
|
|
152
|
+
}
|
|
147
153
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(TRIGGERS.SWITCH_TO_FULL, this.mode) // off --> full
|
|
148
|
-
this.#writeToStorage({ sessionReplayMode: this.mode })
|
|
149
154
|
} else {
|
|
150
155
|
this.initializeRecording(MODE.FULL, true, TRIGGERS.SWITCH_TO_FULL)
|
|
151
156
|
}
|
|
@@ -90,13 +90,13 @@ export class Aggregate extends AggregateBase {
|
|
|
90
90
|
registerHandler('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee)
|
|
91
91
|
registerHandler('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee)
|
|
92
92
|
registerHandler('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee)
|
|
93
|
+
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
95
|
+
if (this.mode === MODE.ERROR) {
|
|
96
|
+
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
97
|
+
registerHandler('trace-jserror', () => {
|
|
98
|
+
if (this.mode === MODE.ERROR) this.switchToFull()
|
|
99
|
+
}, this.featureName, this.ee)
|
|
100
100
|
}
|
|
101
101
|
this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
|
|
102
102
|
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer'
|
|
6
6
|
import { createStringAdders } from '../../../common/payloads/payloads'
|
|
7
|
+
import { handle } from '../../../common/event-emitter/handle'
|
|
8
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
9
|
+
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
7
10
|
import { AJAX_ID } from '../../ajax/constants'
|
|
8
11
|
import { NODE_TYPE } from '../constants'
|
|
9
12
|
import { BelNode } from './bel-node'
|
|
@@ -83,6 +86,10 @@ export class AjaxNode extends BelNode {
|
|
|
83
86
|
...(this.responseHeaders ? { responseHeaders: this.responseHeaders } : {})
|
|
84
87
|
}, { addKey: addStringRaw, addVal: addStringWithTruncation })
|
|
85
88
|
|
|
89
|
+
// .length is UTF-16 code units, not true UTF-8 byte size, so this undercounts multi-byte characters -- but it reuses the
|
|
90
|
+
// already-serialized payloadAttrs strings instead of re-stringifying/byte-counting, which is worth the imprecision here.
|
|
91
|
+
if (payloadAttrs.length) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Payload/Bytes-Added', payloadAttrs.join(';').length], undefined, FEATURE_NAMES.metrics, agentRef.ee)
|
|
92
|
+
|
|
86
93
|
let allAttachedNodes = [...regularAttrs, ...payloadAttrs]
|
|
87
94
|
this.children.forEach(node => allAttachedNodes.push(node.serialize())) // no children is expected under ajax nodes at this time
|
|
88
95
|
|