@newrelic/browser-agent 1.296.0 → 1.297.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/harvest/harvester.js +2 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +10 -41
- package/dist/cjs/features/session_replay/instrument/index.js +4 -4
- package/dist/cjs/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/cjs/features/session_replay/shared/recorder.js +41 -61
- package/dist/cjs/features/session_replay/shared/utils.js +0 -13
- package/dist/cjs/features/utils/aggregate-base.js +6 -5
- package/dist/cjs/features/utils/event-buffer.js +3 -2
- 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 +2 -2
- package/dist/esm/features/session_replay/aggregate/index.js +10 -41
- package/dist/esm/features/session_replay/instrument/index.js +4 -4
- package/dist/esm/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/esm/features/session_replay/shared/recorder.js +42 -62
- package/dist/esm/features/session_replay/shared/utils.js +0 -12
- package/dist/esm/features/utils/aggregate-base.js +6 -5
- package/dist/esm/features/utils/event-buffer.js +3 -2
- package/dist/types/common/harvest/harvester.d.ts +15 -0
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +0 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts +1 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +10 -8
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/utils.d.ts +0 -8
- package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +2 -0
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/event-buffer.d.ts +2 -1
- package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/harvest/harvester.js +2 -2
- package/src/features/session_replay/aggregate/index.js +8 -35
- package/src/features/session_replay/instrument/index.js +1 -1
- package/src/features/session_replay/shared/recorder-events.js +2 -2
- package/src/features/session_replay/shared/recorder.js +39 -67
- package/src/features/session_replay/shared/utils.js +0 -13
- package/src/features/utils/aggregate-base.js +6 -4
- package/src/features/utils/event-buffer.js +3 -2
|
@@ -11,18 +11,13 @@ import { stylesheetEvaluator } from './stylesheet-evaluator';
|
|
|
11
11
|
import { handle } from '../../../common/event-emitter/handle';
|
|
12
12
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
13
13
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
14
|
-
import {
|
|
14
|
+
import { customMasker } from './utils';
|
|
15
15
|
import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
|
|
16
|
-
import { AggregateBase } from '../../utils/aggregate-base';
|
|
17
16
|
import { warn } from '../../../common/util/console';
|
|
18
17
|
import { single } from '../../../common/util/invoke';
|
|
18
|
+
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
19
|
+
const RRWEB_DATA_CHANNEL = 'rrweb-data';
|
|
19
20
|
export class Recorder {
|
|
20
|
-
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
21
|
-
#events;
|
|
22
|
-
/** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
|
|
23
|
-
#backloggedEvents;
|
|
24
|
-
/** array of recorder events -- Will be filled only if forced harvest was triggered and harvester does not exist */
|
|
25
|
-
#preloaded;
|
|
26
21
|
/** flag that if true, blocks events from being "stored". Only set to true when a full snapshot has incomplete nodes (only stylesheets ATM) */
|
|
27
22
|
#fixing = false;
|
|
28
23
|
#warnCSSOnce = single(() => warn(47)); // notifies user of potential replayer issue if fix_stylesheets is off
|
|
@@ -32,13 +27,12 @@ export class Recorder {
|
|
|
32
27
|
this.parent = parent;
|
|
33
28
|
/** A flag that can be set to false by failing conversions to stop the fetching process */
|
|
34
29
|
this.shouldFix = this.parent.agentRef.init.session_replay.fix_stylesheets;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/** Only set to true once a snapshot node has been processed. Used to block preload harvests from sending before we know we have a snapshot */
|
|
30
|
+
|
|
31
|
+
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
32
|
+
this.events = new RecorderEvents(this.shouldFix);
|
|
33
|
+
/** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
|
|
34
|
+
this.backloggedEvents = new RecorderEvents(this.shouldFix);
|
|
35
|
+
/** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
|
|
42
36
|
this.hasSeenSnapshot = false;
|
|
43
37
|
/** 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 */
|
|
44
38
|
this.lastMeta = false;
|
|
@@ -46,32 +40,27 @@ export class Recorder {
|
|
|
46
40
|
this.stopRecording = () => {
|
|
47
41
|
this.parent.agentRef.runtime.isRecording = false;
|
|
48
42
|
};
|
|
43
|
+
registerHandler(RRWEB_DATA_CHANNEL, (event, isCheckout) => {
|
|
44
|
+
this.audit(event, isCheckout);
|
|
45
|
+
}, this.parent.featureName, this.parent.ee);
|
|
49
46
|
}
|
|
50
47
|
getEvents() {
|
|
51
|
-
if (this.#preloaded[0]?.events.length) {
|
|
52
|
-
return {
|
|
53
|
-
...this.#preloaded[0],
|
|
54
|
-
events: this.#preloaded[0].events,
|
|
55
|
-
payloadBytesEstimation: this.#preloaded[0].payloadBytesEstimation,
|
|
56
|
-
type: 'preloaded'
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
48
|
return {
|
|
60
|
-
events: [...this
|
|
49
|
+
events: [...this.backloggedEvents.events, ...this.events.events].filter(x => x),
|
|
61
50
|
type: 'standard',
|
|
62
|
-
cycleTimestamp: Math.min(this
|
|
63
|
-
payloadBytesEstimation: this
|
|
64
|
-
hasError: this
|
|
65
|
-
hasMeta: this
|
|
66
|
-
hasSnapshot: this
|
|
67
|
-
inlinedAllStylesheets: !!this
|
|
51
|
+
cycleTimestamp: Math.min(this.backloggedEvents.cycleTimestamp, this.events.cycleTimestamp),
|
|
52
|
+
payloadBytesEstimation: this.backloggedEvents.payloadBytesEstimation + this.events.payloadBytesEstimation,
|
|
53
|
+
hasError: this.backloggedEvents.hasError || this.events.hasError,
|
|
54
|
+
hasMeta: this.backloggedEvents.hasMeta || this.events.hasMeta,
|
|
55
|
+
hasSnapshot: this.backloggedEvents.hasSnapshot || this.events.hasSnapshot,
|
|
56
|
+
inlinedAllStylesheets: !!this.backloggedEvents.events.length && this.backloggedEvents.inlinedAllStylesheets || this.events.inlinedAllStylesheets
|
|
68
57
|
};
|
|
69
58
|
}
|
|
70
59
|
|
|
71
|
-
/** Clears the buffer (this
|
|
60
|
+
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
72
61
|
clearBuffer() {
|
|
73
|
-
|
|
74
|
-
this
|
|
62
|
+
this.backloggedEvents = this.parent.mode === MODE.ERROR ? this.events : new RecorderEvents(this.shouldFix);
|
|
63
|
+
this.events = new RecorderEvents(this.shouldFix);
|
|
75
64
|
}
|
|
76
65
|
|
|
77
66
|
/** Begin recording using configured recording lib */
|
|
@@ -94,7 +83,9 @@ export class Recorder {
|
|
|
94
83
|
let stop;
|
|
95
84
|
try {
|
|
96
85
|
stop = recorder({
|
|
97
|
-
emit:
|
|
86
|
+
emit: (event, isCheckout) => {
|
|
87
|
+
handle(RRWEB_DATA_CHANNEL, [event, isCheckout], undefined, this.parent.featureName, this.parent.ee);
|
|
88
|
+
},
|
|
98
89
|
blockClass: block_class,
|
|
99
90
|
ignoreClass: ignore_class,
|
|
100
91
|
maskTextClass: mask_text_class,
|
|
@@ -132,7 +123,7 @@ export class Recorder {
|
|
|
132
123
|
/** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
|
|
133
124
|
if (!this.shouldFix) {
|
|
134
125
|
if (incompletes > 0) {
|
|
135
|
-
this.
|
|
126
|
+
this.events.inlinedAllStylesheets = false;
|
|
136
127
|
this.#warnCSSOnce();
|
|
137
128
|
handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee);
|
|
138
129
|
}
|
|
@@ -144,7 +135,7 @@ export class Recorder {
|
|
|
144
135
|
/** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
|
|
145
136
|
stylesheetEvaluator.fix().then(failedToFix => {
|
|
146
137
|
if (failedToFix > 0) {
|
|
147
|
-
this.
|
|
138
|
+
this.events.inlinedAllStylesheets = false;
|
|
148
139
|
this.shouldFix = false;
|
|
149
140
|
}
|
|
150
141
|
handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
|
|
@@ -158,15 +149,12 @@ export class Recorder {
|
|
|
158
149
|
if (!this.#fixing) this.store(event, isCheckout);
|
|
159
150
|
}
|
|
160
151
|
|
|
161
|
-
/** Store a payload in the buffer (this
|
|
152
|
+
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
162
153
|
store(event, isCheckout) {
|
|
163
|
-
if (!event) return;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
event.__newrelic = buildNRMetaNode(event.timestamp, this.parent.timeKeeper);
|
|
168
|
-
event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
|
|
169
|
-
}
|
|
154
|
+
if (!event || this.parent.blocked) return;
|
|
155
|
+
|
|
156
|
+
/** because we've waited until draining to process the buffered rrweb events, we can guarantee the timekeeper exists */
|
|
157
|
+
event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
|
|
170
158
|
event.__serialized = stringify(event);
|
|
171
159
|
const eventBytes = event.__serialized.length;
|
|
172
160
|
/** The estimated size of the payload after compression */
|
|
@@ -181,26 +169,18 @@ export class Recorder {
|
|
|
181
169
|
}
|
|
182
170
|
|
|
183
171
|
// meta event
|
|
184
|
-
|
|
185
|
-
this.currentBufferTarget.hasMeta = true;
|
|
186
|
-
}
|
|
172
|
+
this.events.hasMeta ||= event.type === RRWEB_EVENT_TYPES.Meta;
|
|
187
173
|
// snapshot event
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.currentBufferTarget.add(event);
|
|
174
|
+
this.events.hasSnapshot ||= this.hasSeenSnapshot ||= event.type === RRWEB_EVENT_TYPES.FullSnapshot;
|
|
175
|
+
|
|
176
|
+
//* 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 */
|
|
177
|
+
this.events.add(event, eventBytes);
|
|
193
178
|
|
|
194
179
|
// We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
|
|
195
180
|
// it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
|
|
196
|
-
if ((
|
|
197
|
-
// if we've made it to the ideal size of ~
|
|
198
|
-
|
|
199
|
-
this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent);
|
|
200
|
-
} else {
|
|
201
|
-
// we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
|
|
202
|
-
this.#preloaded.push(new RecorderEvents(this.shouldFix));
|
|
203
|
-
}
|
|
181
|
+
if ((this.events.hasSnapshot && this.events.hasMeta || payloadSize > IDEAL_PAYLOAD_SIZE) && this.parent.mode === MODE.FULL) {
|
|
182
|
+
// if we've made it to the ideal size of ~16kb before the interval timer, we should send early.
|
|
183
|
+
this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent);
|
|
204
184
|
}
|
|
205
185
|
}
|
|
206
186
|
|
|
@@ -214,13 +194,13 @@ export class Recorder {
|
|
|
214
194
|
}
|
|
215
195
|
}
|
|
216
196
|
clearTimestamps() {
|
|
217
|
-
this.
|
|
197
|
+
this.events.cycleTimestamp = undefined;
|
|
218
198
|
}
|
|
219
199
|
|
|
220
200
|
/** Estimate the payload size */
|
|
221
201
|
getPayloadSize(newBytes = 0) {
|
|
222
202
|
// the query param padding constant gives us some padding for the other metadata to be safely injected
|
|
223
|
-
return this.estimateCompression(this.
|
|
203
|
+
return this.estimateCompression(this.events.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING;
|
|
224
204
|
}
|
|
225
205
|
|
|
226
206
|
/** Extensive research has yielded about an 88% compression factor on these payloads.
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { gosNREUMOriginals } from '../../../common/window/nreum';
|
|
6
6
|
import { canEnableSessionTracking } from '../../utils/feature-gates';
|
|
7
|
-
import { originTime } from '../../../common/constants/runtime';
|
|
8
7
|
export function hasReplayPrerequisite(agentInit) {
|
|
9
8
|
return !!gosNREUMOriginals().o.MO &&
|
|
10
9
|
// Session Replay cannot work without Mutation Observer
|
|
@@ -15,17 +14,6 @@ export function hasReplayPrerequisite(agentInit) {
|
|
|
15
14
|
export function isPreloadAllowed(agentInit) {
|
|
16
15
|
return agentInit?.session_replay.preload === true && hasReplayPrerequisite(agentInit);
|
|
17
16
|
}
|
|
18
|
-
export function buildNRMetaNode(timestamp, timeKeeper) {
|
|
19
|
-
const correctedTimestamp = timeKeeper.correctAbsoluteTimestamp(timestamp);
|
|
20
|
-
return {
|
|
21
|
-
originalTimestamp: timestamp,
|
|
22
|
-
correctedTimestamp,
|
|
23
|
-
timestampDiff: timestamp - correctedTimestamp,
|
|
24
|
-
originTime,
|
|
25
|
-
correctedOriginTime: timeKeeper.correctedOriginTime,
|
|
26
|
-
originTimeDiff: Math.floor(originTime - timeKeeper.correctedOriginTime)
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
17
|
export function customMasker(text, element) {
|
|
30
18
|
try {
|
|
31
19
|
if (typeof element?.type === 'string') {
|
|
@@ -34,8 +34,9 @@ export class AggregateBase extends FeatureBase {
|
|
|
34
34
|
/** @type {Boolean} indicates if custom attributes are combined in each event payload for size estimation purposes. this is set to true in derived classes that need to evaluate custom attributes separately from the event payload */
|
|
35
35
|
this.customAttributesAreSeparate = false;
|
|
36
36
|
/** @type {Boolean} indicates if the feature can harvest early. This is set to false in derived classes that need to block early harvests, like ajax under certain conditions */
|
|
37
|
-
this.canHarvestEarly = true;
|
|
38
|
-
|
|
37
|
+
this.canHarvestEarly = true;
|
|
38
|
+
/** @type {Boolean} indicates if the feature is actively in a retry deferral period */
|
|
39
|
+
this.isRetrying = false;
|
|
39
40
|
this.harvestOpts = {}; // features aggregate classes can define custom opts for when their harvest is called
|
|
40
41
|
|
|
41
42
|
const agentEntityGuid = this.agentRef?.runtime?.appMetadata?.agents?.[0]?.entityGuid;
|
|
@@ -83,7 +84,7 @@ export class AggregateBase extends FeatureBase {
|
|
|
83
84
|
* @returns void
|
|
84
85
|
*/
|
|
85
86
|
decideEarlyHarvest() {
|
|
86
|
-
if (!this.canHarvestEarly) return;
|
|
87
|
+
if (!this.canHarvestEarly || this.blocked || this.isRetrying) return;
|
|
87
88
|
const estimatedSize = this.events.byteSize() + (this.customAttributesAreSeparate ? this.agentRef.runtime.jsAttributesMetadata.bytes : 0);
|
|
88
89
|
if (estimatedSize > IDEAL_PAYLOAD_SIZE) {
|
|
89
90
|
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
@@ -172,8 +173,8 @@ export class AggregateBase extends FeatureBase {
|
|
|
172
173
|
* @param {boolean=} result.retry - whether the harvest should be retried
|
|
173
174
|
*/
|
|
174
175
|
postHarvestCleanup(result = {}) {
|
|
175
|
-
|
|
176
|
-
if (
|
|
176
|
+
this.isRetrying = result.sent && result.retry;
|
|
177
|
+
if (this.isRetrying) this.events.reloadSave(this.harvestOpts, result.targetApp?.entityGuid);
|
|
177
178
|
this.events.clearSave(this.harvestOpts, result.targetApp?.entityGuid);
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -38,10 +38,11 @@ export class EventBuffer {
|
|
|
38
38
|
/**
|
|
39
39
|
* Add feature-processed event to our buffer. If this event would cause our total raw size to exceed the set max payload size, it is dropped.
|
|
40
40
|
* @param {any} event - any primitive type or object
|
|
41
|
+
* @param {number} [evaluatedSize] - the evalated size of the event, if already done so before storing in the event buffer
|
|
41
42
|
* @returns {Boolean} true if successfully added; false otherwise
|
|
42
43
|
*/
|
|
43
|
-
add(event) {
|
|
44
|
-
const addSize = stringify(event)?.length || 0; // (estimate) # of bytes a directly stringified event it would take to send
|
|
44
|
+
add(event, evaluatedSize) {
|
|
45
|
+
const addSize = evaluatedSize || stringify(event)?.length || 0; // (estimate) # of bytes a directly stringified event it would take to send
|
|
45
46
|
if (this.#rawBytes + addSize > this.maxPayloadSize) {
|
|
46
47
|
const smTag = inject => "EventBuffer/".concat(inject, "/Dropped/Bytes");
|
|
47
48
|
this.featureAgg?.reportSupportabilityMetric(smTag(this.featureAgg.featureName), addSize); // bytes dropped for this feature will aggregate with this metric tag
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initiate a harvest call.
|
|
3
|
+
* @param {NetworkSendSpec} param0 Specification for sending data
|
|
4
|
+
* @returns {boolean} True if a network call was made. Note that this does not mean or guarantee that it was successful.
|
|
5
|
+
*/
|
|
6
|
+
export function send(agentRef: any, { endpoint, targetApp, payload, localOpts, submitMethod, cbFinished, raw, featureName }: {
|
|
7
|
+
endpoint: any;
|
|
8
|
+
targetApp: any;
|
|
9
|
+
payload: any;
|
|
10
|
+
localOpts?: {} | undefined;
|
|
11
|
+
submitMethod: any;
|
|
12
|
+
cbFinished: any;
|
|
13
|
+
raw: any;
|
|
14
|
+
featureName: any;
|
|
15
|
+
}): boolean;
|
|
1
16
|
export class Harvester {
|
|
2
17
|
constructor(agentRef: any);
|
|
3
18
|
initializedAggregates: any[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harvester.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvester.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"harvester.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvester.js"],"names":[],"mappings":"AA6GA;;;;IAII;AACJ;;;;;;;;;IAFc,OAAO,CA+FpB;AAxLD;IAIE,2BAUC;IAZD,6BAA0B;IAGxB,cAAwB;IAW1B,wCASC;IAED;;;;;OAKG;IACH,iCAJW,MAAM,cACN,MAAM,GACJ,OAAO,CA+CnB;;CACF;8BAGY,OAAO,YAAY,EAAE,eAAe"}
|
|
@@ -30,7 +30,6 @@ export class Aggregate extends AggregateBase {
|
|
|
30
30
|
targetApp: undefined;
|
|
31
31
|
payload: undefined;
|
|
32
32
|
}[] | undefined;
|
|
33
|
-
getCorrectedTimestamp(node: any): any;
|
|
34
33
|
/**
|
|
35
34
|
* returns the timestamps for the earliest and latest nodes in the provided array, even if out of order
|
|
36
35
|
* @param {Object[]} [nodes] - the nodes to evaluate
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAwBA;IACE,2BAAiC;IAIjC,sCAqFC;IAxFD,aAAe;IAKb,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAEnB,0BAA0B;IAC1B,kBAAqB;IACrB,6CAA6C;IAC7C,gBAA2B;IAE3B,cAA8B;IAC9B,kBAA+C;IAG/C,kCAAqG;IAmEvG,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;OAKG;IACH,4BAJW,OAAO,iBACP,OAAO,GACL,IAAI,CAkDhB;IAED,2BASC;IAED;;;oBAyCC;IAED;;;;OAIG;IACH,6BAHW,MAAM,EAAE,GACN;QAAE,UAAU,EAAE,MAAM,GAAC,SAAS,CAAC;QAAC,SAAS,EAAE,MAAM,GAAC,SAAS,CAAA;KAAE,CAUzE;IAED;;;;;;;;;;MAsEC;IAED,sCAKC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;IAED,yCAIC;CACF;8BA3W6B,4BAA4B"}
|
|
@@ -15,7 +15,7 @@ export class RecorderEvents {
|
|
|
15
15
|
hasError: boolean;
|
|
16
16
|
/** Payload metadata -- Denotes whether all stylesheet elements were able to be inlined */
|
|
17
17
|
inlinedAllStylesheets: boolean;
|
|
18
|
-
add(event: any): void;
|
|
18
|
+
add(event: any, evaluatedSize: any): void;
|
|
19
19
|
get events(): any[];
|
|
20
20
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
21
21
|
get payloadBytesEstimation(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recorder-events.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder-events.js"],"names":[],"mappings":"AAMA;IAiBE,+CAGC;IAjBD;;SAEK;IACL,uBAA2B;IAC3B;;;MAGE;IACF,qBAAmB;IACnB,4IAA4I;IAC5I,iBAAe;IACf,+HAA+H;IAC/H,kBAAgB;IAGd,0FAA0F;IAC1F,+BAAoD;IAGtD,
|
|
1
|
+
{"version":3,"file":"recorder-events.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder-events.js"],"names":[],"mappings":"AAMA;IAiBE,+CAGC;IAjBD;;SAEK;IACL,uBAA2B;IAC3B;;;MAGE;IACF,qBAAmB;IACnB,4IAA4I;IAC5I,iBAAe;IACf,+HAA+H;IAC/H,kBAAgB;IAGd,0FAA0F;IAC1F,+BAAoD;IAGtD,0CAEC;IAED,oBAEC;IAED,qGAAqG;IACrG,qCAEC;;CACF"}
|
|
@@ -4,9 +4,11 @@ export class Recorder {
|
|
|
4
4
|
parent: any;
|
|
5
5
|
/** A flag that can be set to false by failing conversions to stop the fetching process */
|
|
6
6
|
shouldFix: any;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
/**
|
|
7
|
+
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
8
|
+
events: RecorderEvents;
|
|
9
|
+
/** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
|
|
10
|
+
backloggedEvents: RecorderEvents;
|
|
11
|
+
/** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
|
|
10
12
|
hasSeenSnapshot: boolean;
|
|
11
13
|
/** 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 */
|
|
12
14
|
lastMeta: boolean;
|
|
@@ -14,15 +16,15 @@ export class Recorder {
|
|
|
14
16
|
stopRecording: () => void;
|
|
15
17
|
getEvents(): {
|
|
16
18
|
events: any[];
|
|
17
|
-
payloadBytesEstimation: number;
|
|
18
19
|
type: string;
|
|
19
20
|
cycleTimestamp: number;
|
|
20
|
-
|
|
21
|
-
hasMeta: boolean;
|
|
21
|
+
payloadBytesEstimation: number;
|
|
22
22
|
hasError: boolean;
|
|
23
|
+
hasMeta: boolean;
|
|
24
|
+
hasSnapshot: boolean;
|
|
23
25
|
inlinedAllStylesheets: boolean;
|
|
24
26
|
};
|
|
25
|
-
/** Clears the buffer (this
|
|
27
|
+
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
26
28
|
clearBuffer(): void;
|
|
27
29
|
/** Begin recording using configured recording lib */
|
|
28
30
|
startRecording(): void;
|
|
@@ -33,7 +35,7 @@ export class Recorder {
|
|
|
33
35
|
* @param {*} isCheckout - Flag indicating if the payload was triggered as a checkout
|
|
34
36
|
*/
|
|
35
37
|
audit(event: any, isCheckout: any): void;
|
|
36
|
-
/** Store a payload in the buffer (this
|
|
38
|
+
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
37
39
|
store(event: any, isCheckout: any): void;
|
|
38
40
|
/** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
|
|
39
41
|
takeFullSnapshot(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAqBA;IAME,yBAkBC;IAjBC,sDAAsD;IACtD,YAAoB;IACpB,0FAA0F;IAC1F,eAAyE;IAEzE,iHAAiH;IACjH,uBAAgD;IAChD,mFAAmF;IACnF,iCAA0D;IAC1D,uIAAuI;IACvI,yBAA4B;IAC5B,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAA+E;IAKjF;;;;;;;;;MAWC;IAED,kFAAkF;IAClF,oBAGC;IAED,qDAAqD;IACrD,uBAiCC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,yHAAyH;IACzH,yCAgCC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BArM8B,mBAAmB"}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
export function hasReplayPrerequisite(agentInit: any): boolean;
|
|
2
2
|
export function isPreloadAllowed(agentInit: any): boolean;
|
|
3
|
-
export function buildNRMetaNode(timestamp: any, timeKeeper: any): {
|
|
4
|
-
originalTimestamp: any;
|
|
5
|
-
correctedTimestamp: any;
|
|
6
|
-
timestampDiff: number;
|
|
7
|
-
originTime: number;
|
|
8
|
-
correctedOriginTime: any;
|
|
9
|
-
originTimeDiff: number;
|
|
10
|
-
};
|
|
11
3
|
export function customMasker(text: any, element: any): any;
|
|
12
4
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/utils.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/utils.js"],"names":[],"mappings":"AAOA,+DAIC;AAED,0DAEC;AAED,2DAUC"}
|
|
@@ -10,6 +10,8 @@ export class AggregateBase extends FeatureBase {
|
|
|
10
10
|
customAttributesAreSeparate: boolean;
|
|
11
11
|
/** @type {Boolean} indicates if the feature can harvest early. This is set to false in derived classes that need to block early harvests, like ajax under certain conditions */
|
|
12
12
|
canHarvestEarly: boolean;
|
|
13
|
+
/** @type {Boolean} indicates if the feature is actively in a retry deferral period */
|
|
14
|
+
isRetrying: boolean;
|
|
13
15
|
harvestOpts: {};
|
|
14
16
|
events: any;
|
|
15
17
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"AAsBA;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,
|
|
1
|
+
{"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"AAsBA;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EA2BhB;IAvBC,iBAAwB;IAIxB,uOAAuO;IACvO,qCAAwC;IACxC,gLAAgL;IAChL,yBAA2B;IAC3B,sFAAsF;IACtF,oBAAuB;IAEvB,gBAAqB;IA4BjB,YAAwJ;IAW9J;;;;OAIG;IACH,2BAOC;IAED;;;;OAIG;IACH,yBAHW,MAAM,EAAE,gBAwBlB;IAED;;OAEG;IACH,cAGC;IADC,6BAAmB;IAGrB,qCAEC;IAED;;;;;;OAMG;IACH,uDAJW,MAAM,GAAC,SAAS,SAyB1B;IAED;;;;;;;OAOG;IACH,4BALG;QAAwB,SAAS,GAAzB,MAAM,YAAC;KACf,QAQF;IAED;;;OAGG;IACH,6CAsBC;IAED;;;OAGG;IACH,2CAOC;IALC,gBAA6C;IAO/C;;;;OAIG;IACH,uCAHW,GAAC,UACD,GAAC,QAIX;;CACF;4BA/N2B,gBAAgB"}
|
|
@@ -15,9 +15,10 @@ export class EventBuffer {
|
|
|
15
15
|
/**
|
|
16
16
|
* Add feature-processed event to our buffer. If this event would cause our total raw size to exceed the set max payload size, it is dropped.
|
|
17
17
|
* @param {any} event - any primitive type or object
|
|
18
|
+
* @param {number} [evaluatedSize] - the evalated size of the event, if already done so before storing in the event buffer
|
|
18
19
|
* @returns {Boolean} true if successfully added; false otherwise
|
|
19
20
|
*/
|
|
20
|
-
add(event: any): boolean;
|
|
21
|
+
add(event: any, evaluatedSize?: number): boolean;
|
|
21
22
|
/**
|
|
22
23
|
* Merges events in the buffer that match the given criteria.
|
|
23
24
|
* @param {Function} matcher - A function that takes an event and returns true if it should be merged.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-buffer.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/event-buffer.js"],"names":[],"mappings":"AAOA;IAME;;;;OAIG;IACH,kDAFW,MAAM,EAKhB;IAFC,uBAAoC;IACpC,+BAA4B;IAG9B,qBAEC;IAED,mBAEC;IAED,aAEC;IAED,mBAEC;IAED,+CAEC;IAED
|
|
1
|
+
{"version":3,"file":"event-buffer.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/event-buffer.js"],"names":[],"mappings":"AAOA;IAME;;;;OAIG;IACH,kDAFW,MAAM,EAKhB;IAFC,uBAAoC;IACpC,+BAA4B;IAG9B,qBAEC;IAED,mBAEC;IAED,aAEC;IAED,mBAEC;IAED,+CAEC;IAED;;;;;OAKG;IACH,WAJW,GAAG,kBACH,MAAM,WAehB;IAED;;;;;KAKC;IACD,+BAHS,MAAM,GACJ,OAAO,CAWjB;IAED;;;;;;;OAOG;IACH,aALG;QAAsB,eAAe;QACf,YAAY;QACZ,gBAAgB;KACtC,GAAU,IAAI,CAWhB;IAED;;OAEG;IACH,aAGC;IAED;;OAEG;IACH,kBAGC;IAED;;OAEG;IACH,mBAKC;;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.297.0-rc.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -295,4 +295,4 @@
|
|
|
295
295
|
"README.md",
|
|
296
296
|
"CHANGELOG.md"
|
|
297
297
|
]
|
|
298
|
-
}
|
|
298
|
+
}
|
|
@@ -112,7 +112,7 @@ const warnings = {}
|
|
|
112
112
|
* @param {NetworkSendSpec} param0 Specification for sending data
|
|
113
113
|
* @returns {boolean} True if a network call was made. Note that this does not mean or guarantee that it was successful.
|
|
114
114
|
*/
|
|
115
|
-
function send (agentRef, { endpoint, targetApp, payload, localOpts = {}, submitMethod, cbFinished, raw, featureName }) {
|
|
115
|
+
export function send (agentRef, { endpoint, targetApp, payload, localOpts = {}, submitMethod, cbFinished, raw, featureName }) {
|
|
116
116
|
if (!agentRef.info.errorBeacon) return false
|
|
117
117
|
|
|
118
118
|
let { body, qs } = cleanPayload(payload)
|
|
@@ -218,7 +218,7 @@ function send (agentRef, { endpoint, targetApp, payload, localOpts = {}, submitM
|
|
|
218
218
|
function cleanPayload (payload = {}) {
|
|
219
219
|
const clean = (input) => {
|
|
220
220
|
if ((typeof Uint8Array !== 'undefined' && input instanceof Uint8Array) || Array.isArray(input)) return input
|
|
221
|
-
if (typeof input === 'string') return input
|
|
221
|
+
if (typeof input === 'string') return input
|
|
222
222
|
return Object.entries(input || {}).reduce((accumulator, [key, value]) => {
|
|
223
223
|
if ((typeof value === 'number') ||
|
|
224
224
|
(typeof value === 'string' && value.length > 0) ||
|
|
@@ -18,7 +18,6 @@ import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/sessi
|
|
|
18
18
|
import { stringify } from '../../../common/util/stringify'
|
|
19
19
|
import { stylesheetEvaluator } from '../shared/stylesheet-evaluator'
|
|
20
20
|
import { now } from '../../../common/timing/now'
|
|
21
|
-
import { buildNRMetaNode } from '../shared/utils'
|
|
22
21
|
import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
|
|
23
22
|
import { cleanURL } from '../../../common/url/clean-url'
|
|
24
23
|
import { canEnableSessionTracking } from '../../utils/feature-gates'
|
|
@@ -94,8 +93,7 @@ export class Aggregate extends AggregateBase {
|
|
|
94
93
|
}
|
|
95
94
|
return
|
|
96
95
|
}
|
|
97
|
-
this.drain()
|
|
98
|
-
this.initializeRecording(srMode)
|
|
96
|
+
this.initializeRecording(srMode).then(() => { this.drain() })
|
|
99
97
|
}).then(() => {
|
|
100
98
|
if (this.mode === MODE.OFF) {
|
|
101
99
|
this.recorder?.stopRecording() // stop any conservative preload recording launched by instrument
|
|
@@ -121,7 +119,7 @@ export class Aggregate extends AggregateBase {
|
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
handleError (e) {
|
|
124
|
-
if (this.recorder) this.recorder.
|
|
122
|
+
if (this.recorder) this.recorder.events.hasError = true
|
|
125
123
|
// run once
|
|
126
124
|
if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
|
|
127
125
|
this.switchToFull()
|
|
@@ -171,10 +169,10 @@ export class Aggregate extends AggregateBase {
|
|
|
171
169
|
|
|
172
170
|
if (!this.recorder) {
|
|
173
171
|
try {
|
|
174
|
-
|
|
172
|
+
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
175
173
|
const { Recorder } = (await import(/* webpackChunkName: "recorder" */'../shared/recorder'))
|
|
176
174
|
this.recorder = new Recorder(this)
|
|
177
|
-
this.recorder.
|
|
175
|
+
this.recorder.events.hasError = this.errorNoticed
|
|
178
176
|
} catch (err) {
|
|
179
177
|
return this.abort(ABORT_REASONS.IMPORT)
|
|
180
178
|
}
|
|
@@ -189,11 +187,6 @@ export class Aggregate extends AggregateBase {
|
|
|
189
187
|
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
190
188
|
// The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
|
|
191
189
|
|
|
192
|
-
// If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
|
|
193
|
-
if (this.mode === MODE.FULL && this.recorder?.getEvents().type === 'preloaded') {
|
|
194
|
-
this.prepUtils().then(() => this.agentRef.runtime.harvester.triggerHarvestFor(this))
|
|
195
|
-
}
|
|
196
|
-
|
|
197
190
|
await this.prepUtils()
|
|
198
191
|
|
|
199
192
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording()
|
|
@@ -231,24 +224,10 @@ export class Aggregate extends AggregateBase {
|
|
|
231
224
|
|
|
232
225
|
let len = 0
|
|
233
226
|
if (!!this.gzipper && !!this.u8) {
|
|
234
|
-
payload.body = this.gzipper(this.u8(`[${payload.body.map(({ __serialized
|
|
235
|
-
if (e.__newrelic && __serialized) return __serialized
|
|
236
|
-
const output = { ...e }
|
|
237
|
-
if (!output.__newrelic) {
|
|
238
|
-
output.__newrelic = buildNRMetaNode(e.timestamp, this.timeKeeper)
|
|
239
|
-
output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(e.timestamp)
|
|
240
|
-
}
|
|
241
|
-
return stringify(output)
|
|
242
|
-
}).join(',')}]`))
|
|
227
|
+
payload.body = this.gzipper(this.u8(`[${payload.body.map(({ __serialized }) => (__serialized)).join(',')}]`))
|
|
243
228
|
len = payload.body.length
|
|
244
229
|
} else {
|
|
245
|
-
payload.body
|
|
246
|
-
if (node.__newrelic) return node
|
|
247
|
-
const output = { ...node }
|
|
248
|
-
output.__newrelic = buildNRMetaNode(node.timestamp, this.timeKeeper)
|
|
249
|
-
output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(node.timestamp)
|
|
250
|
-
return output
|
|
251
|
-
})
|
|
230
|
+
for (let idx in payload.body) delete payload.body[idx].__serialized
|
|
252
231
|
len = stringify(payload.body).length
|
|
253
232
|
}
|
|
254
233
|
|
|
@@ -269,12 +248,6 @@ export class Aggregate extends AggregateBase {
|
|
|
269
248
|
return [payloadOutput]
|
|
270
249
|
}
|
|
271
250
|
|
|
272
|
-
getCorrectedTimestamp (node) {
|
|
273
|
-
if (!node?.timestamp) return
|
|
274
|
-
if (node.__newrelic) return node.timestamp
|
|
275
|
-
return this.timeKeeper.correctAbsoluteTimestamp(node.timestamp)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
251
|
/**
|
|
279
252
|
* returns the timestamps for the earliest and latest nodes in the provided array, even if out of order
|
|
280
253
|
* @param {Object[]} [nodes] - the nodes to evaluate
|
|
@@ -318,8 +291,8 @@ export class Aggregate extends AggregateBase {
|
|
|
318
291
|
|
|
319
292
|
const { firstEvent, lastEvent } = this.getFirstAndLastNodes(events)
|
|
320
293
|
// from rrweb node || from when the harvest cycle started
|
|
321
|
-
const firstTimestamp =
|
|
322
|
-
const lastTimestamp =
|
|
294
|
+
const firstTimestamp = firstEvent?.timestamp || Math.floor(this.timeKeeper.correctAbsoluteTimestamp(recorderEvents.cycleTimestamp))
|
|
295
|
+
const lastTimestamp = lastEvent?.timestamp || Math.floor(this.timeKeeper.correctRelativeTimestamp(relativeNow))
|
|
323
296
|
|
|
324
297
|
const agentMetadata = agentRuntime.appMetadata?.agents?.[0] || {}
|
|
325
298
|
|
|
@@ -78,7 +78,7 @@ export class Instrument extends InstrumentBase {
|
|
|
78
78
|
|
|
79
79
|
// If startReplay() has been used by this point, we must record in full mode regardless of session preload:
|
|
80
80
|
// Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
|
|
81
|
-
this.recorder ??= new Recorder({ mode: this.#mode,
|
|
81
|
+
this.recorder ??= new Recorder({ ...this, mode: this.#mode, agentRef: this.#agentRef, trigger, timeKeeper: this.#agentRef.runtime.timeKeeper }) // if TK exists due to deferred state, pass it
|
|
82
82
|
this.recorder.startRecording()
|
|
83
83
|
this.abortHandler = this.recorder.stopRecording
|
|
84
84
|
} catch (err) {
|