@newrelic/browser-agent 1.277.0 → 1.278.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/common/aggregate/event-aggregator.js +1 -1
- package/dist/cjs/common/config/init.js +1 -10
- package/dist/cjs/common/config/runtime.js +2 -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/harvest/harvester.js +255 -0
- package/dist/cjs/common/harvest/types.js +5 -21
- package/dist/cjs/features/ajax/aggregate/index.js +2 -11
- package/dist/cjs/features/generic_events/aggregate/index.js +3 -10
- package/dist/cjs/features/jserrors/aggregate/index.js +3 -14
- package/dist/cjs/features/logging/aggregate/index.js +4 -12
- package/dist/cjs/features/metrics/aggregate/index.js +7 -15
- package/dist/cjs/features/page_view_event/aggregate/index.js +46 -48
- package/dist/cjs/features/page_view_timing/aggregate/index.js +0 -9
- package/dist/cjs/features/session_replay/aggregate/index.js +21 -43
- package/dist/cjs/features/session_replay/instrument/index.js +2 -1
- package/dist/cjs/features/session_replay/shared/recorder.js +6 -6
- package/dist/cjs/features/session_trace/aggregate/index.js +9 -24
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +8 -2
- package/dist/cjs/features/soft_navigations/aggregate/index.js +4 -11
- package/dist/cjs/features/spa/aggregate/index.js +7 -10
- package/dist/cjs/features/utils/aggregate-base.js +66 -27
- package/dist/cjs/features/utils/event-buffer.js +0 -1
- package/dist/cjs/features/utils/event-store-manager.js +109 -0
- package/dist/cjs/features/utils/instrument-base.js +1 -10
- package/dist/cjs/loaders/features/features.js +16 -10
- package/dist/cjs/loaders/micro-agent.js +1 -0
- package/dist/esm/common/aggregate/event-aggregator.js +1 -1
- package/dist/esm/common/config/init.js +1 -10
- package/dist/esm/common/config/runtime.js +2 -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/harvest/harvester.js +249 -0
- package/dist/esm/common/harvest/types.js +5 -21
- package/dist/esm/features/ajax/aggregate/index.js +3 -12
- package/dist/esm/features/generic_events/aggregate/index.js +3 -10
- package/dist/esm/features/jserrors/aggregate/index.js +4 -15
- package/dist/esm/features/logging/aggregate/index.js +4 -12
- package/dist/esm/features/metrics/aggregate/index.js +7 -15
- package/dist/esm/features/page_view_event/aggregate/index.js +46 -48
- package/dist/esm/features/page_view_timing/aggregate/index.js +1 -10
- package/dist/esm/features/session_replay/aggregate/index.js +22 -44
- package/dist/esm/features/session_replay/instrument/index.js +2 -1
- package/dist/esm/features/session_replay/shared/recorder.js +6 -6
- package/dist/esm/features/session_trace/aggregate/index.js +9 -24
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +8 -2
- package/dist/esm/features/soft_navigations/aggregate/index.js +5 -12
- package/dist/esm/features/spa/aggregate/index.js +8 -11
- package/dist/esm/features/utils/aggregate-base.js +66 -27
- package/dist/esm/features/utils/event-buffer.js +0 -1
- package/dist/esm/features/utils/event-store-manager.js +103 -0
- package/dist/esm/features/utils/instrument-base.js +1 -10
- package/dist/esm/loaders/features/features.js +15 -9
- package/dist/esm/loaders/micro-agent.js +1 -0
- package/dist/types/common/aggregate/event-aggregator.d.ts +1 -1
- package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -1
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/config/runtime.d.ts.map +1 -1
- package/dist/types/common/harvest/harvester.d.ts +16 -0
- package/dist/types/common/harvest/harvester.d.ts.map +1 -0
- package/dist/types/common/harvest/types.d.ts +8 -45
- package/dist/types/common/harvest/types.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +0 -3
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts +0 -3
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts +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 +6 -2
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +12 -15
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +0 -5
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +8 -5
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts +0 -1
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +12 -7
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/event-buffer.d.ts +1 -2
- package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
- package/dist/types/features/utils/event-store-manager.d.ts +43 -0
- package/dist/types/features/utils/event-store-manager.d.ts.map +1 -0
- package/dist/types/features/utils/instrument-base.d.ts +0 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/features/features.d.ts +15 -12
- package/dist/types/loaders/features/features.d.ts.map +1 -1
- package/dist/types/loaders/micro-agent.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/common/aggregate/event-aggregator.js +1 -1
- package/src/common/config/init.js +9 -10
- package/src/common/config/runtime.js +2 -1
- package/src/common/harvest/__mocks__/harvester.js +6 -0
- package/src/common/harvest/harvester.js +230 -0
- package/src/common/harvest/types.js +5 -21
- package/src/features/ajax/aggregate/index.js +3 -14
- package/src/features/generic_events/aggregate/index.js +3 -13
- package/src/features/jserrors/aggregate/index.js +4 -11
- package/src/features/logging/aggregate/index.js +4 -12
- package/src/features/metrics/aggregate/index.js +5 -12
- package/src/features/page_view_event/aggregate/index.js +38 -38
- package/src/features/page_view_timing/aggregate/index.js +1 -12
- package/src/features/session_replay/aggregate/index.js +19 -42
- package/src/features/session_replay/instrument/index.js +1 -1
- package/src/features/session_replay/shared/recorder.js +6 -6
- package/src/features/session_trace/aggregate/index.js +8 -25
- package/src/features/session_trace/aggregate/trace/storage.js +5 -2
- package/src/features/soft_navigations/aggregate/index.js +4 -12
- package/src/features/spa/aggregate/index.js +8 -11
- package/src/features/utils/aggregate-base.js +59 -27
- package/src/features/utils/event-buffer.js +0 -1
- package/src/features/utils/event-store-manager.js +101 -0
- package/src/features/utils/instrument-base.js +2 -8
- package/src/loaders/features/features.js +16 -9
- package/src/loaders/micro-agent.js +1 -0
- package/dist/cjs/common/harvest/harvest-scheduler.js +0 -168
- package/dist/cjs/common/harvest/harvest.js +0 -295
- package/dist/esm/common/harvest/harvest-scheduler.js +0 -160
- package/dist/esm/common/harvest/harvest.js +0 -286
- package/dist/types/common/harvest/harvest-scheduler.d.ts +0 -50
- package/dist/types/common/harvest/harvest-scheduler.d.ts.map +0 -1
- package/dist/types/common/harvest/harvest.d.ts +0 -65
- package/dist/types/common/harvest/harvest.d.ts.map +0 -1
- package/src/common/harvest/__mocks__/harvest.js +0 -13
- package/src/common/harvest/harvest-scheduler.js +0 -166
- package/src/common/harvest/harvest.js +0 -282
|
@@ -2,7 +2,6 @@ import { globalScope, isBrowserScope, originTime } from '../../../common/constan
|
|
|
2
2
|
import { addPT, addPN } from '../../../common/timing/nav-timing';
|
|
3
3
|
import { stringify } from '../../../common/util/stringify';
|
|
4
4
|
import { isValid } from '../../../common/config/info';
|
|
5
|
-
import { Harvest } from '../../../common/harvest/harvest';
|
|
6
5
|
import * as CONSTANTS from '../constants';
|
|
7
6
|
import { getActivatedFeaturesFlags } from './initialized-features';
|
|
8
7
|
import { activateFeatures } from '../../../common/util/feature-flags';
|
|
@@ -21,11 +20,12 @@ export class Aggregate extends AggregateBase {
|
|
|
21
20
|
this.timeToFirstByte = 0;
|
|
22
21
|
this.firstByteToWindowLoad = 0; // our "frontend" duration
|
|
23
22
|
this.firstByteToDomContent = 0; // our "dom processing" duration
|
|
24
|
-
|
|
23
|
+
|
|
25
24
|
if (!isValid(agentRef.agentIdentifier)) {
|
|
26
25
|
this.ee.abort();
|
|
27
26
|
return warn(43);
|
|
28
27
|
}
|
|
28
|
+
agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.agentIdentifier);
|
|
29
29
|
if (isBrowserScope) {
|
|
30
30
|
timeToFirstByte.subscribe(({
|
|
31
31
|
value,
|
|
@@ -45,7 +45,6 @@ export class Aggregate extends AggregateBase {
|
|
|
45
45
|
}
|
|
46
46
|
sendRum() {
|
|
47
47
|
const info = this.agentRef.info;
|
|
48
|
-
const harvester = new Harvest(this);
|
|
49
48
|
const measures = {};
|
|
50
49
|
if (info.queueTime) measures.qt = info.queueTime;
|
|
51
50
|
if (info.applicationTime) measures.ap = info.applicationTime;
|
|
@@ -95,53 +94,52 @@ export class Aggregate extends AggregateBase {
|
|
|
95
94
|
}
|
|
96
95
|
queryParameters.fp = firstPaint.current.value;
|
|
97
96
|
queryParameters.fcp = firstContentfulPaint.current.value;
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const timeKeeper = this.agentRef.runtime.timeKeeper;
|
|
98
|
+
if (timeKeeper?.ready) {
|
|
99
|
+
queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(now()));
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
harvester.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
opts: {
|
|
109
|
-
needResponse: true,
|
|
110
|
-
sendEmptyBody: true
|
|
111
|
-
},
|
|
112
|
-
cbFinished: ({
|
|
113
|
-
status,
|
|
114
|
-
responseText,
|
|
115
|
-
xhr
|
|
116
|
-
}) => {
|
|
117
|
-
const rumEndTime = now();
|
|
118
|
-
if (status >= 400 || status === 0) {
|
|
119
|
-
// Adding retry logic for the rum call will be a separate change
|
|
120
|
-
this.ee.abort();
|
|
121
|
-
return;
|
|
101
|
+
this.rumStartTime = now();
|
|
102
|
+
this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
103
|
+
directSend: {
|
|
104
|
+
targetApp: this.agentRef.mainAppKey,
|
|
105
|
+
payload: {
|
|
106
|
+
qs: queryParameters,
|
|
107
|
+
body
|
|
122
108
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
...flags
|
|
127
|
-
} = JSON.parse(responseText);
|
|
128
|
-
try {
|
|
129
|
-
this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime, app.nrServerTime);
|
|
130
|
-
if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
131
|
-
this.agentRef.runtime.timeKeeper = this.timeKeeper;
|
|
132
|
-
} catch (error) {
|
|
133
|
-
this.ee.abort();
|
|
134
|
-
warn(17, error);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
this.agentRef.runtime.appMetadata = app;
|
|
138
|
-
activateFeatures(flags, this.agentIdentifier);
|
|
139
|
-
this.drain();
|
|
140
|
-
} catch (err) {
|
|
141
|
-
this.ee.abort();
|
|
142
|
-
warn(18, err);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
109
|
+
},
|
|
110
|
+
needResponse: true,
|
|
111
|
+
sendEmptyBody: true
|
|
145
112
|
});
|
|
146
113
|
}
|
|
114
|
+
postHarvestCleanup({
|
|
115
|
+
status,
|
|
116
|
+
responseText,
|
|
117
|
+
xhr
|
|
118
|
+
}) {
|
|
119
|
+
const rumEndTime = now();
|
|
120
|
+
this.blocked = true; // this prevents harvester from polling this feature's event buffer (DNE) on interval; in other words, harvests will skip PVE
|
|
121
|
+
|
|
122
|
+
if (status >= 400 || status === 0) {
|
|
123
|
+
warn(18, status);
|
|
124
|
+
// Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
|
|
125
|
+
this.ee.abort();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const {
|
|
129
|
+
app,
|
|
130
|
+
...flags
|
|
131
|
+
} = JSON.parse(responseText);
|
|
132
|
+
try {
|
|
133
|
+
this.agentRef.runtime.timeKeeper.processRumRequest(xhr, this.rumStartTime, rumEndTime, app.nrServerTime);
|
|
134
|
+
if (!this.agentRef.runtime.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.ee.abort();
|
|
137
|
+
warn(17, error);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.agentRef.runtime.appMetadata = app;
|
|
141
|
+
activateFeatures(flags, this.agentIdentifier);
|
|
142
|
+
this.drain();
|
|
143
|
+
this.agentRef.runtime.harvester.startTimer();
|
|
144
|
+
}
|
|
147
145
|
}
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer';
|
|
7
|
-
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
8
7
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
9
8
|
import { handle } from '../../../common/event-emitter/handle';
|
|
10
9
|
import { FEATURE_NAME } from '../constants';
|
|
11
|
-
import { FEATURE_NAMES
|
|
10
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
12
11
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
13
12
|
import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift';
|
|
14
13
|
import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint';
|
|
@@ -34,10 +33,7 @@ export class Aggregate extends AggregateBase {
|
|
|
34
33
|
registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
|
|
35
34
|
// Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
|
|
36
35
|
registerHandler('winPagehide', msTimestamp => this.addTiming('unload', msTimestamp, null), this.featureName, this.ee);
|
|
37
|
-
const harvestTimeSeconds = agentRef.init.page_view_timing.harvestTimeSeconds || 30;
|
|
38
36
|
this.waitForFlags([]).then(() => {
|
|
39
|
-
/* It's important that CWV api, like "onLCP", is called before the **scheduler** is initialized. The reason is because they listen to the same
|
|
40
|
-
on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
|
|
41
37
|
firstPaint.subscribe(this.#handleVitalMetric);
|
|
42
38
|
firstContentfulPaint.subscribe(this.#handleVitalMetric);
|
|
43
39
|
firstInputDelay.subscribe(this.#handleVitalMetric);
|
|
@@ -61,11 +57,6 @@ export class Aggregate extends AggregateBase {
|
|
|
61
57
|
this.addTiming(name, value * 1000, attrs);
|
|
62
58
|
}, true); // CLS node should only reports on vis change rather than on every change
|
|
63
59
|
|
|
64
|
-
const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
65
|
-
onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
|
|
66
|
-
getPayload: options => this.makeHarvestPayload(options.retry)
|
|
67
|
-
}, this);
|
|
68
|
-
scheduler.startTimer(harvestTimeSeconds);
|
|
69
60
|
this.drain();
|
|
70
61
|
});
|
|
71
62
|
}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
10
|
-
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
11
10
|
import { ABORT_REASONS, FEATURE_NAME, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants';
|
|
12
11
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
13
12
|
import { sharedChannel } from '../../../common/constants/shared-channel';
|
|
@@ -16,7 +15,7 @@ import { warn } from '../../../common/util/console';
|
|
|
16
15
|
import { globalScope } from '../../../common/constants/runtime';
|
|
17
16
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
18
17
|
import { handle } from '../../../common/event-emitter/handle';
|
|
19
|
-
import { FEATURE_NAMES
|
|
18
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
20
19
|
import { RRWEB_VERSION } from "../../../common/constants/env.npm";
|
|
21
20
|
import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants';
|
|
22
21
|
import { stringify } from '../../../common/util/stringify';
|
|
@@ -32,8 +31,6 @@ export class Aggregate extends AggregateBase {
|
|
|
32
31
|
// pass the recorder into the aggregator
|
|
33
32
|
constructor(agentRef, args) {
|
|
34
33
|
super(agentRef, FEATURE_NAME);
|
|
35
|
-
/** The interval to harvest at. This gets overridden if the size of the payload exceeds certain thresholds */
|
|
36
|
-
this.harvestTimeSeconds = agentRef.init.session_replay.harvestTimeSeconds || 60;
|
|
37
34
|
/** Set once the recorder has fully initialized after flag checks and sampling */
|
|
38
35
|
this.initialized = false;
|
|
39
36
|
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
@@ -49,6 +46,7 @@ export class Aggregate extends AggregateBase {
|
|
|
49
46
|
this.timeKeeper = undefined;
|
|
50
47
|
this.recorder = args?.recorder;
|
|
51
48
|
this.errorNoticed = args?.errorNoticed || false;
|
|
49
|
+
this.harvestOpts.raw = true;
|
|
52
50
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
53
51
|
|
|
54
52
|
// 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.
|
|
@@ -73,19 +71,8 @@ export class Aggregate extends AggregateBase {
|
|
|
73
71
|
if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
|
|
74
72
|
this.mode = data.sessionReplay;
|
|
75
73
|
});
|
|
76
|
-
|
|
77
|
-
// Bespoke logic for blobs endpoint.
|
|
78
|
-
this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
79
|
-
onFinished: result => this.postHarvestCleanup(result),
|
|
80
|
-
retryDelay: this.harvestTimeSeconds,
|
|
81
|
-
getPayload: ({
|
|
82
|
-
retry,
|
|
83
|
-
...opts
|
|
84
|
-
}) => this.makeHarvestPayload(retry, opts),
|
|
85
|
-
raw: true
|
|
86
|
-
}, this);
|
|
87
74
|
registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
|
|
88
|
-
this.forceStop(this.mode
|
|
75
|
+
this.forceStop(this.mode === MODE.FULL);
|
|
89
76
|
}, this.featureName, this.ee);
|
|
90
77
|
registerHandler(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
|
|
91
78
|
this.handleError(e);
|
|
@@ -131,7 +118,7 @@ export class Aggregate extends AggregateBase {
|
|
|
131
118
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
132
119
|
}
|
|
133
120
|
replayIsActive() {
|
|
134
|
-
return Boolean(this.
|
|
121
|
+
return Boolean(this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled);
|
|
135
122
|
}
|
|
136
123
|
handleError(e) {
|
|
137
124
|
if (this.recorder) this.recorder.currentBufferTarget.hasError = true;
|
|
@@ -146,20 +133,17 @@ export class Aggregate extends AggregateBase {
|
|
|
146
133
|
// if the error was noticed AFTER the recorder was already imported....
|
|
147
134
|
if (this.recorder && this.initialized) {
|
|
148
135
|
if (!this.recorder.recording) this.recorder.startRecording();
|
|
149
|
-
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
150
136
|
this.syncWithSessionManager({
|
|
151
137
|
sessionReplayMode: this.mode
|
|
152
138
|
});
|
|
153
139
|
} else {
|
|
154
|
-
this.initializeRecording(
|
|
140
|
+
this.initializeRecording(MODE.FULL, true);
|
|
155
141
|
}
|
|
156
142
|
}
|
|
157
143
|
|
|
158
144
|
/**
|
|
159
145
|
* Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
|
|
160
146
|
* @param {boolean} entitlements - the true/false state of the "sr" flag from RUM response
|
|
161
|
-
* @param {boolean} errorSample - the true/false state of the error sampling decision
|
|
162
|
-
* @param {boolean} fullSample - the true/false state of the full sampling decision
|
|
163
147
|
* @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
|
|
164
148
|
* @returns {void}
|
|
165
149
|
*/
|
|
@@ -206,20 +190,14 @@ export class Aggregate extends AggregateBase {
|
|
|
206
190
|
|
|
207
191
|
// If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
|
|
208
192
|
if (this.mode === MODE.ERROR && this.errorNoticed) this.mode = MODE.FULL;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
218
|
-
// If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
|
|
219
|
-
if (!this.scheduler.started) {
|
|
220
|
-
// We only report (harvest) in FULL mode
|
|
221
|
-
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
222
|
-
}
|
|
193
|
+
|
|
194
|
+
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
195
|
+
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
196
|
+
// The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
|
|
197
|
+
|
|
198
|
+
// If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
|
|
199
|
+
if (this.mode === MODE.FULL && this.recorder?.getEvents().type === 'preloaded') {
|
|
200
|
+
this.prepUtils().then(() => this.agentRef.runtime.harvester.triggerHarvestFor(this));
|
|
223
201
|
}
|
|
224
202
|
await this.prepUtils();
|
|
225
203
|
if (!this.recorder.recording) this.recorder.startRecording();
|
|
@@ -240,11 +218,12 @@ export class Aggregate extends AggregateBase {
|
|
|
240
218
|
// compressor failed to load, but we can still record without compression as a last ditch effort
|
|
241
219
|
}
|
|
242
220
|
}
|
|
243
|
-
makeHarvestPayload(shouldRetryOnFail
|
|
221
|
+
makeHarvestPayload(shouldRetryOnFail) {
|
|
222
|
+
if (this.mode !== MODE.FULL || this.blocked) return;
|
|
244
223
|
if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return;
|
|
245
224
|
const recorderEvents = this.recorder.getEvents();
|
|
246
225
|
// get the event type and use that to trigger another harvest if needed
|
|
247
|
-
if (!recorderEvents.events.length
|
|
226
|
+
if (!recorderEvents.events.length) return;
|
|
248
227
|
const payload = this.getHarvestContents(recorderEvents);
|
|
249
228
|
if (!payload.body.length) {
|
|
250
229
|
this.recorder.clearBuffer();
|
|
@@ -268,7 +247,6 @@ export class Aggregate extends AggregateBase {
|
|
|
268
247
|
return stringify(output);
|
|
269
248
|
}).join(','), "]")));
|
|
270
249
|
len = payload.body.length;
|
|
271
|
-
this.scheduler.opts.gzip = true;
|
|
272
250
|
} else {
|
|
273
251
|
payload.body = payload.body.map(({
|
|
274
252
|
__serialized,
|
|
@@ -283,7 +261,6 @@ export class Aggregate extends AggregateBase {
|
|
|
283
261
|
return output;
|
|
284
262
|
});
|
|
285
263
|
len = stringify(payload.body).length;
|
|
286
|
-
this.scheduler.opts.gzip = false;
|
|
287
264
|
}
|
|
288
265
|
if (len > MAX_PAYLOAD_SIZE) {
|
|
289
266
|
this.abort(ABORT_REASONS.TOO_BIG, len);
|
|
@@ -294,8 +271,11 @@ export class Aggregate extends AggregateBase {
|
|
|
294
271
|
sessionReplaySentFirstChunk: true
|
|
295
272
|
});
|
|
296
273
|
this.recorder.clearBuffer();
|
|
297
|
-
if (recorderEvents.type === 'preloaded') this.
|
|
298
|
-
return [
|
|
274
|
+
if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
275
|
+
return [{
|
|
276
|
+
targetApp: undefined,
|
|
277
|
+
payload
|
|
278
|
+
}]; // SR doesn't need a targetApp as it only works for the main, but format needs to make AggregateBase
|
|
299
279
|
}
|
|
300
280
|
getCorrectedTimestamp(node) {
|
|
301
281
|
if (!node?.timestamp) return;
|
|
@@ -381,7 +361,6 @@ export class Aggregate extends AggregateBase {
|
|
|
381
361
|
if (result.status === 429) {
|
|
382
362
|
this.abort(ABORT_REASONS.TOO_MANY);
|
|
383
363
|
}
|
|
384
|
-
if (this.blocked) this.scheduler.stopTimer(true);
|
|
385
364
|
}
|
|
386
365
|
|
|
387
366
|
/**
|
|
@@ -390,7 +369,7 @@ export class Aggregate extends AggregateBase {
|
|
|
390
369
|
* the stopRecording API.
|
|
391
370
|
*/
|
|
392
371
|
forceStop(forceHarvest) {
|
|
393
|
-
if (forceHarvest) this.
|
|
372
|
+
if (forceHarvest) this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
394
373
|
this.mode = MODE.OFF;
|
|
395
374
|
this.recorder?.stopRecording?.();
|
|
396
375
|
this.syncWithSessionManager({
|
|
@@ -409,7 +388,6 @@ export class Aggregate extends AggregateBase {
|
|
|
409
388
|
sessionReplayMode: this.mode
|
|
410
389
|
});
|
|
411
390
|
this.recorder?.clearTimestamps?.();
|
|
412
|
-
this.ee.emit('REPLAY_ABORTED');
|
|
413
391
|
while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
|
|
414
392
|
}
|
|
415
393
|
syncWithSessionManager(state = {}) {
|
|
@@ -79,7 +79,8 @@ export class Instrument extends InstrumentBase {
|
|
|
79
79
|
mode: this.#mode,
|
|
80
80
|
agentIdentifier: this.agentIdentifier,
|
|
81
81
|
trigger,
|
|
82
|
-
ee: this.ee
|
|
82
|
+
ee: this.ee,
|
|
83
|
+
agentRef: this.#agentRef
|
|
83
84
|
});
|
|
84
85
|
this.recorder.startRecording();
|
|
85
86
|
this.abortHandler = this.recorder.stopRecording;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { record as recorder } from 'rrweb';
|
|
2
2
|
import { stringify } from '../../../common/util/stringify';
|
|
3
3
|
import { AVG_COMPRESSION, CHECKOUT_MS, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES } from '../constants';
|
|
4
|
-
import { getConfigurationValue } from '../../../common/config/init';
|
|
5
4
|
import { RecorderEvents } from './recorder-events';
|
|
6
5
|
import { MODE } from '../../../common/session/constants';
|
|
7
6
|
import { stylesheetEvaluator } from './stylesheet-evaluator';
|
|
@@ -10,6 +9,7 @@ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
|
10
9
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
11
10
|
import { buildNRMetaNode } from './utils';
|
|
12
11
|
import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
|
|
12
|
+
import { AggregateBase } from '../../utils/aggregate-base';
|
|
13
13
|
export class Recorder {
|
|
14
14
|
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
15
15
|
#events;
|
|
@@ -34,7 +34,7 @@ export class Recorder {
|
|
|
34
34
|
/** The parent class that instantiated the recorder */
|
|
35
35
|
this.parent = parent;
|
|
36
36
|
/** A flag that can be set to false by failing conversions to stop the fetching process */
|
|
37
|
-
this.shouldFix =
|
|
37
|
+
this.shouldFix = this.parent.agentRef.init.session_replay.fix_stylesheets;
|
|
38
38
|
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
39
39
|
this.stopRecording = () => {/* no-op until set by rrweb initializer */};
|
|
40
40
|
}
|
|
@@ -78,7 +78,7 @@ export class Recorder {
|
|
|
78
78
|
mask_all_inputs,
|
|
79
79
|
inline_images,
|
|
80
80
|
collect_fonts
|
|
81
|
-
} =
|
|
81
|
+
} = this.parent.agentRef.init.session_replay;
|
|
82
82
|
const customMasker = (text, element) => {
|
|
83
83
|
try {
|
|
84
84
|
if (typeof element?.type === 'string' && element.type.toLowerCase() !== 'password' && (element?.dataset?.nrUnmask !== undefined || element?.classList?.contains('nr-unmask'))) return text;
|
|
@@ -154,7 +154,7 @@ export class Recorder {
|
|
|
154
154
|
/** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
|
|
155
155
|
store(event, isCheckout) {
|
|
156
156
|
if (!event) return;
|
|
157
|
-
if (!this.parent
|
|
157
|
+
if (!(this.parent instanceof AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
|
|
158
158
|
if (this.parent.blocked) return;
|
|
159
159
|
if (!this.notified) {
|
|
160
160
|
this.parent.ee.emit(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [true, this.parent.mode]);
|
|
@@ -192,8 +192,8 @@ export class Recorder {
|
|
|
192
192
|
// it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
|
|
193
193
|
if ((event.type === RRWEB_EVENT_TYPES.FullSnapshot && this.currentBufferTarget.hasMeta || payloadSize > IDEAL_PAYLOAD_SIZE) && this.parent.mode === MODE.FULL) {
|
|
194
194
|
// if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
|
|
195
|
-
if (this.parent
|
|
196
|
-
this.parent.
|
|
195
|
+
if (this.parent instanceof AggregateBase) {
|
|
196
|
+
this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent);
|
|
197
197
|
} else {
|
|
198
198
|
// we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
|
|
199
199
|
this.#preloaded.push(new RecorderEvents());
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
2
|
-
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
3
2
|
import { FEATURE_NAME } from '../constants';
|
|
4
3
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
5
4
|
import { TraceStorage } from './trace/storage';
|
|
@@ -7,7 +6,6 @@ import { obj as encodeObj } from '../../../common/url/encode';
|
|
|
7
6
|
import { globalScope } from '../../../common/constants/runtime';
|
|
8
7
|
import { MODE, SESSION_EVENTS } from '../../../common/session/constants';
|
|
9
8
|
import { applyFnToProps } from '../../../common/util/traverse';
|
|
10
|
-
import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features';
|
|
11
9
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
12
10
|
const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
13
11
|
/** Reserved room for query param attrs */
|
|
@@ -16,7 +14,8 @@ export class Aggregate extends AggregateBase {
|
|
|
16
14
|
static featureName = FEATURE_NAME;
|
|
17
15
|
constructor(agentRef) {
|
|
18
16
|
super(agentRef, FEATURE_NAME);
|
|
19
|
-
this.
|
|
17
|
+
this.harvestOpts.raw = true;
|
|
18
|
+
|
|
20
19
|
/** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
|
|
21
20
|
this.entitled = undefined;
|
|
22
21
|
/** A flag used to decide if the 30 node threshold should be ignored on the first harvest to ensure sending on the first payload */
|
|
@@ -25,14 +24,16 @@ export class Aggregate extends AggregateBase {
|
|
|
25
24
|
this.harvesting = false;
|
|
26
25
|
/** TraceStorage is the mechanism that holds, normalizes and aggregates ST nodes. It will be accessed and purged when harvests occur */
|
|
27
26
|
this.events = new TraceStorage(this);
|
|
28
|
-
|
|
27
|
+
|
|
28
|
+
/* This agg needs information about sampling (sts) and entitlements (st) to make the appropriate decisions on running */
|
|
29
29
|
this.waitForFlags(['sts', 'st']).then(([stMode, stEntitled]) => this.initialize(stMode, stEntitled));
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** 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 */
|
|
33
33
|
initialize(stMode, stEntitled, ignoreSession) {
|
|
34
34
|
this.entitled ??= stEntitled;
|
|
35
|
-
if (
|
|
35
|
+
if (!this.entitled) this.blocked = true;
|
|
36
|
+
if (this.blocked) return this.deregisterDrain();
|
|
36
37
|
if (!this.initialized) {
|
|
37
38
|
this.initialized = true;
|
|
38
39
|
/** Store session identifiers at initialization time to be cross-checked later at harvest time for session changes that are subject to race conditions */
|
|
@@ -50,7 +51,7 @@ export class Aggregate extends AggregateBase {
|
|
|
50
51
|
// this will only have an effect if ST is NOT already in full mode
|
|
51
52
|
if (this.mode !== MODE.FULL && (sessionState.sessionReplayMode === MODE.FULL || sessionState.sessionTraceMode === MODE.FULL)) this.switchToFull();
|
|
52
53
|
// if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
|
|
53
|
-
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' &&
|
|
54
|
+
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF) this.abort(2);
|
|
54
55
|
});
|
|
55
56
|
if (typeof PerformanceNavigationTiming !== 'undefined') {
|
|
56
57
|
this.events.storeTiming(globalScope.performance?.getEntriesByType?.('navigation')[0]);
|
|
@@ -67,12 +68,6 @@ export class Aggregate extends AggregateBase {
|
|
|
67
68
|
* If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
|
|
68
69
|
if (this.mode === MODE.OFF) return this.deregisterDrain();
|
|
69
70
|
this.timeKeeper ??= this.agentRef.runtime.timeKeeper;
|
|
70
|
-
this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
71
|
-
onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
|
|
72
|
-
retryDelay: this.harvestTimeSeconds,
|
|
73
|
-
getPayload: options => this.makeHarvestPayload(options.retry),
|
|
74
|
-
raw: true
|
|
75
|
-
}, this);
|
|
76
71
|
|
|
77
72
|
/** The handlers set up by the Inst file */
|
|
78
73
|
registerHandler('bst', (...args) => this.events.storeEvent(...args), this.featureName, this.ee);
|
|
@@ -82,9 +77,7 @@ export class Aggregate extends AggregateBase {
|
|
|
82
77
|
registerHandler('bstApi', (...args) => this.events.storeSTN(...args), this.featureName, this.ee);
|
|
83
78
|
registerHandler('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee);
|
|
84
79
|
registerHandler('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee);
|
|
85
|
-
|
|
86
|
-
/** Only start actually harvesting if running in full mode at init time */
|
|
87
|
-
if (this.mode === MODE.FULL) this.startHarvesting();else {
|
|
80
|
+
if (this.mode !== MODE.FULL) {
|
|
88
81
|
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
89
82
|
registerHandler('trace-jserror', () => {
|
|
90
83
|
if (this.mode === MODE.ERROR) this.switchToFull();
|
|
@@ -95,13 +88,6 @@ export class Aggregate extends AggregateBase {
|
|
|
95
88
|
});
|
|
96
89
|
this.drain();
|
|
97
90
|
}
|
|
98
|
-
|
|
99
|
-
/** 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 */
|
|
100
|
-
startHarvesting() {
|
|
101
|
-
if (this.scheduler.started || this.blocked) return;
|
|
102
|
-
this.scheduler.runHarvest();
|
|
103
|
-
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
104
|
-
}
|
|
105
91
|
preHarvestChecks() {
|
|
106
92
|
if (this.mode !== MODE.FULL) return; // only allow harvest if running in full mode
|
|
107
93
|
if (!this.timeKeeper?.ready) return; // this should likely never happen, but just to be safe, we should never harvest if we cant correct time
|
|
@@ -192,8 +178,8 @@ export class Aggregate extends AggregateBase {
|
|
|
192
178
|
if (prevMode === MODE.OFF || !this.initialized) return this.initialize(this.mode, this.entitled);
|
|
193
179
|
if (this.initialized) {
|
|
194
180
|
this.events.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
|
|
181
|
+
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
195
182
|
}
|
|
196
|
-
this.startHarvesting();
|
|
197
183
|
}
|
|
198
184
|
|
|
199
185
|
/** Stop running for the remainder of the page lifecycle */
|
|
@@ -203,7 +189,6 @@ export class Aggregate extends AggregateBase {
|
|
|
203
189
|
this.agentRef.runtime.session.write({
|
|
204
190
|
sessionTraceMode: this.mode
|
|
205
191
|
});
|
|
206
|
-
this.scheduler?.stopTimer();
|
|
207
192
|
this.events.clear();
|
|
208
193
|
}
|
|
209
194
|
}
|
|
@@ -269,14 +269,20 @@ export class TraceStorage {
|
|
|
269
269
|
this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
/* Below are the interface expected & required of whatever storage is used across all features on an individual basis. This allows a common `.events` property on Trace.
|
|
272
|
+
/* Below are the interface expected & required of whatever storage is used across all features on an individual basis. This allows a common `.events` property on Trace shared with AggregateBase.
|
|
273
|
+
Note that the usage must be in sync with the EventStoreManager class such that AggregateBase.makeHarvestPayload can run the same regardless of which storage class a feature is using. */
|
|
273
274
|
isEmpty() {
|
|
274
275
|
return this.nodeCount === 0;
|
|
275
276
|
}
|
|
276
277
|
save() {
|
|
277
278
|
this.#backupTrace = this.trace;
|
|
278
279
|
}
|
|
279
|
-
get
|
|
280
|
+
get() {
|
|
281
|
+
return [{
|
|
282
|
+
targetApp: this.parent.agentRef.mainAppKey,
|
|
283
|
+
data: this.takeSTNs()
|
|
284
|
+
}];
|
|
285
|
+
}
|
|
280
286
|
clear() {
|
|
281
287
|
this.trace = {};
|
|
282
288
|
this.nodeCount = 0;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { handle } from '../../../common/event-emitter/handle';
|
|
2
2
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
3
|
-
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
4
3
|
import { single } from '../../../common/util/invoke';
|
|
5
4
|
import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
|
|
6
|
-
import { FEATURE_NAMES
|
|
5
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
7
6
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
8
7
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
9
8
|
import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS, INTERACTION_TRIGGERS, IPL_TRIGGER_NAME } from '../constants';
|
|
@@ -16,7 +15,6 @@ export class Aggregate extends AggregateBase {
|
|
|
16
15
|
domObserver
|
|
17
16
|
}) {
|
|
18
17
|
super(agentRef, FEATURE_NAME);
|
|
19
|
-
const harvestTimeSeconds = agentRef.init.soft_navigations.harvestTimeSeconds || 10;
|
|
20
18
|
this.interactionsToHarvest = this.events;
|
|
21
19
|
this.domObserver = domObserver;
|
|
22
20
|
this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef.agentIdentifier);
|
|
@@ -37,17 +35,12 @@ export class Aggregate extends AggregateBase {
|
|
|
37
35
|
this.latestRouteSetByApi = null;
|
|
38
36
|
this.interactionInProgress = null; // aside from the "page load" interaction, there can only ever be 1 ongoing at a time
|
|
39
37
|
this.latestHistoryUrl = null;
|
|
40
|
-
this.
|
|
38
|
+
this.harvestOpts.beforeUnload = () => this.interactionInProgress?.done(); // return any withheld ajax or jserr events so they can be sent with EoL harvest
|
|
39
|
+
|
|
41
40
|
this.waitForFlags(['spa']).then(([spaOn]) => {
|
|
42
41
|
if (spaOn) {
|
|
43
42
|
this.drain();
|
|
44
|
-
|
|
45
|
-
onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
|
|
46
|
-
getPayload: options => this.makeHarvestPayload(options.retry),
|
|
47
|
-
retryDelay: harvestTimeSeconds,
|
|
48
|
-
onUnload: () => this.interactionInProgress?.done() // return any held ajax or jserr events so they can be sent with EoL harvest
|
|
49
|
-
}, this);
|
|
50
|
-
scheduler.startTimer(harvestTimeSeconds, 0);
|
|
43
|
+
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?
|
|
51
44
|
} else {
|
|
52
45
|
this.blocked = true; // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
|
|
53
46
|
this.deregisterDrain();
|
|
@@ -129,7 +122,7 @@ export class Aggregate extends AggregateBase {
|
|
|
129
122
|
*/
|
|
130
123
|
if (this.interactionInProgress?.isActiveDuring(timestamp)) return this.interactionInProgress;
|
|
131
124
|
let saveIxn;
|
|
132
|
-
const interactionsBuffer = this.interactionsToHarvest.get();
|
|
125
|
+
const interactionsBuffer = this.interactionsToHarvest.get(this.agentRef.mainAppKey)[0].data;
|
|
133
126
|
for (let idx = interactionsBuffer.length - 1; idx >= 0; idx--) {
|
|
134
127
|
// reverse search for the latest completed interaction for efficiency
|
|
135
128
|
const finishedInteraction = interactionsBuffer[idx];
|
|
@@ -10,11 +10,10 @@ import { navTimingValues as navTiming } from '../../../common/timing/nav-timing'
|
|
|
10
10
|
import { generateUuid } from '../../../common/ids/unique-id';
|
|
11
11
|
import { Interaction } from './interaction';
|
|
12
12
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
|
|
13
|
-
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
14
13
|
import { Serializer } from './serializer';
|
|
15
14
|
import { ee } from '../../../common/event-emitter/contextual-ee';
|
|
16
15
|
import * as CONSTANTS from '../constants';
|
|
17
|
-
import { FEATURE_NAMES
|
|
16
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
18
17
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
19
18
|
import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint';
|
|
20
19
|
import { firstPaint } from '../../../common/vitals/first-paint';
|
|
@@ -58,13 +57,11 @@ export class Aggregate extends AggregateBase {
|
|
|
58
57
|
pageLoaded: false,
|
|
59
58
|
childTime: 0,
|
|
60
59
|
depth: 0,
|
|
61
|
-
harvestTimeSeconds: agentRef.init.spa.harvestTimeSeconds || 10,
|
|
62
60
|
// The below feature flag is used to disable the SPA ajax fix for specific customers, see https://new-relic.atlassian.net/browse/NR-172169
|
|
63
61
|
disableSpaFix: (agentRef.init.feature_flags || []).indexOf('disable-spa-fix') > -1
|
|
64
62
|
};
|
|
65
63
|
this.spaSerializerClass = new Serializer(this);
|
|
66
64
|
const classThis = this;
|
|
67
|
-
let scheduler;
|
|
68
65
|
const baseEE = ee.get(agentRef.agentIdentifier); // <-- parent baseEE
|
|
69
66
|
const mutationEE = baseEE.get('mutation');
|
|
70
67
|
const promiseEE = baseEE.get('promise');
|
|
@@ -108,13 +105,10 @@ export class Aggregate extends AggregateBase {
|
|
|
108
105
|
// | click ending: | 65 | 50 | | | |
|
|
109
106
|
// click fn-end | 70 | 0 | 0 | 70 | 20 |
|
|
110
107
|
|
|
108
|
+
let harvester;
|
|
111
109
|
this.waitForFlags(['spa']).then(([spaFlag]) => {
|
|
112
110
|
if (spaFlag) {
|
|
113
|
-
|
|
114
|
-
onFinished: result => this.postHarvestCleanup(result.sent && result.retry),
|
|
115
|
-
getPayload: options => this.makeHarvestPayload(options.retry),
|
|
116
|
-
retryDelay: state.harvestTimeSeconds
|
|
117
|
-
}, this);
|
|
111
|
+
harvester = agentRef.runtime.harvester; // since this is after RUM call, PVE would've initialized harvester by now
|
|
118
112
|
this.drain();
|
|
119
113
|
} else {
|
|
120
114
|
this.blocked = true;
|
|
@@ -655,8 +649,11 @@ export class Aggregate extends AggregateBase {
|
|
|
655
649
|
let smCategory;
|
|
656
650
|
if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad';else if (interaction.routeChange) smCategory = 'RouteChange';else smCategory = 'Custom';
|
|
657
651
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ["Spa/Interaction/".concat(smCategory, "/Duration/Ms"), Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, FEATURE_NAMES.metrics, baseEE);
|
|
658
|
-
|
|
659
|
-
|
|
652
|
+
if (!harvester) {
|
|
653
|
+
warn(19);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
harvester.triggerHarvestFor(classThis);
|
|
660
657
|
}
|
|
661
658
|
}
|
|
662
659
|
serializer(eventBuffer) {
|