@newrelic/browser-agent 1.258.2 → 1.260.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 +20 -0
- package/dist/cjs/cdn/polyfills.js +3 -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/drain/drain.js +1 -1
- package/dist/cjs/common/harvest/harvest-scheduler.js +4 -2
- package/dist/cjs/common/session/session-entity.js +8 -1
- package/dist/cjs/common/timing/time-keeper.js +9 -30
- package/dist/cjs/features/ajax/constants.js +3 -2
- package/dist/cjs/features/jserrors/instrument/index.js +3 -4
- package/dist/cjs/features/metrics/aggregate/index.js +0 -8
- package/dist/cjs/features/page_view_event/aggregate/index.js +8 -6
- package/dist/cjs/features/session_replay/aggregate/index.js +31 -36
- package/dist/cjs/features/session_replay/constants.js +5 -2
- package/dist/cjs/features/session_replay/instrument/index.js +53 -13
- package/dist/cjs/features/session_replay/shared/recorder.js +8 -1
- package/dist/cjs/features/session_replay/shared/utils.js +3 -5
- package/dist/cjs/features/session_trace/aggregate/index.js +181 -527
- package/dist/cjs/features/session_trace/aggregate/trace/node.js +19 -0
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +289 -0
- package/dist/cjs/features/session_trace/constants.js +3 -2
- package/dist/cjs/features/session_trace/instrument/index.js +7 -3
- package/dist/cjs/features/utils/aggregate-base.js +1 -0
- package/dist/cjs/features/utils/feature-gates.js +17 -0
- package/dist/cjs/features/utils/instrument-base.js +2 -1
- package/dist/cjs/loaders/agent-base.js +4 -0
- package/dist/cjs/loaders/api/api-methods.js +1 -1
- package/dist/cjs/loaders/api/api.js +2 -2
- package/dist/cjs/loaders/configure/configure.js +1 -0
- package/dist/esm/cdn/polyfills.js +3 -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/harvest-scheduler.js +4 -2
- package/dist/esm/common/session/session-entity.js +8 -1
- package/dist/esm/common/timing/time-keeper.js +9 -30
- package/dist/esm/features/ajax/constants.js +2 -1
- package/dist/esm/features/jserrors/instrument/index.js +3 -4
- package/dist/esm/features/metrics/aggregate/index.js +0 -8
- package/dist/esm/features/page_view_event/aggregate/index.js +8 -6
- package/dist/esm/features/session_replay/aggregate/index.js +32 -37
- package/dist/esm/features/session_replay/constants.js +4 -1
- package/dist/esm/features/session_replay/instrument/index.js +54 -14
- package/dist/esm/features/session_replay/shared/recorder.js +8 -1
- package/dist/esm/features/session_replay/shared/utils.js +4 -6
- package/dist/esm/features/session_trace/aggregate/index.js +182 -527
- package/dist/esm/features/session_trace/aggregate/trace/node.js +12 -0
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +282 -0
- package/dist/esm/features/session_trace/constants.js +2 -1
- package/dist/esm/features/session_trace/instrument/index.js +7 -3
- package/dist/esm/features/utils/aggregate-base.js +1 -0
- package/dist/esm/features/utils/feature-gates.js +11 -0
- package/dist/esm/features/utils/instrument-base.js +3 -2
- package/dist/esm/loaders/agent-base.js +4 -0
- package/dist/esm/loaders/api/api-methods.js +1 -1
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/esm/loaders/configure/configure.js +1 -0
- package/dist/types/common/harvest/harvest-scheduler.d.ts +1 -0
- package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/timing/time-keeper.d.ts +2 -0
- package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
- package/dist/types/features/ajax/constants.d.ts +1 -0
- package/dist/types/features/ajax/constants.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -0
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/constants.d.ts +3 -0
- package/dist/types/features/session_replay/instrument/index.d.ts +0 -1
- package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/utils.d.ts +1 -1
- package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +39 -52
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/node.d.ts +12 -0
- package/dist/types/features/session_trace/aggregate/trace/node.d.ts.map +1 -0
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +43 -0
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -0
- package/dist/types/features/session_trace/constants.d.ts +1 -0
- package/dist/types/features/session_trace/constants.d.ts.map +1 -1
- package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +1 -0
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/feature-gates.d.ts +2 -0
- package/dist/types/features/utils/feature-gates.d.ts.map +1 -0
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/agent-base.d.ts +1 -0
- package/dist/types/loaders/agent-base.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cdn/polyfills.js +2 -0
- package/src/common/drain/drain.js +1 -1
- package/src/common/harvest/harvest-scheduler.js +4 -2
- package/src/common/session/session-entity.js +3 -1
- package/src/common/timing/time-keeper.js +9 -30
- package/src/features/ajax/constants.js +2 -0
- package/src/features/jserrors/instrument/index.js +3 -4
- package/src/features/metrics/aggregate/index.js +0 -8
- package/src/features/page_view_event/aggregate/index.js +9 -5
- package/src/features/session_replay/aggregate/index.js +30 -39
- package/src/features/session_replay/constants.js +4 -0
- package/src/features/session_replay/instrument/index.js +48 -8
- package/src/features/session_replay/shared/__mocks__/utils.js +0 -1
- package/src/features/session_replay/shared/recorder.js +8 -1
- package/src/features/session_replay/shared/utils.js +4 -7
- package/src/features/session_trace/aggregate/index.js +157 -493
- package/src/features/session_trace/aggregate/trace/node.js +12 -0
- package/src/features/session_trace/aggregate/trace/storage.js +287 -0
- package/src/features/session_trace/constants.js +1 -0
- package/src/features/session_trace/instrument/index.js +7 -2
- package/src/features/utils/__mocks__/feature-gates.js +1 -0
- package/src/features/utils/aggregate-base.js +1 -0
- package/src/features/utils/feature-gates.js +11 -0
- package/src/features/utils/instrument-base.js +3 -2
- package/src/loaders/agent-base.js +4 -0
- package/src/loaders/api/api-methods.js +1 -1
- package/src/loaders/api/api.js +2 -2
- package/src/loaders/configure/configure.js +1 -0
- package/dist/cjs/features/session_replay/shared/replay-mode.js +0 -28
- package/dist/cjs/features/utils/handler-cache.js +0 -70
- package/dist/esm/features/session_replay/shared/replay-mode.js +0 -23
- package/dist/esm/features/utils/handler-cache.js +0 -63
- package/dist/types/features/session_replay/shared/replay-mode.d.ts +0 -9
- package/dist/types/features/session_replay/shared/replay-mode.d.ts.map +0 -1
- package/dist/types/features/utils/handler-cache.d.ts +0 -23
- package/dist/types/features/utils/handler-cache.d.ts.map +0 -1
- package/src/features/session_replay/shared/replay-mode.js +0 -23
- package/src/features/utils/handler-cache.js +0 -65
|
@@ -1,578 +1,233 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
1
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
6
2
|
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
7
|
-
import {
|
|
8
|
-
import { getConfigurationValue, getRuntime } from '../../../common/config/config';
|
|
3
|
+
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
|
|
9
4
|
import { FEATURE_NAME } from '../constants';
|
|
10
|
-
import { HandlerCache } from '../../utils/handler-cache';
|
|
11
|
-
import { getSessionReplayMode } from '../../session_replay/shared/replay-mode';
|
|
12
5
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
6
|
+
import { TraceStorage } from './trace/storage';
|
|
7
|
+
import { obj as encodeObj } from '../../../common/url/encode';
|
|
8
|
+
import { deregisterDrain } from '../../../common/drain/drain';
|
|
9
|
+
import { globalScope } from '../../../common/constants/runtime';
|
|
13
10
|
import { MODE, SESSION_EVENTS } from '../../../common/session/constants';
|
|
14
|
-
import { now } from '../../../common/timing/now';
|
|
15
|
-
import { originTime } from '../../../common/constants/runtime';
|
|
16
|
-
const ignoredEvents = {
|
|
17
|
-
// we find that certain events make the data too noisy to be useful
|
|
18
|
-
global: {
|
|
19
|
-
mouseup: true,
|
|
20
|
-
mousedown: true
|
|
21
|
-
},
|
|
22
|
-
// certain events are present both in the window and in PVT metrics. PVT metrics are prefered so the window events should be ignored
|
|
23
|
-
window: {
|
|
24
|
-
load: true,
|
|
25
|
-
pagehide: true
|
|
26
|
-
},
|
|
27
|
-
// when ajax instrumentation is disabled, all XMLHttpRequest events will return with origin = xhrOriginMissing and should be ignored
|
|
28
|
-
xhrOriginMissing: {
|
|
29
|
-
ignoreAll: true
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
const toAggregate = {
|
|
33
|
-
typing: [1000, 2000],
|
|
34
|
-
scrolling: [100, 1000],
|
|
35
|
-
mousing: [1000, 2000],
|
|
36
|
-
touching: [1000, 2000]
|
|
37
|
-
};
|
|
38
|
-
const MAX_TRACE_DURATION = 10 * 60 * 1000; // 10 minutes
|
|
39
|
-
const REQ_THRESHOLD_TO_SEND = 30;
|
|
40
11
|
const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
41
|
-
|
|
12
|
+
/** Reserved room for query param attrs */
|
|
13
|
+
const QUERY_PARAM_PADDING = 5000;
|
|
42
14
|
export class Aggregate extends AggregateBase {
|
|
43
15
|
static featureName = FEATURE_NAME;
|
|
44
|
-
|
|
45
|
-
constructor(agentIdentifier, aggregator, argsObj) {
|
|
46
|
-
var _this;
|
|
16
|
+
constructor(agentIdentifier, aggregator) {
|
|
47
17
|
super(agentIdentifier, aggregator, FEATURE_NAME);
|
|
48
|
-
_this = this;
|
|
49
18
|
this.agentRuntime = getRuntime(agentIdentifier);
|
|
50
|
-
this.
|
|
51
|
-
this.ptid = '';
|
|
52
|
-
this.trace = {};
|
|
53
|
-
this.nodeCount = 0;
|
|
54
|
-
this.sentTrace = null;
|
|
55
|
-
this.prevStoredEvents = new Set();
|
|
56
|
-
this.harvestTimeSeconds = getConfigurationValue(agentIdentifier, 'session_trace.harvestTimeSeconds') || 10;
|
|
57
|
-
this.maxNodesPerHarvest = getConfigurationValue(agentIdentifier, 'session_trace.maxNodesPerHarvest') || 1000;
|
|
58
|
-
/**
|
|
59
|
-
* Standalone (mode) refers to the legacy version of ST before the idea of 'session' or the Replay feature existed.
|
|
60
|
-
* It has some different behavior vs when used in tandem with replay. */
|
|
61
|
-
this.isStandalone = false;
|
|
62
|
-
const operationalGate = new HandlerCache(); // acts as a controller-intermediary that can enable or disable this feature's collection dynamically
|
|
63
|
-
const sessionEntity = this.agentRuntime.session;
|
|
64
|
-
this.operationalGate = operationalGate;
|
|
19
|
+
this.agentInfo = getInfo(agentIdentifier);
|
|
65
20
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
let seenAnError = false;
|
|
85
|
-
let mostRecentModeKnown;
|
|
86
|
-
this.ee.on(SESSION_EVENTS.UPDATE, (eventType, sessionState) => {
|
|
87
|
-
// this will only have an effect if ST is NOT already in full mode
|
|
88
|
-
if (sessionState.sessionReplayMode === MODE.FULL) switchToFull();
|
|
21
|
+
/** A buffer to hold on to harvested traces in the case that a retry must be made later */
|
|
22
|
+
this.sentTrace = null;
|
|
23
|
+
this.harvestTimeSeconds = getConfigurationValue(agentIdentifier, 'session_trace.harvestTimeSeconds') || 30;
|
|
24
|
+
/** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
|
|
25
|
+
this.entitled = undefined;
|
|
26
|
+
/** A flag used to decide if the 30 node threshold should be ignored on the first harvest to ensure sending on the first payload */
|
|
27
|
+
this.everHarvested = false;
|
|
28
|
+
/** If the harvest module is harvesting */
|
|
29
|
+
this.harvesting = false;
|
|
30
|
+
/** TraceStorage is the mechanism that holds, normalizes and aggregates ST nodes. It will be accessed and purged when harvests occur */
|
|
31
|
+
this.traceStorage = new TraceStorage(this);
|
|
32
|
+
/** This agg needs information about sampling (sts) and entitlements (st) to make the appropriate decisions on running */
|
|
33
|
+
this.waitForFlags(['sts', 'st']).then(_ref => {
|
|
34
|
+
let [stMode, stEntitled] = _ref;
|
|
35
|
+
return this.initialize(stMode, stEntitled);
|
|
89
36
|
});
|
|
37
|
+
}
|
|
90
38
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this
|
|
105
|
-
if (
|
|
106
|
-
this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
|
|
107
|
-
this.#scheduler.runHarvest({
|
|
108
|
-
needResponse: true
|
|
109
|
-
});
|
|
110
|
-
} else {
|
|
111
|
-
controlTraceOp(MODE.FULL);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
if (!sessionEntity) {
|
|
116
|
-
// Since session manager isn't around, do the old Trace behavior of waiting for RUM response to decide feature activation.
|
|
117
|
-
this.isStandalone = true;
|
|
118
|
-
this.waitForFlags(['stn']).then(_ref => {
|
|
119
|
-
let [on] = _ref;
|
|
120
|
-
return controlTraceOp(on);
|
|
121
|
-
}, this.featureName, this.ee);
|
|
122
|
-
} else {
|
|
123
|
-
registerHandler('trace-jserror', () => {
|
|
124
|
-
seenAnError = true;
|
|
125
|
-
switchToFull();
|
|
126
|
-
}, this.featureName, this.ee);
|
|
127
|
-
const stopTracePerm = () => {
|
|
128
|
-
if (sessionEntity.state.sessionTraceMode !== MODE.OFF) sessionEntity.write({
|
|
129
|
-
sessionTraceMode: MODE.OFF
|
|
130
|
-
});
|
|
131
|
-
operationalGate.permanentlyDecide(false);
|
|
132
|
-
if (mostRecentModeKnown === MODE.FULL) this.#scheduler?.runHarvest(); // allow queued nodes (past opGate) to final harvest, unless they were buffered in other modes
|
|
133
|
-
this.#scheduler?.stopTimer(true); // the 'true' arg here will forcibly block any future call to runHarvest, so the last runHarvest above must be prior
|
|
134
|
-
this.#scheduler = null;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// CAUTION: everything inside this promise runs post-load; event subscribers must be pre-load aka synchronous with constructor
|
|
138
|
-
this.waitForFlags(['stn', 'sr']).then(async _ref2 => {
|
|
139
|
-
let [traceOn, replayOn] = _ref2;
|
|
140
|
-
if (!replayOn) {
|
|
141
|
-
// When sr = 0 from BCS, also do the old Trace behavior:
|
|
142
|
-
this.isStandalone = true;
|
|
143
|
-
controlTraceOp(traceOn);
|
|
144
|
-
} else {
|
|
145
|
-
this.ee.on('REPLAY_ABORTED', () => stopTracePerm());
|
|
146
|
-
/* Assuming on page visible that the trace mode is updated from shared session,
|
|
147
|
-
- if trace is turned off from the other page, it should be likewise here.
|
|
148
|
-
- if trace switches to Full mode, harvest should start (prev: Error) if not already running (prev: Full). */
|
|
149
|
-
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
150
|
-
const updatedTraceMode = sessionEntity.state.sessionTraceMode;
|
|
151
|
-
if (updatedTraceMode === MODE.OFF) stopTracePerm();else if (updatedTraceMode === MODE.FULL && this.#scheduler && !this.#scheduler.started) this.#scheduler.runHarvest({
|
|
152
|
-
needResponse: true
|
|
153
|
-
});
|
|
154
|
-
mostRecentModeKnown = updatedTraceMode;
|
|
155
|
-
});
|
|
156
|
-
this.ee.on(SESSION_EVENTS.PAUSE, () => {
|
|
157
|
-
mostRecentModeKnown = sessionEntity.state.sessionTraceMode;
|
|
158
|
-
});
|
|
159
|
-
if (!sessionEntity.isNew) {
|
|
160
|
-
// inherit the same mode as existing session's Trace
|
|
161
|
-
if (sessionEntity.state.sessionReplayMode === MODE.OFF) this.isStandalone = true;
|
|
162
|
-
controlTraceOp(mostRecentModeKnown = sessionEntity.state.sessionTraceMode);
|
|
163
|
-
} else {
|
|
164
|
-
// for new sessions, see the truth table associated with NEWRELIC-8662 wrt the new Trace behavior under session management
|
|
165
|
-
const replayMode = await getSessionReplayMode(agentIdentifier);
|
|
166
|
-
if (replayMode === MODE.OFF) this.isStandalone = true; // without SR, Traces are still subject to old harvest limits
|
|
167
|
-
|
|
168
|
-
let startingMode;
|
|
169
|
-
if (traceOn) {
|
|
170
|
-
// CASE: both trace (entitlement+sampling) & replay (entitlement) flags are true from RUM
|
|
171
|
-
startingMode = MODE.FULL; // always full capture regardless of replay sampling decisions
|
|
172
|
-
} else {
|
|
173
|
-
// CASE: trace flag is off, BUT it must still run if replay is on (possibly)
|
|
174
|
-
// At this point, it's possible that 1 or more exception was thrown, in which case just start in full if Replay originally started in ERROR mode.
|
|
175
|
-
if (replayMode === MODE.ERROR && seenAnError) startingMode = MODE.FULL;else startingMode = replayMode;
|
|
176
|
-
}
|
|
177
|
-
sessionEntity.write({
|
|
178
|
-
sessionTraceMode: mostRecentModeKnown = startingMode
|
|
179
|
-
});
|
|
180
|
-
controlTraceOp(startingMode);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
39
|
+
/** Sets up event listeners, and initializes this module to run in the correct "mode". Can be triggered from a few places, but makes an effort to only set up listeners once */
|
|
40
|
+
initialize(stMode, stEntitled, ignoreSession) {
|
|
41
|
+
var _this = this;
|
|
42
|
+
this.entitled ??= stEntitled;
|
|
43
|
+
if (this.blocked || !this.entitled) return deregisterDrain(this.agentIdentifier, this.featureName);
|
|
44
|
+
if (!this.initialized) {
|
|
45
|
+
// 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.
|
|
46
|
+
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
47
|
+
this.abort();
|
|
48
|
+
});
|
|
49
|
+
// The SessionEntity can have updates (locally or across tabs for SR mode changes), (across tabs for ST mode changes).
|
|
50
|
+
// Those updates should be sync'd here to ensure this page also honors the mode after initialization
|
|
51
|
+
this.ee.on(SESSION_EVENTS.UPDATE, (eventType, sessionState) => {
|
|
52
|
+
// this will only have an effect if ST is NOT already in full mode
|
|
53
|
+
if (this.mode !== MODE.FULL && (sessionState.sessionReplayMode === MODE.FULL || sessionState.sessionTraceMode === MODE.FULL)) this.switchToFull();
|
|
183
54
|
});
|
|
184
55
|
}
|
|
185
|
-
/* --- EoS --- */
|
|
186
56
|
|
|
187
|
-
|
|
57
|
+
/** ST/SR sampling flow in BCS - https://drive.google.com/file/d/19hwt2oft-8Hh4RrjpLqEXfpP_9wYBLcq/view?usp=sharing */
|
|
58
|
+
/** ST will run in the mode provided by BCS if the session IS NEW. If not... it will use the state of the session entity to determine what mode to run in */
|
|
59
|
+
if (!this.agentRuntime.session.isNew && !ignoreSession) this.mode = this.agentRuntime.session.state.sessionTraceMode;else this.mode = stMode;
|
|
60
|
+
this.initialized = true;
|
|
61
|
+
/** If the mode is off, we do not want to hold up draining for other features, so we deregister the feature for now.
|
|
62
|
+
* If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
|
|
63
|
+
if (this.mode === MODE.OFF) return deregisterDrain(this.agentIdentifier, this.featureName);
|
|
64
|
+
this.timeKeeper ??= this.agentRuntime.timeKeeper;
|
|
65
|
+
this.scheduler = new HarvestScheduler('browser/blobs', {
|
|
66
|
+
onFinished: this.onHarvestFinished.bind(this),
|
|
67
|
+
retryDelay: this.harvestTimeSeconds,
|
|
68
|
+
getPayload: this.prepareHarvest.bind(this),
|
|
69
|
+
raw: true
|
|
70
|
+
}, this);
|
|
71
|
+
|
|
72
|
+
/** The handlers set up by the Inst file */
|
|
188
73
|
registerHandler('bst', function () {
|
|
189
|
-
|
|
190
|
-
args[_key] = arguments[_key];
|
|
191
|
-
}
|
|
192
|
-
return operationalGate.settle(() => _this.storeEvent(...args));
|
|
74
|
+
return _this.traceStorage.storeEvent(...arguments);
|
|
193
75
|
}, this.featureName, this.ee);
|
|
194
76
|
registerHandler('bstResource', function () {
|
|
195
|
-
|
|
196
|
-
args[_key2] = arguments[_key2];
|
|
197
|
-
}
|
|
198
|
-
return operationalGate.settle(() => _this.storeResources(...args));
|
|
77
|
+
return _this.traceStorage.storeResources(...arguments);
|
|
199
78
|
}, this.featureName, this.ee);
|
|
200
79
|
registerHandler('bstHist', function () {
|
|
201
|
-
|
|
202
|
-
args[_key3] = arguments[_key3];
|
|
203
|
-
}
|
|
204
|
-
return operationalGate.settle(() => _this.storeHist(...args));
|
|
80
|
+
return _this.traceStorage.storeHist(...arguments);
|
|
205
81
|
}, this.featureName, this.ee);
|
|
206
82
|
registerHandler('bstXhrAgg', function () {
|
|
207
|
-
|
|
208
|
-
args[_key4] = arguments[_key4];
|
|
209
|
-
}
|
|
210
|
-
return operationalGate.settle(() => _this.storeXhrAgg(...args));
|
|
83
|
+
return _this.traceStorage.storeXhrAgg(...arguments);
|
|
211
84
|
}, this.featureName, this.ee);
|
|
212
85
|
registerHandler('bstApi', function () {
|
|
213
|
-
|
|
214
|
-
args[_key5] = arguments[_key5];
|
|
215
|
-
}
|
|
216
|
-
return operationalGate.settle(() => _this.storeSTN(...args));
|
|
86
|
+
return _this.traceStorage.storeSTN(...arguments);
|
|
217
87
|
}, this.featureName, this.ee);
|
|
218
88
|
registerHandler('trace-jserror', function () {
|
|
219
|
-
|
|
220
|
-
args[_key6] = arguments[_key6];
|
|
221
|
-
}
|
|
222
|
-
return operationalGate.settle(() => _this.storeErrorAgg(...args));
|
|
89
|
+
return _this.traceStorage.storeErrorAgg(...arguments);
|
|
223
90
|
}, this.featureName, this.ee);
|
|
224
91
|
registerHandler('pvtAdded', function () {
|
|
225
|
-
|
|
226
|
-
args[_key7] = arguments[_key7];
|
|
227
|
-
}
|
|
228
|
-
return operationalGate.settle(() => _this.processPVT(...args));
|
|
92
|
+
return _this.traceStorage.processPVT(...arguments);
|
|
229
93
|
}, this.featureName, this.ee);
|
|
230
|
-
this.drain();
|
|
231
|
-
}
|
|
232
|
-
startTracing(startupBuffer) {
|
|
233
|
-
let dontStartHarvestYet = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
234
94
|
if (typeof PerformanceNavigationTiming !== 'undefined') {
|
|
235
|
-
this.storeTiming(
|
|
95
|
+
this.traceStorage.storeTiming(globalScope.performance?.getEntriesByType?.('navigation')[0]);
|
|
236
96
|
} else {
|
|
237
|
-
this.storeTiming(
|
|
238
|
-
}
|
|
239
|
-
this.#scheduler = new HarvestScheduler('resources', {
|
|
240
|
-
onFinished: this.#onHarvestFinished.bind(this),
|
|
241
|
-
retryDelay: this.harvestTimeSeconds
|
|
242
|
-
}, this);
|
|
243
|
-
this.#scheduler.harvest.on('resources', this.#prepareHarvest.bind(this));
|
|
244
|
-
if (dontStartHarvestYet === false) this.#scheduler.runHarvest({
|
|
245
|
-
needResponse: true
|
|
246
|
-
}); // sends first stn harvest immediately
|
|
247
|
-
startupBuffer.decide(true); // signal to ALLOW & process data in EE's buffer into internal nodes queued for next harvest
|
|
248
|
-
}
|
|
249
|
-
#onHarvestFinished(result) {
|
|
250
|
-
if (result.sent && result.responseText && !this.ptid) {
|
|
251
|
-
// continue interval harvest only if ptid was returned by server on the first
|
|
252
|
-
this.agentRuntime.ptid = this.ptid = result.responseText;
|
|
253
|
-
this.#scheduler.startTimer(this.harvestTimeSeconds);
|
|
254
|
-
}
|
|
255
|
-
if (result.sent && result.retry && this.sentTrace) {
|
|
256
|
-
// merge previous trace back into buffer to retry for next harvest
|
|
257
|
-
Object.entries(this.sentTrace).forEach(_ref3 => {
|
|
258
|
-
let [name, listOfSTNodes] = _ref3;
|
|
259
|
-
if (this.nodeCount >= this.maxNodesPerHarvest) return;
|
|
260
|
-
this.nodeCount += listOfSTNodes.length;
|
|
261
|
-
this.trace[name] = this.trace[name] ? listOfSTNodes.concat(this.trace[name]) : listOfSTNodes;
|
|
262
|
-
});
|
|
263
|
-
this.sentTrace = null;
|
|
97
|
+
this.traceStorage.storeTiming(globalScope.performance?.timing);
|
|
264
98
|
}
|
|
265
|
-
}
|
|
266
|
-
#prepareHarvest(options) {
|
|
267
|
-
this.prevStoredEvents.clear(); // release references to past events for GC
|
|
268
|
-
if (this.isStandalone) {
|
|
269
|
-
if (this.ptid && now() >= MAX_TRACE_DURATION) {
|
|
270
|
-
// Perform a final harvest once we hit or exceed the max session trace time
|
|
271
|
-
options.isFinalHarvest = true;
|
|
272
|
-
this.operationalGate.permanentlyDecide(false);
|
|
273
|
-
this.#scheduler.stopTimer(true);
|
|
274
|
-
} else if (this.ptid && this.nodeCount <= REQ_THRESHOLD_TO_SEND && !options.isFinalHarvest) {
|
|
275
|
-
// Only harvest when more than some threshold of nodes are pending, after the very first harvest, with the exception of the last outgoing harvest.
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
// -- *cli May '26 - Update: Not rate limiting backgrounded pages either for now.
|
|
280
|
-
// if (this.ptid && document.visibilityState === 'hidden' && this.nodeCount <= REQ_THRESHOLD_TO_SEND) return
|
|
281
99
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
100
|
+
/** Only start actually harvesting if running in full mode at init time */
|
|
101
|
+
if (this.mode === MODE.FULL) this.startHarvesting();else {
|
|
102
|
+
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
103
|
+
registerHandler('trace-jserror', () => {
|
|
104
|
+
if (this.mode === MODE.ERROR) this.switchToFull();
|
|
105
|
+
}, this.featureName, this.ee);
|
|
287
106
|
}
|
|
288
|
-
|
|
107
|
+
this.agentRuntime.session.write({
|
|
108
|
+
sessionTraceMode: this.mode
|
|
109
|
+
});
|
|
110
|
+
this.drain();
|
|
289
111
|
}
|
|
290
112
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
this.
|
|
294
|
-
|
|
113
|
+
/** This module does not auto harvest by default -- it needs to be kicked off. Once this method is called, it will then harvest on an interval */
|
|
114
|
+
startHarvesting() {
|
|
115
|
+
if (this.scheduler.started || this.blocked) return;
|
|
116
|
+
this.scheduler.runHarvest();
|
|
117
|
+
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Called by the harvest scheduler at harvest time to retrieve the payload. This will only actually return a payload if running in full mode */
|
|
121
|
+
prepareHarvest() {
|
|
122
|
+
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
123
|
+
this.traceStorage.prevStoredEvents.clear(); // release references to past events for GC
|
|
124
|
+
if (!this.timeKeeper?.ready) return; // this should likely never happen, but just to be safe, we should never harvest if we cant correct time
|
|
125
|
+
if (this.mode === MODE.OFF && this.traceStorage.nodeCount === 0) return;
|
|
126
|
+
if (this.mode === MODE.ERROR) return; // Trace in this mode should never be harvesting, even on unload
|
|
127
|
+
|
|
128
|
+
/** Get the ST nodes from the traceStorage buffer. This also returns helpful metadata about the payload. */
|
|
129
|
+
const {
|
|
130
|
+
stns,
|
|
131
|
+
earliestTimeStamp,
|
|
132
|
+
latestTimeStamp
|
|
133
|
+
} = this.traceStorage.takeSTNs();
|
|
134
|
+
if (options.retry) {
|
|
135
|
+
this.sentTrace = stns;
|
|
136
|
+
}
|
|
137
|
+
const firstSessionHarvest = !this.agentRuntime.session.state.traceHarvestStarted;
|
|
138
|
+
if (firstSessionHarvest) this.agentRuntime.session.write({
|
|
139
|
+
traceHarvestStarted: true
|
|
295
140
|
});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
141
|
+
const hasReplay = this.agentRuntime.session?.state.sessionReplayMode === 1;
|
|
142
|
+
const endUserId = this.agentInfo?.jsAttributes?.['enduser.id'];
|
|
143
|
+
this.everHarvested = true;
|
|
144
|
+
|
|
145
|
+
/** The blob consumer expects the following and will reject if not supplied:
|
|
146
|
+
* browser_monitoring_key
|
|
147
|
+
* type
|
|
148
|
+
* app_id
|
|
149
|
+
* protocol_version
|
|
150
|
+
* attributes
|
|
151
|
+
*
|
|
152
|
+
* For data that does not fit the schema of the above, it should be url-encoded and placed into `attributes`
|
|
153
|
+
*/
|
|
154
|
+
const agentMetadata = this.agentRuntime.appMetadata?.agents?.[0] || {};
|
|
155
|
+
return {
|
|
156
|
+
qs: {
|
|
157
|
+
browser_monitoring_key: this.agentInfo.licenseKey,
|
|
158
|
+
type: 'BrowserSessionChunk',
|
|
159
|
+
app_id: this.agentInfo.applicationID,
|
|
160
|
+
protocol_version: '0',
|
|
161
|
+
timestamp: this.timeKeeper.convertRelativeTimestamp(earliestTimeStamp),
|
|
162
|
+
attributes: encodeObj({
|
|
163
|
+
...(agentMetadata.entityGuid && {
|
|
164
|
+
entityGuid: agentMetadata.entityGuid
|
|
165
|
+
}),
|
|
166
|
+
harvestId: "".concat(this.agentRuntime.session?.state.value, "_").concat(this.agentRuntime.ptid, "_").concat(this.agentRuntime.harvestCount),
|
|
167
|
+
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
168
|
+
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
169
|
+
// trace payload metadata
|
|
170
|
+
'trace.firstTimestamp': this.timeKeeper.convertRelativeTimestamp(earliestTimeStamp),
|
|
171
|
+
'trace.lastTimestamp': this.timeKeeper.convertRelativeTimestamp(latestTimeStamp),
|
|
172
|
+
'trace.nodes': stns.length,
|
|
173
|
+
'trace.originTimestamp': this.timeKeeper.correctedOriginTime,
|
|
174
|
+
// other payload metadata
|
|
175
|
+
agentVersion: this.agentRuntime.version,
|
|
176
|
+
...(firstSessionHarvest && {
|
|
177
|
+
firstSessionHarvest
|
|
178
|
+
}),
|
|
179
|
+
...(hasReplay && {
|
|
180
|
+
hasReplay
|
|
181
|
+
}),
|
|
182
|
+
ptid: "".concat(this.agentRuntime.ptid),
|
|
183
|
+
session: "".concat(this.agentRuntime.session?.state.value),
|
|
184
|
+
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
185
|
+
...(endUserId && {
|
|
186
|
+
'enduser.id': endUserId
|
|
187
|
+
})
|
|
188
|
+
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
189
|
+
}, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
190
|
+
},
|
|
191
|
+
body: stns
|
|
192
|
+
};
|
|
303
193
|
}
|
|
304
194
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const lck = key.toLowerCase();
|
|
315
|
-
if (lck.indexOf('size') >= 0 || lck.indexOf('status') >= 0) continue;
|
|
316
|
-
|
|
317
|
-
// ignore inherited methods, meaningless 0 values, and bogus timestamps
|
|
318
|
-
// that are in the future (Microsoft Edge seems to sometimes produce these)
|
|
319
|
-
if (!(typeof val === 'number' && val >= 0)) continue;
|
|
320
|
-
val = Math.round(val);
|
|
321
|
-
this.storeSTN({
|
|
322
|
-
n: key,
|
|
323
|
-
s: val,
|
|
324
|
-
e: val,
|
|
325
|
-
o: 'document',
|
|
326
|
-
t: 'timing'
|
|
195
|
+
/** When the harvest scheduler finishes, this callback is executed. It's main purpose is to determine if the payload needs to be retried
|
|
196
|
+
* and if so, it will take all data from the temporary buffer and place it back into the traceStorage module
|
|
197
|
+
*/
|
|
198
|
+
onHarvestFinished(result) {
|
|
199
|
+
if (result.sent && result.retry && this.sentTrace) {
|
|
200
|
+
// merge previous trace back into buffer to retry for next harvest
|
|
201
|
+
Object.entries(this.sentTrace).forEach(_ref2 => {
|
|
202
|
+
let [name, listOfSTNodes] = _ref2;
|
|
203
|
+
this.traceStorage.restoreNode(name, listOfSTNodes);
|
|
327
204
|
});
|
|
205
|
+
this.sentTrace = null;
|
|
328
206
|
}
|
|
329
207
|
}
|
|
330
208
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (this.
|
|
334
|
-
|
|
335
|
-
this.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
s: start,
|
|
339
|
-
e: end,
|
|
340
|
-
t: 'event'
|
|
341
|
-
};
|
|
342
|
-
try {
|
|
343
|
-
// webcomponents-lite.js can trigger an exception on currentEvent.target getter because
|
|
344
|
-
// it does not check currentEvent.currentTarget before calling getRootNode() on it
|
|
345
|
-
evt.o = this.evtOrigin(currentEvent.target, target);
|
|
346
|
-
} catch (e) {
|
|
347
|
-
evt.o = this.evtOrigin(null, target);
|
|
348
|
-
}
|
|
349
|
-
this.storeSTN(evt);
|
|
350
|
-
}
|
|
351
|
-
shouldIgnoreEvent(event, target) {
|
|
352
|
-
const origin = this.evtOrigin(event.target, target);
|
|
353
|
-
if (event.type in ignoredEvents.global) return true;
|
|
354
|
-
if (!!ignoredEvents[origin] && ignoredEvents[origin].ignoreAll) return true;
|
|
355
|
-
return !!(!!ignoredEvents[origin] && event.type in ignoredEvents[origin]);
|
|
356
|
-
}
|
|
357
|
-
evtName(type) {
|
|
358
|
-
switch (type) {
|
|
359
|
-
case 'keydown':
|
|
360
|
-
case 'keyup':
|
|
361
|
-
case 'keypress':
|
|
362
|
-
return 'typing';
|
|
363
|
-
case 'mousemove':
|
|
364
|
-
case 'mouseenter':
|
|
365
|
-
case 'mouseleave':
|
|
366
|
-
case 'mouseover':
|
|
367
|
-
case 'mouseout':
|
|
368
|
-
return 'mousing';
|
|
369
|
-
case 'scroll':
|
|
370
|
-
return 'scrolling';
|
|
371
|
-
case 'touchstart':
|
|
372
|
-
case 'touchmove':
|
|
373
|
-
case 'touchend':
|
|
374
|
-
case 'touchcancel':
|
|
375
|
-
case 'touchenter':
|
|
376
|
-
case 'touchleave':
|
|
377
|
-
return 'touching';
|
|
378
|
-
default:
|
|
379
|
-
return type;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
evtOrigin(t, target) {
|
|
383
|
-
let origin = 'unknown';
|
|
384
|
-
if (t && t instanceof XMLHttpRequest) {
|
|
385
|
-
const params = this.ee.context(t).params;
|
|
386
|
-
if (!params || !params.status || !params.method || !params.host || !params.pathname) return 'xhrOriginMissing';
|
|
387
|
-
origin = params.status + ' ' + params.method + ': ' + params.host + params.pathname;
|
|
388
|
-
} else if (t && typeof t.tagName === 'string') {
|
|
389
|
-
origin = t.tagName.toLowerCase();
|
|
390
|
-
if (t.id) origin += '#' + t.id;
|
|
391
|
-
if (t.className) {
|
|
392
|
-
for (let i = 0; i < t.classList.length; i++) origin += '.' + t.classList[i];
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
if (origin === 'unknown') {
|
|
396
|
-
if (typeof target === 'string') origin = target;else if (target === document) origin = 'document';else if (target === window) origin = 'window';else if (target instanceof FileReader) origin = 'FileReader';
|
|
397
|
-
}
|
|
398
|
-
return origin;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Tracks when the window history API specified by wrap-history is used.
|
|
402
|
-
storeHist(path, old, time) {
|
|
403
|
-
const node = {
|
|
404
|
-
n: 'history.pushState',
|
|
405
|
-
s: time,
|
|
406
|
-
e: time,
|
|
407
|
-
o: path,
|
|
408
|
-
t: old
|
|
409
|
-
};
|
|
410
|
-
this.storeSTN(node);
|
|
411
|
-
}
|
|
412
|
-
#laststart = 0;
|
|
413
|
-
// Processes all the PerformanceResourceTiming entries captured (by observer).
|
|
414
|
-
storeResources(resources) {
|
|
415
|
-
if (!resources || resources.length === 0) return;
|
|
416
|
-
resources.forEach(currentResource => {
|
|
417
|
-
if ((currentResource.fetchStart | 0) <= this.#laststart) return; // don't recollect already-seen resources
|
|
418
|
-
|
|
419
|
-
const parsed = parseUrl(currentResource.name);
|
|
420
|
-
const res = {
|
|
421
|
-
n: currentResource.initiatorType,
|
|
422
|
-
s: currentResource.fetchStart | 0,
|
|
423
|
-
e: currentResource.responseEnd | 0,
|
|
424
|
-
o: parsed.protocol + '://' + parsed.hostname + ':' + parsed.port + parsed.pathname,
|
|
425
|
-
// resource.name is actually a URL so it's the source
|
|
426
|
-
t: currentResource.entryType
|
|
427
|
-
};
|
|
428
|
-
this.storeSTN(res);
|
|
209
|
+
/** Switch from "off" or "error" to full mode (if entitled) */
|
|
210
|
+
switchToFull() {
|
|
211
|
+
if (this.mode === MODE.FULL || !this.entitled || this.blocked) return;
|
|
212
|
+
const prevMode = this.mode;
|
|
213
|
+
this.mode = MODE.FULL;
|
|
214
|
+
this.agentRuntime.session.write({
|
|
215
|
+
sessionTraceMode: this.mode
|
|
429
216
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
// JavascriptError (FEATURE) events pipes into ST here.
|
|
434
|
-
storeErrorAgg(type, name, params, metrics) {
|
|
435
|
-
if (type !== 'err') return; // internal errors are purposefully ignored
|
|
436
|
-
const node = {
|
|
437
|
-
n: 'error',
|
|
438
|
-
s: metrics.time,
|
|
439
|
-
e: metrics.time,
|
|
440
|
-
o: params.message,
|
|
441
|
-
t: params.stackHash
|
|
442
|
-
};
|
|
443
|
-
this.storeSTN(node);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Ajax (FEATURE) events--XML & fetches--pipes into ST here.
|
|
447
|
-
storeXhrAgg(type, name, params, metrics) {
|
|
448
|
-
if (type !== 'xhr') return;
|
|
449
|
-
const node = {
|
|
450
|
-
n: 'Ajax',
|
|
451
|
-
s: metrics.time,
|
|
452
|
-
e: metrics.time + metrics.duration,
|
|
453
|
-
o: params.status + ' ' + params.method + ': ' + params.host + params.pathname,
|
|
454
|
-
t: 'ajax'
|
|
455
|
-
};
|
|
456
|
-
this.storeSTN(node);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Central function called by all the other store__ & addToTrace API to append a trace node.
|
|
460
|
-
storeSTN(stn) {
|
|
461
|
-
if (this.nodeCount >= this.maxNodesPerHarvest) {
|
|
462
|
-
// limit the amount of pending data awaiting next harvest
|
|
463
|
-
if (this.isStandalone || this.agentRuntime.session.state.sessionTraceMode !== MODE.ERROR) return;
|
|
464
|
-
const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
|
|
465
|
-
if (openedSpace === 0) return;
|
|
466
|
-
}
|
|
467
|
-
if (this.isStandalone && now() >= MAX_TRACE_DURATION) {
|
|
468
|
-
return;
|
|
217
|
+
if (prevMode === MODE.OFF || !this.initialized) return this.initialize(this.mode, this.entitled);
|
|
218
|
+
if (this.initialized) {
|
|
219
|
+
this.traceStorage.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
|
|
469
220
|
}
|
|
470
|
-
|
|
471
|
-
this.nodeCount++;
|
|
221
|
+
this.startHarvesting();
|
|
472
222
|
}
|
|
473
223
|
|
|
474
|
-
/**
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
let prunedNodes = 0;
|
|
481
|
-
const cutoffHighResTime = Math.max(now() - lookbackDuration, 0);
|
|
482
|
-
Object.keys(this.trace).forEach(nameCategory => {
|
|
483
|
-
const nodeList = this.trace[nameCategory];
|
|
484
|
-
/* Notice nodes are appending under their name's list as they end and are stored. This means each list is already (roughly) sorted in chronological order by end time.
|
|
485
|
-
* This isn't exact since nodes go through some processing & EE handlers chain, but it's close enough as we still capture nodes whose duration overlaps the lookback window.
|
|
486
|
-
* ASSUMPTION: all 'end' timings stored are relative to timeOrigin (DOMHighResTimeStamp) and not Unix epoch based. */
|
|
487
|
-
let cutoffIdx = nodeList.findIndex(node => cutoffHighResTime <= node.e);
|
|
488
|
-
if (cutoffIdx === 0) return;else if (cutoffIdx < 0) {
|
|
489
|
-
// whole list falls outside lookback window and is irrelevant
|
|
490
|
-
cutoffIdx = nodeList.length;
|
|
491
|
-
delete this.trace[nameCategory];
|
|
492
|
-
} else nodeList.splice(0, cutoffIdx); // chop off everything outside our window i.e. before the last <lookbackDuration> timeframe
|
|
493
|
-
|
|
494
|
-
this.nodeCount -= cutoffIdx;
|
|
495
|
-
prunedNodes += cutoffIdx;
|
|
224
|
+
/** Stop running for the remainder of the page lifecycle */
|
|
225
|
+
abort() {
|
|
226
|
+
this.blocked = true;
|
|
227
|
+
this.mode = MODE.OFF;
|
|
228
|
+
this.agentRuntime.session.write({
|
|
229
|
+
sessionTraceMode: this.mode
|
|
496
230
|
});
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// Used by session trace's harvester to create the payload body.
|
|
501
|
-
takeSTNs(retry) {
|
|
502
|
-
if (!this.resourceObserver) {
|
|
503
|
-
// if PO isn't supported, this checks resourcetiming buffer every harvest.
|
|
504
|
-
this.storeResources(window.performance.getEntriesByType('resource'));
|
|
505
|
-
}
|
|
506
|
-
let earliestTimeStamp = Infinity;
|
|
507
|
-
const stns = Object.entries(this.trace).flatMap(_ref4 => {
|
|
508
|
-
let [name, listOfSTNodes] = _ref4;
|
|
509
|
-
// basically take the "this.trace" map-obj and concat all the list-type values
|
|
510
|
-
const oldestNodeTS = listOfSTNodes.reduce((acc, next) => !acc || next.s < acc ? next.s : acc, undefined);
|
|
511
|
-
if (oldestNodeTS < earliestTimeStamp) earliestTimeStamp = oldestNodeTS;
|
|
512
|
-
if (!(name in toAggregate)) return listOfSTNodes;
|
|
513
|
-
// Special processing for event nodes dealing with user inputs:
|
|
514
|
-
const reindexByOriginFn = this.smearEvtsByOrigin(name);
|
|
515
|
-
const partitionListByOriginMap = listOfSTNodes.sort((a, b) => a.s - b.s).reduce(reindexByOriginFn, {});
|
|
516
|
-
return Object.values(partitionListByOriginMap).flat(); // join the partitions back into 1-D, now ordered by origin then start time
|
|
517
|
-
}, this);
|
|
518
|
-
if (stns.length === 0) return {};
|
|
519
|
-
if (retry) {
|
|
520
|
-
this.sentTrace = this.trace;
|
|
521
|
-
}
|
|
522
|
-
this.trace = {};
|
|
523
|
-
this.nodeCount = 0;
|
|
524
|
-
let firstHarvestOfSession;
|
|
525
|
-
if (this.agentRuntime.session) {
|
|
526
|
-
const isFirstPayload = !this.agentRuntime.session.state.traceHarvestStarted;
|
|
527
|
-
firstHarvestOfSession = {
|
|
528
|
-
fsh: Number(isFirstPayload)
|
|
529
|
-
}; // converted to '0' | '1'
|
|
530
|
-
if (isFirstPayload) this.agentRuntime.session.write({
|
|
531
|
-
traceHarvestStarted: true
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
return {
|
|
535
|
-
qs: {
|
|
536
|
-
st: originTime,
|
|
537
|
-
/** hr === "hasReplay" in NR1, standalone is always checked and processed before harvesting
|
|
538
|
-
* so a race condition between ST and SR states should not be a concern if implemented here */
|
|
539
|
-
hr: Number(!this.isStandalone),
|
|
540
|
-
/** fts === "firstTimestamp" in NR1, indicates what the earliest NODE timestamp was
|
|
541
|
-
* so that blob parsing doesn't need to happen to support UI/API functions */
|
|
542
|
-
fts: originTime + earliestTimeStamp,
|
|
543
|
-
/** n === "nodeCount" in NR1, a count of nodes in the ST payload, so that blob parsing doesn't need to happen to support UI/API functions */
|
|
544
|
-
n: stns.length,
|
|
545
|
-
// node count
|
|
546
|
-
...firstHarvestOfSession
|
|
547
|
-
},
|
|
548
|
-
body: {
|
|
549
|
-
res: stns
|
|
550
|
-
}
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
smearEvtsByOrigin(name) {
|
|
554
|
-
const maxGap = toAggregate[name][0];
|
|
555
|
-
const maxLen = toAggregate[name][1];
|
|
556
|
-
const lastO = {};
|
|
557
|
-
return (byOrigin, evtNode) => {
|
|
558
|
-
let lastArr = byOrigin[evtNode.o];
|
|
559
|
-
if (!lastArr) lastArr = byOrigin[evtNode.o] = [];
|
|
560
|
-
const last = lastO[evtNode.o];
|
|
561
|
-
if (name === 'scrolling' && !trivial(evtNode)) {
|
|
562
|
-
lastO[evtNode.o] = null;
|
|
563
|
-
evtNode.n = 'scroll';
|
|
564
|
-
lastArr.push(evtNode);
|
|
565
|
-
} else if (last && evtNode.s - last.s < maxLen && last.e > evtNode.s - maxGap) {
|
|
566
|
-
last.e = evtNode.e;
|
|
567
|
-
} else {
|
|
568
|
-
lastO[evtNode.o] = evtNode;
|
|
569
|
-
lastArr.push(evtNode);
|
|
570
|
-
}
|
|
571
|
-
return byOrigin;
|
|
572
|
-
};
|
|
573
|
-
function trivial(node) {
|
|
574
|
-
const limit = 4;
|
|
575
|
-
return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
|
|
576
|
-
}
|
|
231
|
+
this.scheduler.stopTimer();
|
|
577
232
|
}
|
|
578
233
|
}
|