@newrelic/browser-agent 1.301.0 → 1.302.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 +13 -0
- package/dist/cjs/common/config/init-types.js +1 -1
- package/dist/cjs/common/config/init.js +7 -1
- package/dist/cjs/common/config/runtime.js +1 -1
- package/dist/cjs/common/constants/agent-constants.js +11 -2
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/drain/drain.js +1 -1
- package/dist/cjs/common/harvest/harvester.js +30 -39
- package/dist/cjs/common/util/mfe.js +45 -0
- package/dist/cjs/features/ajax/aggregate/index.js +5 -1
- package/dist/cjs/features/generic_events/aggregate/index.js +9 -8
- package/dist/cjs/features/generic_events/constants.js +3 -1
- package/dist/cjs/features/generic_events/instrument/index.js +38 -32
- package/dist/cjs/features/jserrors/aggregate/index.js +18 -17
- package/dist/cjs/features/logging/aggregate/index.js +19 -15
- package/dist/cjs/features/logging/shared/utils.js +3 -3
- package/dist/cjs/features/page_view_event/aggregate/index.js +3 -33
- package/dist/cjs/features/session_replay/aggregate/index.js +13 -13
- package/dist/cjs/features/session_replay/shared/recorder.js +3 -2
- package/dist/cjs/features/session_trace/aggregate/index.js +0 -2
- package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -2
- package/dist/cjs/features/utils/aggregate-base.js +45 -47
- package/dist/cjs/loaders/api/addPageAction.js +2 -2
- package/dist/cjs/loaders/api/log.js +2 -2
- package/dist/cjs/loaders/api/noticeError.js +2 -2
- package/dist/cjs/loaders/api/register-api-types.js +5 -5
- package/dist/cjs/loaders/api/register.js +80 -97
- package/dist/esm/common/config/init-types.js +1 -1
- package/dist/esm/common/config/init.js +7 -1
- package/dist/esm/common/config/runtime.js +1 -1
- package/dist/esm/common/constants/agent-constants.js +9 -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/drain/drain.js +1 -1
- package/dist/esm/common/harvest/harvester.js +30 -39
- package/dist/esm/common/util/mfe.js +38 -0
- package/dist/esm/features/ajax/aggregate/index.js +5 -1
- package/dist/esm/features/generic_events/aggregate/index.js +9 -8
- package/dist/esm/features/generic_events/constants.js +3 -1
- package/dist/esm/features/generic_events/instrument/index.js +38 -32
- package/dist/esm/features/jserrors/aggregate/index.js +18 -17
- package/dist/esm/features/logging/aggregate/index.js +19 -15
- package/dist/esm/features/logging/shared/utils.js +3 -3
- package/dist/esm/features/page_view_event/aggregate/index.js +3 -33
- package/dist/esm/features/session_replay/aggregate/index.js +13 -13
- package/dist/esm/features/session_replay/shared/recorder.js +3 -2
- package/dist/esm/features/session_trace/aggregate/index.js +0 -2
- package/dist/esm/features/soft_navigations/aggregate/index.js +1 -2
- package/dist/esm/features/utils/aggregate-base.js +46 -48
- package/dist/esm/loaders/api/addPageAction.js +2 -2
- package/dist/esm/loaders/api/log.js +2 -2
- package/dist/esm/loaders/api/noticeError.js +2 -2
- package/dist/esm/loaders/api/register-api-types.js +5 -5
- package/dist/esm/loaders/api/register.js +80 -97
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init-types.d.ts +4 -1
- package/dist/types/common/config/init-types.d.ts.map +1 -1
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/constants/agent-constants.d.ts +7 -4
- package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
- package/dist/types/common/harvest/harvester.d.ts +2 -2
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/common/util/mfe.d.ts +20 -0
- package/dist/types/common/util/mfe.d.ts.map +1 -0
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +2 -2
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/constants.d.ts +1 -0
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +3 -3
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts +3 -3
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/shared/utils.d.ts +2 -2
- package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -4
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +13 -4
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +1 -0
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +13 -5
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/loaders/api/addPageAction.d.ts +1 -1
- package/dist/types/loaders/api/addPageAction.d.ts.map +1 -1
- package/dist/types/loaders/api/log.d.ts +1 -1
- package/dist/types/loaders/api/log.d.ts.map +1 -1
- package/dist/types/loaders/api/noticeError.d.ts +1 -1
- package/dist/types/loaders/api/noticeError.d.ts.map +1 -1
- package/dist/types/loaders/api/register-api-types.d.ts +4 -4
- package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts +8 -1
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/config/init-types.js +1 -1
- package/src/common/config/init.js +3 -1
- package/src/common/config/runtime.js +1 -1
- package/src/common/constants/agent-constants.js +10 -0
- package/src/common/drain/drain.js +1 -1
- package/src/common/harvest/harvester.js +27 -32
- package/src/common/util/mfe.js +35 -0
- package/src/features/ajax/aggregate/index.js +7 -1
- package/src/features/generic_events/aggregate/index.js +9 -8
- package/src/features/generic_events/constants.js +3 -1
- package/src/features/generic_events/instrument/index.js +44 -35
- package/src/features/jserrors/aggregate/index.js +17 -17
- package/src/features/logging/aggregate/index.js +20 -13
- package/src/features/logging/shared/utils.js +3 -3
- package/src/features/page_view_event/aggregate/index.js +3 -28
- package/src/features/session_replay/aggregate/index.js +12 -10
- package/src/features/session_replay/shared/recorder.js +3 -2
- package/src/features/session_trace/aggregate/index.js +0 -2
- package/src/features/soft_navigations/aggregate/index.js +1 -2
- package/src/features/utils/aggregate-base.js +47 -42
- package/src/loaders/api/addPageAction.js +2 -2
- package/src/loaders/api/log.js +2 -2
- package/src/loaders/api/noticeError.js +2 -2
- package/src/loaders/api/register-api-types.js +5 -5
- package/src/loaders/api/register.js +62 -89
- package/dist/cjs/common/util/target.js +0 -34
- package/dist/cjs/features/utils/entity-manager.js +0 -46
- package/dist/cjs/features/utils/event-store-manager.js +0 -174
- package/dist/cjs/loaders/api/register-api.js +0 -165
- package/dist/esm/common/util/target.js +0 -27
- package/dist/esm/features/utils/entity-manager.js +0 -39
- package/dist/esm/features/utils/event-store-manager.js +0 -166
- package/dist/esm/loaders/api/register-api.js +0 -159
- package/dist/types/common/util/target.d.ts +0 -18
- package/dist/types/common/util/target.d.ts.map +0 -1
- package/dist/types/features/utils/entity-manager.d.ts +0 -11
- package/dist/types/features/utils/entity-manager.d.ts.map +0 -1
- package/dist/types/features/utils/event-store-manager.d.ts +0 -85
- package/dist/types/features/utils/event-store-manager.d.ts.map +0 -1
- package/dist/types/loaders/api/register-api.d.ts +0 -14
- package/dist/types/loaders/api/register-api.d.ts.map +0 -1
- package/src/common/util/target.js +0 -27
- package/src/features/utils/entity-manager.js +0 -45
- package/src/features/utils/event-store-manager.js +0 -165
- package/src/loaders/api/register-api.js +0 -152
|
@@ -33,6 +33,13 @@ export class Instrument extends InstrumentBase {
|
|
|
33
33
|
setupFinishedAPI(agentRef);
|
|
34
34
|
setupRegisterAPI(agentRef);
|
|
35
35
|
setupMeasureAPI(agentRef);
|
|
36
|
+
const ufEnabled = agentRef.init.feature_flags.includes('user_frustrations');
|
|
37
|
+
let historyEE;
|
|
38
|
+
if (isBrowserScope && ufEnabled) {
|
|
39
|
+
wrapFetch(this.ee);
|
|
40
|
+
wrapXhr(this.ee);
|
|
41
|
+
historyEE = wrapHistory(this.ee);
|
|
42
|
+
}
|
|
36
43
|
if (isBrowserScope) {
|
|
37
44
|
if (agentRef.init.user_actions.enabled) {
|
|
38
45
|
OBSERVED_EVENTS.forEach(eventType => windowAddEventListener(eventType, evt => handle('ua', [evt], undefined, this.featureName, this.ee), true));
|
|
@@ -46,6 +53,37 @@ export class Instrument extends InstrumentBase {
|
|
|
46
53
|
}
|
|
47
54
|
// Capture is not used here so that we don't get element focus/blur events, only the window's as they do not bubble. They are also not cancellable, so no worries about being front of line.
|
|
48
55
|
);
|
|
56
|
+
if (ufEnabled) {
|
|
57
|
+
globalScope.addEventListener('error', () => {
|
|
58
|
+
handle('uaErr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
59
|
+
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
60
|
+
this.ee.on('open-xhr-start', (args, xhr) => {
|
|
61
|
+
if (!isInternalTraffic(args[1])) {
|
|
62
|
+
xhr.addEventListener('readystatechange', () => {
|
|
63
|
+
if (xhr.readyState === 2) {
|
|
64
|
+
// HEADERS_RECEIVED
|
|
65
|
+
handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
this.ee.on('fetch-start', fetchArguments => {
|
|
71
|
+
if (fetchArguments.length >= 1 && !isInternalTraffic(extractUrl(fetchArguments[0]))) {
|
|
72
|
+
handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
function isInternalTraffic(url) {
|
|
76
|
+
const parsedUrl = parseUrl(url);
|
|
77
|
+
return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port);
|
|
78
|
+
}
|
|
79
|
+
historyEE.on('pushState-end', navigationChange);
|
|
80
|
+
historyEE.on('replaceState-end', navigationChange);
|
|
81
|
+
window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
|
|
82
|
+
window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
|
|
83
|
+
function navigationChange() {
|
|
84
|
+
historyEE.emit('navChange');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
49
87
|
}
|
|
50
88
|
if (agentRef.init.performance.resources.enabled && globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
|
|
51
89
|
const observer = new PerformanceObserver(list => {
|
|
@@ -58,14 +96,6 @@ export class Instrument extends InstrumentBase {
|
|
|
58
96
|
buffered: true
|
|
59
97
|
});
|
|
60
98
|
}
|
|
61
|
-
const historyEE = wrapHistory(this.ee);
|
|
62
|
-
historyEE.on('pushState-end', navigationChange);
|
|
63
|
-
historyEE.on('replaceState-end', navigationChange);
|
|
64
|
-
window.addEventListener('hashchange', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
|
|
65
|
-
window.addEventListener('popstate', navigationChange, eventListenerOpts(true, this.removeOnAbort?.signal));
|
|
66
|
-
function navigationChange() {
|
|
67
|
-
historyEE.emit('navChange');
|
|
68
|
-
}
|
|
69
99
|
}
|
|
70
100
|
try {
|
|
71
101
|
this.removeOnAbort = new AbortController();
|
|
@@ -74,30 +104,6 @@ export class Instrument extends InstrumentBase {
|
|
|
74
104
|
this.removeOnAbort?.abort();
|
|
75
105
|
this.abortHandler = undefined; // weakly allow this abort op to run only once
|
|
76
106
|
};
|
|
77
|
-
globalScope.addEventListener('error', () => {
|
|
78
|
-
handle('uaErr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
79
|
-
}, eventListenerOpts(false, this.removeOnAbort?.signal));
|
|
80
|
-
wrapFetch(this.ee);
|
|
81
|
-
wrapXhr(this.ee);
|
|
82
|
-
this.ee.on('open-xhr-start', (args, xhr) => {
|
|
83
|
-
if (!isInternalTraffic(args[1])) {
|
|
84
|
-
xhr.addEventListener('readystatechange', () => {
|
|
85
|
-
if (xhr.readyState === 2) {
|
|
86
|
-
// HEADERS_RECEIVED
|
|
87
|
-
handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
this.ee.on('fetch-start', fetchArguments => {
|
|
93
|
-
if (fetchArguments.length >= 1 && !isInternalTraffic(extractUrl(fetchArguments[0]))) {
|
|
94
|
-
handle('uaXhr', [], undefined, FEATURE_NAMES.genericEvents, this.ee);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
function isInternalTraffic(url) {
|
|
98
|
-
const parsedUrl = parseUrl(url);
|
|
99
|
-
return agentRef.beacons.includes(parsedUrl.hostname + ':' + parsedUrl.port);
|
|
100
|
-
}
|
|
101
107
|
|
|
102
108
|
/** If any of the sources are active, import the aggregator. otherwise deregister */
|
|
103
109
|
if (genericEventSourceConfigs.some(x => x)) this.importAggregator(agentRef, () => import(/* webpackChunkName: "generic_events-aggregate" */'../aggregate'));else this.deregisterDrain();
|
|
@@ -17,8 +17,7 @@ import { AggregateBase } from '../../utils/aggregate-base';
|
|
|
17
17
|
import { now } from '../../../common/timing/now';
|
|
18
18
|
import { applyFnToProps } from '../../../common/util/traverse';
|
|
19
19
|
import { evaluateInternalError } from './internal-errors';
|
|
20
|
-
import {
|
|
21
|
-
import { warn } from '../../../common/util/console';
|
|
20
|
+
import { getVersion2Attributes } from '../../../common/util/mfe';
|
|
22
21
|
import { buildCauseString } from './cause-string';
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -29,6 +28,9 @@ export class Aggregate extends AggregateBase {
|
|
|
29
28
|
static featureName = FEATURE_NAME;
|
|
30
29
|
constructor(agentRef) {
|
|
31
30
|
super(agentRef, FEATURE_NAME);
|
|
31
|
+
|
|
32
|
+
/** set up agg-level behaviors specific to this feature */
|
|
33
|
+
this.harvestOpts.aggregatorTypes = ['err', 'ierr', 'xhr']; // the types in EventAggregator this feature cares about
|
|
32
34
|
this.stackReported = {};
|
|
33
35
|
this.observedAt = {};
|
|
34
36
|
this.pageviewReported = {};
|
|
@@ -41,8 +43,6 @@ export class Aggregate extends AggregateBase {
|
|
|
41
43
|
register('ierr', (...args) => this.storeError(...args), this.featureName, this.ee);
|
|
42
44
|
register('softNavFlush', (interactionId, wasFinished, softNavAttrs, interactionEndTime) => this.onSoftNavNotification(interactionId, wasFinished, softNavAttrs, interactionEndTime), this.featureName, this.ee); // when an ixn is done or cancelled
|
|
43
45
|
|
|
44
|
-
this.harvestOpts.aggregatorTypes = ['err', 'ierr', 'xhr']; // the types in EventAggregator this feature cares about
|
|
45
|
-
|
|
46
46
|
// 0 == off, 1 == on
|
|
47
47
|
this.waitForFlags(['err']).then(([errFlag]) => {
|
|
48
48
|
if (errFlag) {
|
|
@@ -102,10 +102,8 @@ export class Aggregate extends AggregateBase {
|
|
|
102
102
|
* @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
|
|
103
103
|
* @returns
|
|
104
104
|
*/
|
|
105
|
-
storeError(err, time, internal, customAttributes, hasReplay, swallowReason,
|
|
105
|
+
storeError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
|
|
106
106
|
if (!err) return;
|
|
107
|
-
const target = this.agentRef.runtime.entityManager.get(targetEntityGuid);
|
|
108
|
-
if (!target) return warn(56, this.featureName);
|
|
109
107
|
// are we in an interaction
|
|
110
108
|
time = time || now();
|
|
111
109
|
let filterOutput;
|
|
@@ -143,7 +141,7 @@ export class Aggregate extends AggregateBase {
|
|
|
143
141
|
if (filterOutput?.group) params.errorGroup = filterOutput.group;
|
|
144
142
|
|
|
145
143
|
// Should only decorate "hasReplay" for the container agent, so check if the target matches the config
|
|
146
|
-
if (hasReplay &&
|
|
144
|
+
if (hasReplay && !target) params.hasReplay = hasReplay;
|
|
147
145
|
/**
|
|
148
146
|
* The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
|
|
149
147
|
* stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
|
|
@@ -178,14 +176,14 @@ export class Aggregate extends AggregateBase {
|
|
|
178
176
|
|
|
179
177
|
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
180
178
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes];
|
|
181
|
-
if (this.shouldAllowMainAgentToCapture(
|
|
179
|
+
if (this.shouldAllowMainAgentToCapture(target)) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee);
|
|
182
180
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
183
181
|
if (this.blocked) return;
|
|
184
182
|
if (err?.__newrelic?.[this.agentIdentifier]) {
|
|
185
183
|
params._interactionId = err.__newrelic[this.agentIdentifier].interactionId;
|
|
186
184
|
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId;
|
|
187
185
|
}
|
|
188
|
-
if (this.shouldAllowMainAgentToCapture(
|
|
186
|
+
if (this.shouldAllowMainAgentToCapture(target)) {
|
|
189
187
|
const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav]);
|
|
190
188
|
// 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.
|
|
191
189
|
// They each will also tack on their respective properties to the params object as part of the decision flow.
|
|
@@ -206,11 +204,14 @@ export class Aggregate extends AggregateBase {
|
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
// always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
|
|
209
|
-
if (
|
|
207
|
+
if (target) this.#storeJserrorForHarvest([...jsErrorEvent, target], false, params._softNavAttributes);
|
|
210
208
|
}
|
|
211
209
|
#storeJserrorForHarvest(errorInfoArr, softNavOccurredFinished, softNavCustomAttrs = {}) {
|
|
212
|
-
let [type, bucketHash, params, newMetrics, localAttrs,
|
|
213
|
-
const allCustomAttrs = {
|
|
210
|
+
let [type, bucketHash, params, newMetrics, localAttrs, target] = errorInfoArr;
|
|
211
|
+
const allCustomAttrs = {
|
|
212
|
+
/** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
|
|
213
|
+
...getVersion2Attributes(target, this)
|
|
214
|
+
};
|
|
214
215
|
if (softNavOccurredFinished) {
|
|
215
216
|
Object.entries(softNavCustomAttrs).forEach(([k, v]) => setCustom(k, v)); // when an ixn finishes, it'll include stuff in jsAttributes + attrs specific to the ixn
|
|
216
217
|
bucketHash += params.browserInteractionId;
|
|
@@ -225,7 +226,7 @@ export class Aggregate extends AggregateBase {
|
|
|
225
226
|
|
|
226
227
|
const jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
|
|
227
228
|
const aggregateHash = bucketHash + ':' + jsAttributesHash;
|
|
228
|
-
this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]
|
|
229
|
+
this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]);
|
|
229
230
|
function setCustom(key, val) {
|
|
230
231
|
allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
|
|
231
232
|
}
|
|
@@ -234,11 +235,11 @@ export class Aggregate extends AggregateBase {
|
|
|
234
235
|
/**
|
|
235
236
|
* If the event lacks an entityGuid (the default behavior), the main agent should capture the data. If the data is assigned to a sub-entity target
|
|
236
237
|
* the main agent should not capture events unless it is configured to do so.
|
|
237
|
-
* @param {string}
|
|
238
|
+
* @param {string} target - the context object for the event
|
|
238
239
|
* @returns {boolean} - whether the main agent should capture the event to its internal target
|
|
239
240
|
*/
|
|
240
|
-
shouldAllowMainAgentToCapture(
|
|
241
|
-
return !
|
|
241
|
+
shouldAllowMainAgentToCapture(target) {
|
|
242
|
+
return !target || this.agentRef.init.api.duplicate_registered_data;
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
// TO-DO: Remove this function when old spa is taken out. #storeJserrorForHarvest handles the work with the softnav feature.
|
|
@@ -10,15 +10,18 @@ import { FEATURE_NAME, LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS, LOGGING_MODE }
|
|
|
10
10
|
import { Log } from '../shared/log';
|
|
11
11
|
import { isValidLogLevel } from '../shared/utils';
|
|
12
12
|
import { applyFnToProps } from '../../../common/util/traverse';
|
|
13
|
-
import { isContainerAgentTarget } from '../../../common/util/target';
|
|
14
13
|
import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants';
|
|
15
14
|
import { ABORT_REASONS } from '../../session_replay/constants';
|
|
16
15
|
import { canEnableSessionTracking } from '../../utils/feature-gates';
|
|
16
|
+
import { getVersion2Attributes } from '../../../common/util/mfe';
|
|
17
17
|
export class Aggregate extends AggregateBase {
|
|
18
18
|
static featureName = FEATURE_NAME;
|
|
19
19
|
constructor(agentRef) {
|
|
20
20
|
super(agentRef, FEATURE_NAME);
|
|
21
21
|
this.isSessionTrackingEnabled = canEnableSessionTracking(agentRef.init) && agentRef.runtime.session;
|
|
22
|
+
|
|
23
|
+
/** set up agg-level behaviors specific to this feature */
|
|
24
|
+
this.harvestOpts.raw = true;
|
|
22
25
|
super.customAttributesAreSeparate = true;
|
|
23
26
|
|
|
24
27
|
// The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
|
|
@@ -29,7 +32,6 @@ export class Aggregate extends AggregateBase {
|
|
|
29
32
|
if (this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
|
|
30
33
|
if (this.loggingMode !== LOGGING_MODE.OFF && data.loggingMode === LOGGING_MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);else this.loggingMode = data.loggingMode;
|
|
31
34
|
});
|
|
32
|
-
this.harvestOpts.raw = true;
|
|
33
35
|
this.waitForFlags(['log']).then(([loggingMode]) => {
|
|
34
36
|
const session = this.agentRef.runtime.session ?? {};
|
|
35
37
|
if (this.loggingMode === LOGGING_MODE.OFF || session.isNew && loggingMode === LOGGING_MODE.OFF) {
|
|
@@ -56,10 +58,14 @@ export class Aggregate extends AggregateBase {
|
|
|
56
58
|
loggingMode: this.loggingMode
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
|
-
handleLog(timestamp, message, attributes = {}, level = LOG_LEVELS.INFO,
|
|
60
|
-
if (!this.agentRef.runtime.entityManager.get(targetEntityGuid)) return warn(56, this.featureName);
|
|
61
|
+
handleLog(timestamp, message, attributes = {}, level = LOG_LEVELS.INFO, target) {
|
|
61
62
|
if (this.blocked || !this.loggingMode) return;
|
|
62
63
|
if (!attributes || typeof attributes !== 'object') attributes = {};
|
|
64
|
+
attributes = {
|
|
65
|
+
...attributes,
|
|
66
|
+
/** Specific attributes only supplied if harvesting to endpoint version 2 */
|
|
67
|
+
...getVersion2Attributes(target, this)
|
|
68
|
+
};
|
|
63
69
|
if (typeof level === 'string') level = level.toUpperCase();
|
|
64
70
|
if (!isValidLogLevel(level)) return warn(30, level);
|
|
65
71
|
if (this.loggingMode < (LOGGING_MODE[level] || Infinity)) {
|
|
@@ -83,10 +89,9 @@ export class Aggregate extends AggregateBase {
|
|
|
83
89
|
}
|
|
84
90
|
if (typeof message !== 'string' || !message) return warn(32);
|
|
85
91
|
const log = new Log(Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp)), message, attributes, level);
|
|
86
|
-
this.events.add(log
|
|
92
|
+
this.events.add(log);
|
|
87
93
|
}
|
|
88
|
-
serializer(eventBuffer
|
|
89
|
-
const target = this.agentRef.runtime.entityManager.get(targetEntityGuid);
|
|
94
|
+
serializer(eventBuffer) {
|
|
90
95
|
const sessionEntity = this.agentRef.runtime.session;
|
|
91
96
|
return [{
|
|
92
97
|
common: {
|
|
@@ -94,19 +99,19 @@ export class Aggregate extends AggregateBase {
|
|
|
94
99
|
attributes: {
|
|
95
100
|
...this.agentRef.info.jsAttributes,
|
|
96
101
|
// user-provided custom attributes
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
...(this.harvestEndpointVersion === 1 && {
|
|
103
|
+
'entity.guid': this.agentRef.runtime.appMetadata.agents[0].entityGuid,
|
|
104
|
+
appId: this.agentRef.info.applicationID
|
|
105
|
+
}),
|
|
99
106
|
...(sessionEntity && {
|
|
100
107
|
session: sessionEntity.state.value || '0',
|
|
101
108
|
// The session ID that we generate and keep across page loads
|
|
102
|
-
hasReplay: sessionEntity.state.sessionReplayMode === 1
|
|
109
|
+
hasReplay: sessionEntity.state.sessionReplayMode === 1,
|
|
103
110
|
// True if a session replay recording is running
|
|
104
111
|
hasTrace: sessionEntity.state.sessionTraceMode === 1 // True if a session trace recording is running
|
|
105
112
|
}),
|
|
106
113
|
ptid: this.agentRef.runtime.ptid,
|
|
107
114
|
// page trace id
|
|
108
|
-
appId: target.applicationID || this.agentRef.info.applicationID,
|
|
109
|
-
// Application ID from info object,
|
|
110
115
|
standalone: Boolean(this.agentRef.info.sa),
|
|
111
116
|
// copy paste (true) vs APM (false)
|
|
112
117
|
agentVersion: this.agentRef.runtime.version,
|
|
@@ -121,10 +126,9 @@ export class Aggregate extends AggregateBase {
|
|
|
121
126
|
logs: applyFnToProps(eventBuffer, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
|
|
122
127
|
}];
|
|
123
128
|
}
|
|
124
|
-
queryStringsBuilder(
|
|
125
|
-
const target = this.agentRef.runtime.entityManager.get(targetEntityGuid);
|
|
129
|
+
queryStringsBuilder() {
|
|
126
130
|
return {
|
|
127
|
-
browser_monitoring_key:
|
|
131
|
+
browser_monitoring_key: this.agentRef.info.licenseKey
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -13,11 +13,11 @@ import { LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS } from '../constants';
|
|
|
13
13
|
* @param {string} message - the log message string
|
|
14
14
|
* @param {{[key: string]: *}} customAttributes - The log's custom attributes if any
|
|
15
15
|
* @param {enum} level - the log level enum
|
|
16
|
-
* @param {object=}
|
|
16
|
+
* @param {object=} target - the optional target provided by an api call
|
|
17
17
|
*/
|
|
18
|
-
export function bufferLog(ee, message, customAttributes = {}, level = LOG_LEVELS.INFO,
|
|
18
|
+
export function bufferLog(ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, target, timestamp = now()) {
|
|
19
19
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ["API/logging/".concat(level.toLowerCase(), "/called")], undefined, FEATURE_NAMES.metrics, ee);
|
|
20
|
-
handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level,
|
|
20
|
+
handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, target], undefined, FEATURE_NAMES.logging, ee);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -17,8 +17,6 @@ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
|
|
|
17
17
|
import { now } from '../../../common/timing/now';
|
|
18
18
|
import { TimeKeeper } from '../../../common/timing/time-keeper';
|
|
19
19
|
import { applyFnToProps } from '../../../common/util/traverse';
|
|
20
|
-
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
21
|
-
import { isContainerAgentTarget } from '../../../common/util/target';
|
|
22
20
|
export class Aggregate extends AggregateBase {
|
|
23
21
|
static featureName = CONSTANTS.FEATURE_NAME;
|
|
24
22
|
constructor(agentRef) {
|
|
@@ -27,9 +25,6 @@ export class Aggregate extends AggregateBase {
|
|
|
27
25
|
this.firstByteToWindowLoad = 0; // our "frontend" duration
|
|
28
26
|
this.firstByteToDomContent = 0; // our "dom processing" duration
|
|
29
27
|
|
|
30
|
-
registerHandler('send-rum', (customAttibutes, target) => {
|
|
31
|
-
this.sendRum(customAttibutes, target);
|
|
32
|
-
}, this.featureName, this.ee);
|
|
33
28
|
if (!isValid(agentRef.info)) {
|
|
34
29
|
this.ee.abort();
|
|
35
30
|
return warn(43);
|
|
@@ -57,7 +52,7 @@ export class Aggregate extends AggregateBase {
|
|
|
57
52
|
*
|
|
58
53
|
* @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
|
|
59
54
|
* @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
|
|
60
|
-
* @param {*} target The target to harvest to
|
|
55
|
+
* @param {*} target The target to harvest to
|
|
61
56
|
*/
|
|
62
57
|
sendRum(customAttributes = this.agentRef.info.jsAttributes, target = {
|
|
63
58
|
licenseKey: this.agentRef.info.licenseKey,
|
|
@@ -120,7 +115,7 @@ export class Aggregate extends AggregateBase {
|
|
|
120
115
|
this.rumStartTime = now();
|
|
121
116
|
this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
122
117
|
directSend: {
|
|
123
|
-
|
|
118
|
+
target,
|
|
124
119
|
payload: {
|
|
125
120
|
qs: queryParameters,
|
|
126
121
|
body
|
|
@@ -133,8 +128,7 @@ export class Aggregate extends AggregateBase {
|
|
|
133
128
|
postHarvestCleanup({
|
|
134
129
|
status,
|
|
135
130
|
responseText,
|
|
136
|
-
xhr
|
|
137
|
-
targetApp
|
|
131
|
+
xhr
|
|
138
132
|
}) {
|
|
139
133
|
const rumEndTime = now();
|
|
140
134
|
let app, flags;
|
|
@@ -143,14 +137,10 @@ export class Aggregate extends AggregateBase {
|
|
|
143
137
|
app,
|
|
144
138
|
...flags
|
|
145
139
|
} = JSON.parse(responseText));
|
|
146
|
-
this.processEntities(app.agents, targetApp);
|
|
147
140
|
} catch (error) {
|
|
148
141
|
// wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
|
|
149
142
|
warn(53, error);
|
|
150
143
|
}
|
|
151
|
-
|
|
152
|
-
/** Only run agent-wide side-effects if the harvest was for the main agent */
|
|
153
|
-
if (!isContainerAgentTarget(targetApp, this.agentRef)) return;
|
|
154
144
|
if (status >= 400 || status === 0) {
|
|
155
145
|
warn(18, status);
|
|
156
146
|
// Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
|
|
@@ -181,24 +171,4 @@ export class Aggregate extends AggregateBase {
|
|
|
181
171
|
this.agentRef.runtime.harvester.startTimer();
|
|
182
172
|
activateFeatures(flags, this.agentRef);
|
|
183
173
|
}
|
|
184
|
-
processEntities(entities, targetApp) {
|
|
185
|
-
if (!entities || !targetApp) return;
|
|
186
|
-
entities.forEach(agent => {
|
|
187
|
-
const entityManager = this.agentRef.runtime.entityManager;
|
|
188
|
-
const entityGuid = agent.entityGuid;
|
|
189
|
-
const entity = entityManager.get(entityGuid);
|
|
190
|
-
if (entity) return; // already processed
|
|
191
|
-
|
|
192
|
-
if (isContainerAgentTarget(targetApp, this.agentRef)) {
|
|
193
|
-
entityManager.setDefaultEntity({
|
|
194
|
-
...targetApp,
|
|
195
|
-
entityGuid
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
entityManager.set(agent.entityGuid, {
|
|
199
|
-
...targetApp,
|
|
200
|
-
entityGuid
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
174
|
}
|
|
@@ -36,6 +36,8 @@ export class Aggregate extends AggregateBase {
|
|
|
36
36
|
this.gzipper = undefined;
|
|
37
37
|
/** populated with the u8 string lib async */
|
|
38
38
|
this.u8 = undefined;
|
|
39
|
+
/** flips to false if the compressor libraries cannot import */
|
|
40
|
+
this.shouldCompress = true;
|
|
39
41
|
|
|
40
42
|
/** set by BCS response */
|
|
41
43
|
this.entitled = false;
|
|
@@ -207,23 +209,22 @@ export class Aggregate extends AggregateBase {
|
|
|
207
209
|
this.gzipper = gzipSync;
|
|
208
210
|
this.u8 = strToU8;
|
|
209
211
|
} catch (err) {
|
|
210
|
-
|
|
212
|
+
this.shouldCompress = false;
|
|
213
|
+
// compressor failed to load, but we can still try to record without compression as a last ditch effort
|
|
211
214
|
}
|
|
212
215
|
}
|
|
213
|
-
makeHarvestPayload(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (this.mode !== MODE.FULL || this.blocked) return;
|
|
219
|
-
if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return;
|
|
216
|
+
makeHarvestPayload() {
|
|
217
|
+
if (this.mode !== MODE.FULL || this.blocked) return; // harvests should only be made in FULL mode, and not if the feature is blocked
|
|
218
|
+
if (this.shouldCompress && !this.gzipper) return; // if compression is enabled, but the libraries have not loaded, wait for them to load
|
|
219
|
+
if (!this.recorder || !this.timeKeeper?.ready || !(this.recorder.hasSeenSnapshot && this.recorder.hasSeenMeta)) return; // if the recorder or the timekeeper is not ready, or the recorder has not yet seen a snapshot, do not harvest
|
|
220
|
+
|
|
220
221
|
const recorderEvents = this.recorder.getEvents();
|
|
221
222
|
// get the event type and use that to trigger another harvest if needed
|
|
222
223
|
if (!recorderEvents.events.length) return;
|
|
223
224
|
const payload = this.getHarvestContents(recorderEvents);
|
|
224
225
|
if (!payload.body.length) {
|
|
225
226
|
this.recorder.clearBuffer();
|
|
226
|
-
return
|
|
227
|
+
return;
|
|
227
228
|
}
|
|
228
229
|
this.reportSupportabilityMetric('SessionReplay/Harvest/Attempts');
|
|
229
230
|
let len = 0;
|
|
@@ -238,19 +239,18 @@ export class Aggregate extends AggregateBase {
|
|
|
238
239
|
}
|
|
239
240
|
if (len > MAX_PAYLOAD_SIZE) {
|
|
240
241
|
this.abort(ABORT_REASONS.TOO_BIG, len);
|
|
241
|
-
return
|
|
242
|
+
return;
|
|
242
243
|
}
|
|
244
|
+
|
|
243
245
|
// TODO -- Gracefully handle the buffer for retries.
|
|
244
246
|
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
|
|
245
247
|
sessionReplaySentFirstChunk: true
|
|
246
248
|
});
|
|
247
249
|
this.recorder.clearBuffer();
|
|
248
|
-
if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
249
|
-
payloadOutput.payload = payload;
|
|
250
250
|
if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
|
|
251
251
|
warn(59, JSON.stringify(this.agentRef.runtime.session.state));
|
|
252
252
|
}
|
|
253
|
-
return
|
|
253
|
+
return payload;
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
/**
|
|
@@ -42,6 +42,7 @@ export class Recorder {
|
|
|
42
42
|
this.backloggedEvents = new RecorderEvents(this.shouldFix);
|
|
43
43
|
/** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
|
|
44
44
|
this.hasSeenSnapshot = false;
|
|
45
|
+
this.hasSeenMeta = false;
|
|
45
46
|
/** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
|
|
46
47
|
this.lastMeta = false;
|
|
47
48
|
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
@@ -196,9 +197,9 @@ export class Recorder {
|
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
// meta event
|
|
199
|
-
this.events.hasMeta ||= event.type === RRWEB_EVENT_TYPES.Meta;
|
|
200
|
+
this.hasSeenMeta ||= this.events.hasMeta ||= event.type === RRWEB_EVENT_TYPES.Meta;
|
|
200
201
|
// snapshot event
|
|
201
|
-
this.
|
|
202
|
+
this.hasSeenSnapshot ||= this.events.hasSnapshot ||= event.type === RRWEB_EVENT_TYPES.FullSnapshot;
|
|
202
203
|
|
|
203
204
|
//* dont let the EventBuffer class double evaluate the event data size, it's a performance burden and we have special reasons to do it outside the event buffer */
|
|
204
205
|
this.events.add(event, eventBytes);
|
|
@@ -92,8 +92,6 @@ export class Aggregate extends AggregateBase {
|
|
|
92
92
|
sessionTraceMode: this.mode
|
|
93
93
|
});
|
|
94
94
|
this.drain();
|
|
95
|
-
/** try to harvest immediately. This will not send if the trace is not running in FULL mode due to the pre-harvest checks. */
|
|
96
|
-
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
97
95
|
}
|
|
98
96
|
preHarvestChecks() {
|
|
99
97
|
if (this.blocked || this.mode !== MODE.FULL) return; // only allow harvest if running in full mode
|
|
@@ -46,7 +46,6 @@ export class Aggregate extends AggregateBase {
|
|
|
46
46
|
this.waitForFlags(['spa']).then(([spaOn]) => {
|
|
47
47
|
if (spaOn) {
|
|
48
48
|
this.drain();
|
|
49
|
-
setTimeout(() => agentRef.runtime.harvester.triggerHarvestFor(this), 0); // send the IPL ixn on next tick, giving some time for any ajax to finish; we may want to just remove this?
|
|
50
49
|
} else {
|
|
51
50
|
this.blocked = true; // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
|
|
52
51
|
this.deregisterDrain();
|
|
@@ -141,7 +140,7 @@ export class Aggregate extends AggregateBase {
|
|
|
141
140
|
*/
|
|
142
141
|
if (this.interactionInProgress?.isActiveDuring(timestamp)) return this.interactionInProgress;
|
|
143
142
|
let saveIxn;
|
|
144
|
-
const interactionsBuffer = this.interactionsToHarvest.get()
|
|
143
|
+
const interactionsBuffer = this.interactionsToHarvest.get();
|
|
145
144
|
if (!interactionsBuffer) return undefined; // no interactions have been staged yet, so nothing to search through)
|
|
146
145
|
for (let idx = interactionsBuffer.length - 1; idx >= 0; idx--) {
|
|
147
146
|
// reverse search for the latest completed interaction for efficiency
|