@newrelic/browser-agent 1.303.0 → 1.304.0-rc.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 +13 -0
- package/dist/cjs/common/config/init.js +0 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/session/session-entity.js +1 -0
- package/dist/cjs/common/util/monkey-patched.js +5 -3
- package/dist/cjs/common/window/page-visibility.js +4 -0
- package/dist/cjs/common/wrap/wrap-websocket.js +262 -32
- package/dist/cjs/features/generic_events/aggregate/index.js +24 -8
- package/dist/cjs/features/generic_events/instrument/index.js +13 -3
- package/dist/cjs/features/jserrors/aggregate/index.js +4 -1
- package/dist/cjs/features/metrics/aggregate/index.js +0 -6
- package/dist/cjs/features/metrics/constants.js +2 -4
- package/dist/cjs/features/metrics/instrument/index.js +0 -11
- package/dist/cjs/features/page_view_timing/instrument/index.js +1 -2
- package/dist/cjs/features/session_replay/shared/recorder.js +2 -1
- package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -1
- package/dist/cjs/features/soft_navigations/instrument/index.js +1 -0
- package/dist/cjs/features/spa/aggregate/index.js +1 -1
- package/dist/esm/common/config/init.js +0 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/session/session-entity.js +1 -0
- package/dist/esm/common/util/monkey-patched.js +5 -3
- package/dist/esm/common/window/page-visibility.js +4 -1
- package/dist/esm/common/wrap/wrap-websocket.js +262 -31
- package/dist/esm/features/generic_events/aggregate/index.js +24 -8
- package/dist/esm/features/generic_events/instrument/index.js +13 -3
- package/dist/esm/features/jserrors/aggregate/index.js +4 -1
- package/dist/esm/features/metrics/aggregate/index.js +0 -6
- package/dist/esm/features/metrics/constants.js +1 -4
- package/dist/esm/features/metrics/instrument/index.js +1 -14
- package/dist/esm/features/page_view_timing/instrument/index.js +2 -3
- package/dist/esm/features/session_replay/shared/recorder.js +2 -1
- package/dist/esm/features/soft_navigations/aggregate/index.js +1 -1
- package/dist/esm/features/soft_navigations/instrument/index.js +1 -0
- package/dist/esm/features/spa/aggregate/index.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/util/monkey-patched.d.ts.map +1 -1
- package/dist/types/common/window/page-visibility.d.ts +1 -0
- package/dist/types/common/window/page-visibility.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-websocket.d.ts +0 -2
- package/dist/types/common/wrap/wrap-websocket.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +0 -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/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/constants.d.ts +0 -1
- package/dist/types/features/metrics/constants.d.ts.map +1 -1
- package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/common/config/init.js +0 -1
- package/src/common/session/session-entity.js +1 -0
- package/src/common/util/monkey-patched.js +5 -3
- package/src/common/window/page-visibility.js +5 -1
- package/src/common/wrap/wrap-websocket.js +248 -30
- package/src/features/generic_events/aggregate/index.js +26 -8
- package/src/features/generic_events/instrument/index.js +13 -3
- package/src/features/jserrors/aggregate/index.js +4 -1
- package/src/features/metrics/aggregate/index.js +0 -6
- package/src/features/metrics/constants.js +0 -4
- package/src/features/metrics/instrument/index.js +0 -10
- package/src/features/page_view_timing/instrument/index.js +2 -3
- package/src/features/session_replay/shared/recorder.js +2 -1
- package/src/features/soft_navigations/aggregate/index.js +1 -1
- package/src/features/soft_navigations/instrument/index.js +1 -0
- package/src/features/spa/aggregate/index.js +1 -1
- package/dist/cjs/features/metrics/aggregate/websocket-detection.js +0 -39
- package/dist/esm/features/metrics/aggregate/websocket-detection.js +0 -33
- package/dist/types/features/metrics/aggregate/websocket-detection.d.ts +0 -12
- package/dist/types/features/metrics/aggregate/websocket-detection.d.ts.map +0 -1
- package/src/features/metrics/aggregate/websocket-detection.js +0 -35
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
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.304.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.303.0...v1.304.0) (2025-12-03)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Reduce Session Replay snapshot sizes ([#1630](https://github.com/newrelic/newrelic-browser-agent/issues/1630)) ([7d3edc8](https://github.com/newrelic/newrelic-browser-agent/commit/7d3edc80edd421d4960b95c5d84ca5eef7fca6a4))
|
|
12
|
+
* Warn message on session reset ([#1635](https://github.com/newrelic/newrelic-browser-agent/issues/1635)) ([6e2110f](https://github.com/newrelic/newrelic-browser-agent/commit/6e2110f129ad682444203a9aa65b4a4955bf7636))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* Observe document.body after document is interactive ([#1636](https://github.com/newrelic/newrelic-browser-agent/issues/1636)) ([34c7db3](https://github.com/newrelic/newrelic-browser-agent/commit/34c7db3c695b1950e63970d0a22a2ddacd0481aa))
|
|
18
|
+
|
|
6
19
|
## [1.303.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.302.0...v1.303.0) (2025-11-13)
|
|
7
20
|
|
|
8
21
|
|
|
@@ -151,7 +151,6 @@ const InitModelFn = () => {
|
|
|
151
151
|
collect_fonts: false,
|
|
152
152
|
inline_images: false,
|
|
153
153
|
fix_stylesheets: true,
|
|
154
|
-
// recording config settings
|
|
155
154
|
mask_all_inputs: true,
|
|
156
155
|
// this has a getter/setter to facilitate validation of the selectors
|
|
157
156
|
get mask_text_selector() {
|
|
@@ -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.304.0-rc.0";
|
|
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.304.0-rc.0";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -20,9 +20,11 @@ const checked = new Map();
|
|
|
20
20
|
function isNative(...fns) {
|
|
21
21
|
return fns.every(fn => {
|
|
22
22
|
if (checked.has(fn)) return checked.get(fn);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const fnString = typeof fn === 'function' ? fn.toString() : '';
|
|
24
|
+
const isNative = fnString.includes('[native code]');
|
|
25
|
+
const isNr = fnString.includes('nrWrapper');
|
|
26
|
+
if (!isNative && !isNr) {
|
|
27
|
+
(0, _console.warn)(64, fn?.name || fnString);
|
|
26
28
|
}
|
|
27
29
|
checked.set(fn, isNative);
|
|
28
30
|
return isNative;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.subscribeToPageUnload = subscribeToPageUnload;
|
|
6
7
|
exports.subscribeToVisibilityChange = subscribeToVisibilityChange;
|
|
7
8
|
var _eventListenerOpts = require("../event-listener/event-listener-opts");
|
|
8
9
|
/**
|
|
@@ -25,4 +26,7 @@ function subscribeToVisibilityChange(cb, toHiddenOnly = false, capture, abortSig
|
|
|
25
26
|
}
|
|
26
27
|
cb(document.visibilityState);
|
|
27
28
|
}
|
|
29
|
+
}
|
|
30
|
+
function subscribeToPageUnload(cb, capture, abortSignal) {
|
|
31
|
+
(0, _eventListenerOpts.windowAddEventListener)('pagehide', cb, capture, abortSignal);
|
|
28
32
|
}
|
|
@@ -3,61 +3,291 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.WEBSOCKET_TAG = exports.ADD_EVENT_LISTENER_TAG = void 0;
|
|
7
6
|
exports.wrapWebSocket = wrapWebSocket;
|
|
8
7
|
var _runtime = require("../constants/runtime");
|
|
9
|
-
var _now = require("../timing/now");
|
|
10
|
-
var _load = require("../window/load");
|
|
11
8
|
var _uniqueId = require("../ids/unique-id");
|
|
9
|
+
var _now = require("../timing/now");
|
|
12
10
|
var _nreum = require("../window/nreum");
|
|
11
|
+
var _pageVisibility = require("../window/page-visibility");
|
|
13
12
|
/**
|
|
14
13
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
15
14
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
|
-
const WEBSOCKET_TAG = exports.WEBSOCKET_TAG = 'websocket-';
|
|
19
|
-
const ADD_EVENT_LISTENER_TAG = exports.ADD_EVENT_LISTENER_TAG = 'addEventListener';
|
|
20
17
|
const wrapped = {};
|
|
18
|
+
const openWebSockets = new Set(); // track all instances to close out metrics on page unload
|
|
19
|
+
|
|
21
20
|
function wrapWebSocket(sharedEE) {
|
|
22
|
-
if (wrapped[sharedEE.debugId]++) return sharedEE;
|
|
23
21
|
const originals = (0, _nreum.gosNREUMOriginals)().o;
|
|
24
22
|
if (!originals.WS) return sharedEE;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
const wsEE = sharedEE.get('websockets');
|
|
24
|
+
if (wrapped[wsEE.debugId]++) return wsEE;
|
|
25
|
+
wrapped[wsEE.debugId] = 1; // otherwise, first feature to wrap events
|
|
26
|
+
|
|
27
|
+
// This handles page navigation scenarios where the browser closes WebSockets after pagehide fires
|
|
28
|
+
(0, _pageVisibility.subscribeToPageUnload)(() => {
|
|
29
|
+
const unloadTime = (0, _now.now)();
|
|
30
|
+
openWebSockets.forEach(ws => {
|
|
31
|
+
ws.nrData.closedAt = unloadTime;
|
|
32
|
+
ws.nrData.closeCode = 1001; // Going Away - standard code for page navigation
|
|
33
|
+
ws.nrData.closeReason = 'Page navigating away';
|
|
34
|
+
ws.nrData.closeWasClean = false;
|
|
35
|
+
if (ws.nrData.openedAt) {
|
|
36
|
+
ws.nrData.connectedDuration = unloadTime - ws.nrData.openedAt;
|
|
37
|
+
}
|
|
38
|
+
wsEE.emit('ws', [ws.nrData], ws);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
33
41
|
class WrappedWebSocket extends WebSocket {
|
|
34
42
|
static name = 'WebSocket';
|
|
43
|
+
static toString() {
|
|
44
|
+
// fake native WebSocket when static class is stringified
|
|
45
|
+
return 'function WebSocket() { [native code] }';
|
|
46
|
+
}
|
|
47
|
+
toString() {
|
|
48
|
+
// fake [object WebSocket] when instance is stringified
|
|
49
|
+
return '[object WebSocket]';
|
|
50
|
+
}
|
|
51
|
+
get [Symbol.toStringTag]() {
|
|
52
|
+
// fake [object WebSocket] when Object.prototype.toString.call is used on instance
|
|
53
|
+
return WrappedWebSocket.name;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Private method to tag send, close, and event listener errors with WebSocket ID for JSErrors feature
|
|
57
|
+
#tagError(error) {
|
|
58
|
+
;
|
|
59
|
+
(error.__newrelic ??= {}).socketId = this.nrData.socketId;
|
|
60
|
+
this.nrData.hasErrors ??= true;
|
|
61
|
+
}
|
|
35
62
|
constructor(...args) {
|
|
36
63
|
super(...args);
|
|
37
|
-
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.addEventListener(evt, function (e) {
|
|
44
|
-
this.report(ADD_EVENT_LISTENER_TAG, {
|
|
45
|
-
eventType: evt,
|
|
46
|
-
event: e
|
|
47
|
-
});
|
|
64
|
+
/** @type {WebSocketData} */
|
|
65
|
+
this.nrData = new WebSocketData(args[0], args[1]);
|
|
66
|
+
this.addEventListener('open', () => {
|
|
67
|
+
this.nrData.openedAt = (0, _now.now)();
|
|
68
|
+
['protocol', 'extensions', 'binaryType'].forEach(prop => {
|
|
69
|
+
this.nrData[prop] = this[prop];
|
|
48
70
|
});
|
|
71
|
+
openWebSockets.add(this);
|
|
72
|
+
});
|
|
73
|
+
this.addEventListener('message', event => {
|
|
74
|
+
const {
|
|
75
|
+
type,
|
|
76
|
+
size
|
|
77
|
+
} = getDataInfo(event.data);
|
|
78
|
+
this.nrData.messageOrigin ??= event.origin; // the origin of messages thru WS lifetime cannot be changed, so set once is sufficient
|
|
79
|
+
this.nrData.messageCount = (this.nrData.messageCount ?? 0) + 1;
|
|
80
|
+
this.nrData.messageBytes = (this.nrData.messageBytes ?? 0) + size;
|
|
81
|
+
this.nrData.messageBytesMin = Math.min(this.nrData.messageBytesMin ?? Infinity, size);
|
|
82
|
+
this.nrData.messageBytesMax = Math.max(this.nrData.messageBytesMax ?? 0, size);
|
|
83
|
+
if (!(this.nrData.messageTypes ?? '').includes(type)) {
|
|
84
|
+
this.nrData.messageTypes = this.nrData.messageTypes ? "".concat(this.nrData.messageTypes, ",").concat(type) : type;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
this.addEventListener('close', event => {
|
|
88
|
+
this.nrData.closedAt = (0, _now.now)();
|
|
89
|
+
this.nrData.closeCode = event.code;
|
|
90
|
+
this.nrData.closeReason = event.reason;
|
|
91
|
+
this.nrData.closeWasClean = event.wasClean;
|
|
92
|
+
this.nrData.connectedDuration = this.nrData.closedAt - this.nrData.openedAt;
|
|
93
|
+
openWebSockets.delete(this); // remove from tracking set since it's now closed
|
|
94
|
+
wsEE.emit('ws', [this.nrData], this);
|
|
49
95
|
});
|
|
50
96
|
}
|
|
51
|
-
|
|
52
|
-
this
|
|
97
|
+
addEventListener(type, listener, ...rest) {
|
|
98
|
+
const wsInstance = this;
|
|
99
|
+
const wrappedListener = typeof listener === 'function' ? function (...args) {
|
|
100
|
+
try {
|
|
101
|
+
return listener.apply(this, args);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
wsInstance.#tagError(error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
} : listener?.handleEvent ? {
|
|
107
|
+
// case for listener === object with handleEvent
|
|
108
|
+
handleEvent: function (...args) {
|
|
109
|
+
try {
|
|
110
|
+
return listener.handleEvent.apply(listener, args);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
wsInstance.#tagError(error);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} : listener; // case for listener === null
|
|
117
|
+
return super.addEventListener(type, wrappedListener, ...rest);
|
|
118
|
+
}
|
|
119
|
+
send(data) {
|
|
120
|
+
// Only track metrics if the connection is OPEN; data sent in CONNECTING state throws, and data sent in CLOSING/CLOSED states is silently discarded
|
|
121
|
+
if (this.readyState === WebSocket.OPEN) {
|
|
122
|
+
const {
|
|
123
|
+
type,
|
|
124
|
+
size
|
|
125
|
+
} = getDataInfo(data);
|
|
126
|
+
this.nrData.sendCount = (this.nrData.sendCount ?? 0) + 1;
|
|
127
|
+
this.nrData.sendBytes = (this.nrData.sendBytes ?? 0) + size;
|
|
128
|
+
this.nrData.sendBytesMin = Math.min(this.nrData.sendBytesMin ?? Infinity, size);
|
|
129
|
+
this.nrData.sendBytesMax = Math.max(this.nrData.sendBytesMax ?? 0, size);
|
|
130
|
+
if (!(this.nrData.sendTypes ?? '').includes(type)) {
|
|
131
|
+
this.nrData.sendTypes = this.nrData.sendTypes ? "".concat(this.nrData.sendTypes, ",").concat(type) : type;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
53
134
|
try {
|
|
54
|
-
return super.send(
|
|
55
|
-
} catch (
|
|
56
|
-
this
|
|
57
|
-
throw
|
|
135
|
+
return super.send(data);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
this.#tagError(error);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
close(...args) {
|
|
142
|
+
try {
|
|
143
|
+
super.close(...args);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.#tagError(error);
|
|
146
|
+
throw error;
|
|
58
147
|
}
|
|
59
148
|
}
|
|
60
149
|
}
|
|
61
150
|
_runtime.globalScope.WebSocket = WrappedWebSocket;
|
|
62
|
-
return
|
|
151
|
+
return wsEE;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Returns the data type and size of the WebSocket send data
|
|
156
|
+
* @param {*} data - The data being sent
|
|
157
|
+
* @returns {{ type: string, size: number }} - The type name and size in bytes
|
|
158
|
+
*/
|
|
159
|
+
function getDataInfo(data) {
|
|
160
|
+
if (typeof data === 'string') {
|
|
161
|
+
return {
|
|
162
|
+
type: 'string',
|
|
163
|
+
size: new TextEncoder().encode(data).length // efficient way to calculate the # of UTF-8 bytes that WS sends (cannot use string length)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (data instanceof ArrayBuffer) {
|
|
167
|
+
return {
|
|
168
|
+
type: 'ArrayBuffer',
|
|
169
|
+
size: data.byteLength
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (data instanceof Blob) {
|
|
173
|
+
return {
|
|
174
|
+
type: 'Blob',
|
|
175
|
+
size: data.size
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (data instanceof DataView) {
|
|
179
|
+
return {
|
|
180
|
+
type: 'DataView',
|
|
181
|
+
size: data.byteLength
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (ArrayBuffer.isView(data)) {
|
|
185
|
+
return {
|
|
186
|
+
type: 'TypedArray',
|
|
187
|
+
size: data.byteLength
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
type: 'unknown',
|
|
192
|
+
size: 0
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* WebSocket instrumentation data model
|
|
198
|
+
*/
|
|
199
|
+
class WebSocketData {
|
|
200
|
+
/**
|
|
201
|
+
* @param {string} requestedUrl - The URL passed to WebSocket constructor
|
|
202
|
+
* @param {string|string[]} [requestedProtocols] - The protocols passed to WebSocket constructor
|
|
203
|
+
*/
|
|
204
|
+
constructor(requestedUrl, requestedProtocols) {
|
|
205
|
+
/** @type {number} Timestamp when the WebSocket was constructed (relative time); will be time corrected later when timeKeeper is available */
|
|
206
|
+
this.timestamp = (0, _now.now)();
|
|
207
|
+
|
|
208
|
+
/** @type {string} Most current URL when WebSocket was created; relevant for SPA */
|
|
209
|
+
this.currentUrl = window.location.href;
|
|
210
|
+
|
|
211
|
+
/*
|
|
212
|
+
* pageUrl will be set by addEvent later; unlike timestamp and currentUrl, it's not sensitive to *when* it is set.
|
|
213
|
+
* It should not be explicitly defined here as it will overwrite the default provided by Generic Events later.
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
/** @type {string} Unique identifier for this WebSocket connection */
|
|
217
|
+
this.socketId = (0, _uniqueId.generateRandomHexString)(8);
|
|
218
|
+
|
|
219
|
+
/** @type {string} The URL requested for the WebSocket connection */
|
|
220
|
+
this.requestedUrl = requestedUrl;
|
|
221
|
+
|
|
222
|
+
/** @type {string} Comma-separated list of requested protocols */
|
|
223
|
+
this.requestedProtocols = Array.isArray(requestedProtocols) ? requestedProtocols.join(',') : requestedProtocols || '';
|
|
224
|
+
|
|
225
|
+
// Properties set when connection opens
|
|
226
|
+
/** @type {number} [openedAt] Timestamp when connection opened */
|
|
227
|
+
this.openedAt = undefined;
|
|
228
|
+
|
|
229
|
+
/** @type {string} [protocol] The sub-protocol selected by the server */
|
|
230
|
+
this.protocol = undefined;
|
|
231
|
+
|
|
232
|
+
/** @type {string} [extensions] The extensions selected by the server */
|
|
233
|
+
this.extensions = undefined;
|
|
234
|
+
|
|
235
|
+
/** @type {string} [binaryType] The binary type ('blob' or 'arraybuffer') */
|
|
236
|
+
this.binaryType = undefined;
|
|
237
|
+
|
|
238
|
+
// Message received metrics
|
|
239
|
+
/** @type {string} [messageOrigin] Origin of messages (set once) */
|
|
240
|
+
this.messageOrigin = undefined;
|
|
241
|
+
|
|
242
|
+
/** @type {number} [messageCount] Total number of messages received */
|
|
243
|
+
this.messageCount = undefined;
|
|
244
|
+
|
|
245
|
+
/** @type {number} [messageBytes] Total bytes received */
|
|
246
|
+
this.messageBytes = undefined;
|
|
247
|
+
|
|
248
|
+
/** @type {number} [messageBytesMin] Minimum message size received */
|
|
249
|
+
this.messageBytesMin = undefined;
|
|
250
|
+
|
|
251
|
+
/** @type {number} [messageBytesMax] Maximum message size received */
|
|
252
|
+
this.messageBytesMax = undefined;
|
|
253
|
+
|
|
254
|
+
/** @type {string} [messageTypes] Comma-separated list of message types received */
|
|
255
|
+
this.messageTypes = undefined;
|
|
256
|
+
|
|
257
|
+
// Send metrics
|
|
258
|
+
/** @type {number} [sendCount] Total number of messages sent */
|
|
259
|
+
this.sendCount = undefined;
|
|
260
|
+
|
|
261
|
+
/** @type {number} [sendBytes] Total bytes sent */
|
|
262
|
+
this.sendBytes = undefined;
|
|
263
|
+
|
|
264
|
+
/** @type {number} [sendBytesMin] Minimum message size sent */
|
|
265
|
+
this.sendBytesMin = undefined;
|
|
266
|
+
|
|
267
|
+
/** @type {number} [sendBytesMax] Maximum message size sent */
|
|
268
|
+
this.sendBytesMax = undefined;
|
|
269
|
+
|
|
270
|
+
/** @type {string} [sendTypes] Comma-separated list of message types sent */
|
|
271
|
+
this.sendTypes = undefined;
|
|
272
|
+
|
|
273
|
+
// Close metrics
|
|
274
|
+
/** @type {number} [closedAt] Timestamp when connection closed */
|
|
275
|
+
this.closedAt = undefined;
|
|
276
|
+
|
|
277
|
+
/** @type {number} [closeCode] WebSocket close code */
|
|
278
|
+
this.closeCode = undefined;
|
|
279
|
+
|
|
280
|
+
/** @type {string} [closeReason] WebSocket close reason */
|
|
281
|
+
this.closeReason = undefined;
|
|
282
|
+
|
|
283
|
+
/** @type {boolean} [closeWasClean] Whether the connection closed cleanly */
|
|
284
|
+
this.closeWasClean = undefined;
|
|
285
|
+
|
|
286
|
+
/** @type {number} [connectedDuration] Duration of the connection in milliseconds */
|
|
287
|
+
this.connectedDuration = undefined;
|
|
288
|
+
|
|
289
|
+
// Error tracking
|
|
290
|
+
/** @type {boolean} [hasErrors] Whether any errors occurred */
|
|
291
|
+
this.hasErrors = undefined;
|
|
292
|
+
}
|
|
63
293
|
}
|
|
@@ -39,7 +39,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
39
39
|
if (_constants.RESERVED_EVENT_TYPES.includes(eventType)) return (0, _console.warn)(46);
|
|
40
40
|
this.addEvent({
|
|
41
41
|
eventType,
|
|
42
|
-
timestamp: this
|
|
42
|
+
timestamp: this.#toEpoch(timestamp),
|
|
43
43
|
...attributes
|
|
44
44
|
}, target);
|
|
45
45
|
}, this.featureName, this.ee);
|
|
@@ -48,7 +48,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
48
48
|
this.addEvent({
|
|
49
49
|
...attributes,
|
|
50
50
|
eventType: 'PageAction',
|
|
51
|
-
timestamp: this
|
|
51
|
+
timestamp: this.#toEpoch(timestamp),
|
|
52
52
|
timeSinceLoad: timestamp / 1000,
|
|
53
53
|
actionName: name,
|
|
54
54
|
referrerUrl: this.referrerUrl,
|
|
@@ -75,7 +75,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
75
75
|
} = aggregatedUserAction.event;
|
|
76
76
|
const userActionEvent = {
|
|
77
77
|
eventType: 'UserAction',
|
|
78
|
-
timestamp: this
|
|
78
|
+
timestamp: this.#toEpoch(timeStamp),
|
|
79
79
|
action: type,
|
|
80
80
|
actionCount: aggregatedUserAction.count,
|
|
81
81
|
actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
|
|
@@ -161,7 +161,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
161
161
|
this.addEvent({
|
|
162
162
|
...detailObj,
|
|
163
163
|
eventType: 'BrowserPerformance',
|
|
164
|
-
timestamp: this
|
|
164
|
+
timestamp: this.#toEpoch(entry.startTime),
|
|
165
165
|
entryName: entry.name,
|
|
166
166
|
entryDuration: entry.duration,
|
|
167
167
|
entryType: type
|
|
@@ -227,7 +227,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
227
227
|
const event = {
|
|
228
228
|
...entryObject,
|
|
229
229
|
eventType: 'BrowserPerformance',
|
|
230
|
-
timestamp:
|
|
230
|
+
timestamp: this.#toEpoch(entryObject.startTime),
|
|
231
231
|
entryName: (0, _cleanUrl.cleanURL)(name),
|
|
232
232
|
entryDuration: duration,
|
|
233
233
|
firstParty
|
|
@@ -247,13 +247,29 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
247
247
|
const event = {
|
|
248
248
|
...customAttributes,
|
|
249
249
|
eventType: 'BrowserPerformance',
|
|
250
|
-
timestamp:
|
|
250
|
+
timestamp: this.#toEpoch(start),
|
|
251
251
|
entryName: n,
|
|
252
252
|
entryDuration: duration,
|
|
253
253
|
entryType: 'measure'
|
|
254
254
|
};
|
|
255
255
|
this.addEvent(event, target);
|
|
256
256
|
}, this.featureName, this.ee);
|
|
257
|
+
if (agentRef.init.feature_flags.includes('websockets')) {
|
|
258
|
+
(0, _registerHandler.registerHandler)('ws-complete', nrData => {
|
|
259
|
+
const event = {
|
|
260
|
+
...nrData,
|
|
261
|
+
eventType: 'WebSocket',
|
|
262
|
+
timestamp: this.#toEpoch(nrData.timestamp),
|
|
263
|
+
openedAt: this.#toEpoch(nrData.openedAt),
|
|
264
|
+
closedAt: this.#toEpoch(nrData.closedAt)
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Report supportability metrics for WebSocket completion
|
|
268
|
+
this.reportSupportabilityMetric('WebSocket/Completed/Seen');
|
|
269
|
+
this.reportSupportabilityMetric('WebSocket/Completed/Bytes', (0, _stringify.stringify)(event).length);
|
|
270
|
+
this.addEvent(event);
|
|
271
|
+
}, this.featureName, this.ee);
|
|
272
|
+
}
|
|
257
273
|
this.drain();
|
|
258
274
|
});
|
|
259
275
|
}
|
|
@@ -284,7 +300,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
284
300
|
}
|
|
285
301
|
const defaultEventAttributes = {
|
|
286
302
|
/** should be overridden by the event-specific attributes, but just in case -- set it to now() */
|
|
287
|
-
timestamp:
|
|
303
|
+
timestamp: this.#toEpoch((0, _now.now)()),
|
|
288
304
|
/** all generic events require pageUrl(s) */
|
|
289
305
|
pageUrl: (0, _cleanUrl.cleanURL)('' + _runtime.initialLocation),
|
|
290
306
|
currentUrl: (0, _cleanUrl.cleanURL)('' + location),
|
|
@@ -312,7 +328,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
312
328
|
at: this.agentRef.info.atts
|
|
313
329
|
};
|
|
314
330
|
}
|
|
315
|
-
toEpoch(timestamp) {
|
|
331
|
+
#toEpoch(timestamp) {
|
|
316
332
|
return Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp));
|
|
317
333
|
}
|
|
318
334
|
#trackSupportabilityMetrics() {
|
|
@@ -21,6 +21,7 @@ var _wrapFetch = require("../../../common/wrap/wrap-fetch");
|
|
|
21
21
|
var _wrapXhr = require("../../../common/wrap/wrap-xhr");
|
|
22
22
|
var _parseUrl = require("../../../common/url/parse-url");
|
|
23
23
|
var _extractUrl = require("../../../common/url/extract-url");
|
|
24
|
+
var _wrapWebsocket = require("../../../common/wrap/wrap-websocket");
|
|
24
25
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
|
|
25
26
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
26
27
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -29,8 +30,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
29
30
|
static featureName = _constants.FEATURE_NAME;
|
|
30
31
|
constructor(agentRef) {
|
|
31
32
|
super(agentRef, _constants.FEATURE_NAME);
|
|
33
|
+
const websocketsEnabled = agentRef.init.feature_flags.includes('websockets');
|
|
34
|
+
const ufEnabled = agentRef.init.feature_flags.includes('user_frustrations');
|
|
35
|
+
|
|
32
36
|
/** config values that gate whether the generic events aggregator should be imported at all */
|
|
33
|
-
const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.
|
|
37
|
+
const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.performance.resources.enabled, agentRef.init.user_actions.enabled, websocketsEnabled];
|
|
34
38
|
|
|
35
39
|
/** feature specific APIs */
|
|
36
40
|
(0, _addPageAction.setupAddPageActionAPI)(agentRef);
|
|
@@ -38,13 +42,13 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
38
42
|
(0, _finished.setupFinishedAPI)(agentRef);
|
|
39
43
|
(0, _register.setupRegisterAPI)(agentRef);
|
|
40
44
|
(0, _measure.setupMeasureAPI)(agentRef);
|
|
41
|
-
|
|
42
|
-
let historyEE;
|
|
45
|
+
let historyEE, websocketsEE;
|
|
43
46
|
if (_runtime.isBrowserScope && ufEnabled) {
|
|
44
47
|
(0, _wrapFetch.wrapFetch)(this.ee);
|
|
45
48
|
(0, _wrapXhr.wrapXhr)(this.ee);
|
|
46
49
|
historyEE = (0, _wrapHistory.wrapHistory)(this.ee);
|
|
47
50
|
}
|
|
51
|
+
if (websocketsEnabled) websocketsEE = (0, _wrapWebsocket.wrapWebSocket)(this.ee);
|
|
48
52
|
if (_runtime.isBrowserScope) {
|
|
49
53
|
if (agentRef.init.user_actions.enabled) {
|
|
50
54
|
_constants.OBSERVED_EVENTS.forEach(eventType => (0, _eventListenerOpts.windowAddEventListener)(eventType, evt => (0, _handle.handle)('ua', [evt], undefined, this.featureName, this.ee), true));
|
|
@@ -102,6 +106,12 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
102
106
|
});
|
|
103
107
|
}
|
|
104
108
|
}
|
|
109
|
+
if (websocketsEnabled) {
|
|
110
|
+
// this can apply outside browser scope such as in worker
|
|
111
|
+
websocketsEE.on('ws', nrData => {
|
|
112
|
+
(0, _handle.handle)('ws-complete', [nrData], undefined, this.featureName, this.ee);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
105
115
|
try {
|
|
106
116
|
this.removeOnAbort = new AbortController();
|
|
107
117
|
} catch (e) {}
|
|
@@ -184,10 +184,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
184
184
|
if (this.shouldAllowMainAgentToCapture(target)) (0, _handle.handle)('trace-jserror', jsErrorEvent, undefined, _features.FEATURE_NAMES.sessionTrace, this.ee);
|
|
185
185
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
186
186
|
if (this.blocked) return;
|
|
187
|
-
if (err
|
|
187
|
+
if (err.__newrelic?.[this.agentIdentifier]) {
|
|
188
188
|
params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
|
|
189
189
|
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
|
|
190
190
|
}
|
|
191
|
+
if (err.__newrelic?.socketId) {
|
|
192
|
+
customAttributes.socketId = err.__newrelic.socketId;
|
|
193
|
+
}
|
|
191
194
|
if (this.shouldAllowMainAgentToCapture(target)) {
|
|
192
195
|
const softNavInUse = Boolean(this.agentRef.features?.[_features.FEATURE_NAMES.softNav]);
|
|
193
196
|
// Note: the following are subject to potential race cond wherein if the other feature aren't fully initialized, it'll be treated as there being no associated interaction.
|
|
@@ -150,12 +150,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
150
150
|
// webdriver detection
|
|
151
151
|
if (navigator.webdriver) this.storeSupportabilityMetrics('Generic/WebDriver/Detected');
|
|
152
152
|
|
|
153
|
-
// WATCHABLE_WEB_SOCKET_EVENTS.forEach(tag => {
|
|
154
|
-
// registerHandler('buffered-' + WEBSOCKET_TAG + tag, (...args) => {
|
|
155
|
-
// handleWebsocketEvents(this.storeSupportabilityMetrics.bind(this), tag, ...args)
|
|
156
|
-
// }, this.featureName, this.ee)
|
|
157
|
-
// })
|
|
158
|
-
|
|
159
153
|
/** all the harvest metadata metrics need to be evaluated simulataneously at unload time so just temporarily buffer them and dont make SMs immediately from the data */
|
|
160
154
|
(0, _registerHandler.registerHandler)('harvest-metadata', (harvestMetadataObject = {}) => {
|
|
161
155
|
try {
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
7
|
-
var _wrapWebsocket = require("../../common/wrap/wrap-websocket");
|
|
6
|
+
exports.SUPPORTABILITY_METRIC_CHANNEL = exports.SUPPORTABILITY_METRIC = exports.FEATURE_NAME = exports.CUSTOM_METRIC_CHANNEL = exports.CUSTOM_METRIC = void 0;
|
|
8
7
|
var _features = require("../../loaders/features/features");
|
|
9
8
|
/**
|
|
10
9
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
@@ -15,5 +14,4 @@ const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.metrics;
|
|
|
15
14
|
const SUPPORTABILITY_METRIC = exports.SUPPORTABILITY_METRIC = 'sm';
|
|
16
15
|
const CUSTOM_METRIC = exports.CUSTOM_METRIC = 'cm';
|
|
17
16
|
const SUPPORTABILITY_METRIC_CHANNEL = exports.SUPPORTABILITY_METRIC_CHANNEL = 'storeSupportabilityMetrics';
|
|
18
|
-
const CUSTOM_METRIC_CHANNEL = exports.CUSTOM_METRIC_CHANNEL = 'storeEventMetrics';
|
|
19
|
-
const WATCHABLE_WEB_SOCKET_EVENTS = exports.WATCHABLE_WEB_SOCKET_EVENTS = ['new', 'send', 'close', _wrapWebsocket.ADD_EVENT_LISTENER_TAG];
|
|
17
|
+
const CUSTOM_METRIC_CHANNEL = exports.CUSTOM_METRIC_CHANNEL = 'storeEventMetrics';
|
|
@@ -12,21 +12,10 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
12
12
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
13
13
|
* SPDX-License-Identifier: Apache-2.0
|
|
14
14
|
*/
|
|
15
|
-
// import { handle } from '../../../common/event-emitter/handle'
|
|
16
|
-
// import { WEBSOCKET_TAG, wrapWebSocket } from '../../../common/wrap/wrap-websocket'
|
|
17
|
-
|
|
18
15
|
class Instrument extends _instrumentBase.InstrumentBase {
|
|
19
16
|
static featureName = _constants.FEATURE_NAME;
|
|
20
17
|
constructor(agentRef) {
|
|
21
18
|
super(agentRef, _constants.FEATURE_NAME);
|
|
22
|
-
// wrapWebSocket(this.ee) - feb'25 : removing wrapping again to avoid integration issues
|
|
23
|
-
|
|
24
|
-
// WATCHABLE_WEB_SOCKET_EVENTS.forEach((suffix) => {
|
|
25
|
-
// this.ee.on(WEBSOCKET_TAG + suffix, (...args) => {
|
|
26
|
-
// handle('buffered-' + WEBSOCKET_TAG + suffix, [...args], undefined, this.featureName, this.ee)
|
|
27
|
-
// })
|
|
28
|
-
// })
|
|
29
|
-
|
|
30
19
|
if (_runtime.isBrowserScope) {
|
|
31
20
|
document.addEventListener('securitypolicyviolation', e => {
|
|
32
21
|
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['Generic/CSPViolation/Detected'], undefined, this.featureName, this.ee);
|
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.PageViewTiming = exports.Instrument = void 0;
|
|
7
7
|
var _handle = require("../../../common/event-emitter/handle");
|
|
8
8
|
var _pageVisibility = require("../../../common/window/page-visibility");
|
|
9
|
-
var _eventListenerOpts = require("../../../common/event-listener/event-listener-opts");
|
|
10
9
|
var _instrumentBase = require("../../utils/instrument-base");
|
|
11
10
|
var _constants = require("../constants");
|
|
12
11
|
var _runtime = require("../../../common/constants/runtime");
|
|
@@ -25,7 +24,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
25
24
|
(0, _pageVisibility.subscribeToVisibilityChange)(() => (0, _handle.handle)('docHidden', [(0, _now.now)()], undefined, _constants.FEATURE_NAME, this.ee), true);
|
|
26
25
|
|
|
27
26
|
// Window fires its pagehide event (typically on navigation--this occurrence is a *subset* of vis change); don't defer this unless it's guarantee it cannot happen before load(?)
|
|
28
|
-
(0,
|
|
27
|
+
(0, _pageVisibility.subscribeToPageUnload)(() => (0, _handle.handle)('winPagehide', [(0, _now.now)()], undefined, _constants.FEATURE_NAME, this.ee));
|
|
29
28
|
this.importAggregator(agentRef, () => Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "page_view_timing-aggregate" */'../aggregate'))));
|
|
30
29
|
}
|
|
31
30
|
}
|
|
@@ -133,7 +133,8 @@ class Recorder {
|
|
|
133
133
|
inlineImages: inline_images,
|
|
134
134
|
collectFonts: collect_fonts,
|
|
135
135
|
checkoutEveryNms: _constants.CHECKOUT_MS[mode],
|
|
136
|
-
recordAfter: 'DOMContentLoaded'
|
|
136
|
+
recordAfter: 'DOMContentLoaded',
|
|
137
|
+
slimDOMOptions: 'all'
|
|
137
138
|
});
|
|
138
139
|
} catch (err) {
|
|
139
140
|
this.ee.emit('internal-error', [err]);
|