@newrelic/browser-agent 1.251.0 → 1.251.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 +8 -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/harvest-scheduler.js +2 -1
- package/dist/cjs/features/metrics/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +26 -15
- package/dist/cjs/features/session_replay/shared/recorder.js +1 -1
- package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +30 -25
- 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/harvest-scheduler.js +2 -1
- package/dist/esm/features/metrics/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +26 -15
- package/dist/esm/features/session_replay/shared/recorder.js +1 -1
- package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +30 -25
- package/dist/types/features/session_replay/aggregate/index.d.ts +8 -3
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/harvest/harvest-scheduler.js +1 -1
- package/src/features/metrics/aggregate/index.js +1 -1
- package/src/features/session_replay/aggregate/index.js +26 -16
- package/src/features/session_replay/shared/recorder.js +1 -1
- package/src/features/session_replay/shared/stylesheet-evaluator.js +23 -18
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.251.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.0...v1.251.1) (2024-01-29)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Fix deferred Session Replay payloads ([#868](https://github.com/newrelic/newrelic-browser-agent/issues/868)) ([f69e4b0](https://github.com/newrelic/newrelic-browser-agent/commit/f69e4b0eba5a54f4e67316f5e6a30090cf7360cc))
|
|
12
|
+
* Pass unload options to simultaneous harvests in Session Replay ([#870](https://github.com/newrelic/newrelic-browser-agent/issues/870)) ([655aa5d](https://github.com/newrelic/newrelic-browser-agent/commit/655aa5d261d03f71086d3cfc73cb72db51cb28c7))
|
|
13
|
+
|
|
6
14
|
## [1.251.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.250.0...v1.251.0) (2024-01-24)
|
|
7
15
|
|
|
8
16
|
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.251.
|
|
15
|
+
const VERSION = exports.VERSION = "1.251.1";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.251.
|
|
15
|
+
const VERSION = exports.VERSION = "1.251.1";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -109,7 +109,8 @@ class HarvestScheduler extends _sharedContext.SharedContext {
|
|
|
109
109
|
if (!submitMethod) return false;
|
|
110
110
|
const retry = !opts?.unload && submitMethod === submitData.xhr;
|
|
111
111
|
payload = this.opts.getPayload({
|
|
112
|
-
retry
|
|
112
|
+
retry,
|
|
113
|
+
opts
|
|
113
114
|
});
|
|
114
115
|
if (!payload) {
|
|
115
116
|
if (this.started) {
|
|
@@ -113,7 +113,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
113
113
|
|
|
114
114
|
// [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
|
|
115
115
|
(0, _eventListenerOpts.windowAddEventListener)('pageshow', evt => {
|
|
116
|
-
if (evt
|
|
116
|
+
if (evt?.persisted) {
|
|
117
117
|
this.storeSupportabilityMetrics('Generic/BFCache/PageRestored');
|
|
118
118
|
}
|
|
119
119
|
});
|
|
@@ -32,7 +32,6 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
|
|
|
32
32
|
* It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
|
|
33
33
|
* functionality is validated and a full user experience is curated.
|
|
34
34
|
*/
|
|
35
|
-
let gzipper, u8;
|
|
36
35
|
class Aggregate extends _aggregateBase.AggregateBase {
|
|
37
36
|
static featureName = _constants.FEATURE_NAME;
|
|
38
37
|
// pass the recorder into the aggregator
|
|
@@ -44,8 +43,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
44
43
|
this.initialized = false;
|
|
45
44
|
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
46
45
|
this.blocked = false;
|
|
47
|
-
/**
|
|
48
|
-
this.
|
|
46
|
+
/** populated with the gzipper lib async */
|
|
47
|
+
this.gzipper = undefined;
|
|
48
|
+
/** populated with the u8 string lib async */
|
|
49
|
+
this.u8 = undefined;
|
|
49
50
|
/** the mode to start in. Defaults to off */
|
|
50
51
|
const {
|
|
51
52
|
session
|
|
@@ -92,6 +93,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
92
93
|
getPayload: this.prepareHarvest.bind(this),
|
|
93
94
|
raw: true
|
|
94
95
|
}, this);
|
|
96
|
+
if (this.recorder?.getEvents().type === 'preloaded') {
|
|
97
|
+
this.prepUtils().then(() => {
|
|
98
|
+
this.scheduler.runHarvest();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
95
101
|
(0, _registerHandler.registerHandler)('recordReplay', () => {
|
|
96
102
|
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
97
103
|
if (this.blocked || !this.entitled) return;
|
|
@@ -196,24 +202,29 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
196
202
|
// We only report (harvest) in FULL mode
|
|
197
203
|
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
198
204
|
}
|
|
205
|
+
await this.prepUtils();
|
|
206
|
+
if (!this.recorder.recording) this.recorder.startRecording();
|
|
207
|
+
this.syncWithSessionManager({
|
|
208
|
+
sessionReplayMode: this.mode
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
async prepUtils() {
|
|
199
212
|
try {
|
|
200
213
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
201
214
|
const {
|
|
202
215
|
gzipSync,
|
|
203
216
|
strToU8
|
|
204
217
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "compressor" */'fflate')));
|
|
205
|
-
gzipper = gzipSync;
|
|
206
|
-
u8 = strToU8;
|
|
218
|
+
this.gzipper = gzipSync;
|
|
219
|
+
this.u8 = strToU8;
|
|
207
220
|
} catch (err) {
|
|
208
221
|
// compressor failed to load, but we can still record without compression as a last ditch effort
|
|
209
|
-
this.shouldCompress = false;
|
|
210
222
|
}
|
|
211
|
-
if (!this.recorder.recording) this.recorder.startRecording();
|
|
212
|
-
this.syncWithSessionManager({
|
|
213
|
-
sessionReplayMode: this.mode
|
|
214
|
-
});
|
|
215
223
|
}
|
|
216
224
|
prepareHarvest() {
|
|
225
|
+
let {
|
|
226
|
+
opts
|
|
227
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
217
228
|
if (!this.recorder) return;
|
|
218
229
|
const recorderEvents = this.recorder.getEvents();
|
|
219
230
|
// get the event type and use that to trigger another harvest if needed
|
|
@@ -224,8 +235,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
224
235
|
return;
|
|
225
236
|
}
|
|
226
237
|
let len = 0;
|
|
227
|
-
if (this.
|
|
228
|
-
payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
238
|
+
if (!!this.gzipper && !!this.u8) {
|
|
239
|
+
payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
229
240
|
len = payload.body.length;
|
|
230
241
|
this.scheduler.opts.gzip = true;
|
|
231
242
|
} else {
|
|
@@ -251,7 +262,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
251
262
|
sessionReplaySentFirstChunk: true
|
|
252
263
|
});
|
|
253
264
|
this.recorder.clearBuffer();
|
|
254
|
-
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
|
|
265
|
+
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
|
|
255
266
|
return [payload];
|
|
256
267
|
}
|
|
257
268
|
getHarvestContents(recorderEvents) {
|
|
@@ -282,7 +293,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
282
293
|
const relativeNow = (0, _now.now)();
|
|
283
294
|
const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
|
|
284
295
|
const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
|
|
285
|
-
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
|
|
296
|
+
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
|
|
286
297
|
const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
|
|
287
298
|
return {
|
|
288
299
|
qs: {
|
|
@@ -293,7 +304,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
293
304
|
attributes: (0, _encode.obj)({
|
|
294
305
|
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
295
306
|
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
296
|
-
...(this.
|
|
307
|
+
...(!!this.gzipper && !!this.u8 && {
|
|
297
308
|
content_encoding: 'gzip'
|
|
298
309
|
}),
|
|
299
310
|
'replay.firstTimestamp': firstTimestamp,
|
|
@@ -194,7 +194,7 @@ class Recorder {
|
|
|
194
194
|
* https://staging.onenr.io/037jbJWxbjy
|
|
195
195
|
* */
|
|
196
196
|
estimateCompression(data) {
|
|
197
|
-
if (this.
|
|
197
|
+
if (!!this.parent.gzipper && !!this.parent.u8) return data * _constants.AVG_COMPRESSION;
|
|
198
198
|
return data;
|
|
199
199
|
}
|
|
200
200
|
}
|
|
@@ -60,33 +60,38 @@ class StylesheetEvaluator {
|
|
|
60
60
|
* @returns {Promise}
|
|
61
61
|
*/
|
|
62
62
|
async #fetchAndOverride(target, href) {
|
|
63
|
-
const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
|
|
64
|
-
if (!stylesheetContents.ok) {
|
|
65
|
-
this.failedToFix = true;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const stylesheetText = await stylesheetContents.text();
|
|
69
63
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
|
|
65
|
+
if (!stylesheetContents.ok) {
|
|
66
|
+
this.failedToFix = true;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const stylesheetText = await stylesheetContents.text();
|
|
70
|
+
try {
|
|
71
|
+
const cssSheet = new CSSStyleSheet();
|
|
72
|
+
await cssSheet.replace(stylesheetText);
|
|
73
|
+
Object.defineProperty(target, 'cssRules', {
|
|
74
|
+
get() {
|
|
75
|
+
return cssSheet.cssRules;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
Object.defineProperty(target, 'rules', {
|
|
79
|
+
get() {
|
|
80
|
+
return cssSheet.rules;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
// cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
|
|
85
|
+
// this is appended in prep of forking rrweb
|
|
86
|
+
Object.defineProperty(target, 'cssText', {
|
|
87
|
+
get() {
|
|
88
|
+
return stylesheetText;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.failedToFix = true;
|
|
92
|
+
}
|
|
82
93
|
} catch (err) {
|
|
83
|
-
//
|
|
84
|
-
// this is appended in prep of forking rrweb
|
|
85
|
-
Object.defineProperty(target, 'cssText', {
|
|
86
|
-
get() {
|
|
87
|
-
return stylesheetText;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
94
|
+
// failed to fetch
|
|
90
95
|
this.failedToFix = true;
|
|
91
96
|
}
|
|
92
97
|
}
|
|
@@ -102,7 +102,8 @@ export class HarvestScheduler extends SharedContext {
|
|
|
102
102
|
if (!submitMethod) return false;
|
|
103
103
|
const retry = !opts?.unload && submitMethod === submitData.xhr;
|
|
104
104
|
payload = this.opts.getPayload({
|
|
105
|
-
retry
|
|
105
|
+
retry,
|
|
106
|
+
opts
|
|
106
107
|
});
|
|
107
108
|
if (!payload) {
|
|
108
109
|
if (this.started) {
|
|
@@ -107,7 +107,7 @@ export class Aggregate extends AggregateBase {
|
|
|
107
107
|
|
|
108
108
|
// [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
|
|
109
109
|
windowAddEventListener('pageshow', evt => {
|
|
110
|
-
if (evt
|
|
110
|
+
if (evt?.persisted) {
|
|
111
111
|
this.storeSupportabilityMetrics('Generic/BFCache/PageRestored');
|
|
112
112
|
}
|
|
113
113
|
});
|
|
@@ -27,7 +27,6 @@ import { now } from '../../../common/timing/now';
|
|
|
27
27
|
import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants';
|
|
28
28
|
import { stringify } from '../../../common/util/stringify';
|
|
29
29
|
import { stylesheetEvaluator } from '../shared/stylesheet-evaluator';
|
|
30
|
-
let gzipper, u8;
|
|
31
30
|
export class Aggregate extends AggregateBase {
|
|
32
31
|
static featureName = FEATURE_NAME;
|
|
33
32
|
// pass the recorder into the aggregator
|
|
@@ -39,8 +38,10 @@ export class Aggregate extends AggregateBase {
|
|
|
39
38
|
this.initialized = false;
|
|
40
39
|
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
41
40
|
this.blocked = false;
|
|
42
|
-
/**
|
|
43
|
-
this.
|
|
41
|
+
/** populated with the gzipper lib async */
|
|
42
|
+
this.gzipper = undefined;
|
|
43
|
+
/** populated with the u8 string lib async */
|
|
44
|
+
this.u8 = undefined;
|
|
44
45
|
/** the mode to start in. Defaults to off */
|
|
45
46
|
const {
|
|
46
47
|
session
|
|
@@ -87,6 +88,11 @@ export class Aggregate extends AggregateBase {
|
|
|
87
88
|
getPayload: this.prepareHarvest.bind(this),
|
|
88
89
|
raw: true
|
|
89
90
|
}, this);
|
|
91
|
+
if (this.recorder?.getEvents().type === 'preloaded') {
|
|
92
|
+
this.prepUtils().then(() => {
|
|
93
|
+
this.scheduler.runHarvest();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
90
96
|
registerHandler('recordReplay', () => {
|
|
91
97
|
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
92
98
|
if (this.blocked || !this.entitled) return;
|
|
@@ -191,24 +197,29 @@ export class Aggregate extends AggregateBase {
|
|
|
191
197
|
// We only report (harvest) in FULL mode
|
|
192
198
|
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
193
199
|
}
|
|
200
|
+
await this.prepUtils();
|
|
201
|
+
if (!this.recorder.recording) this.recorder.startRecording();
|
|
202
|
+
this.syncWithSessionManager({
|
|
203
|
+
sessionReplayMode: this.mode
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async prepUtils() {
|
|
194
207
|
try {
|
|
195
208
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
196
209
|
const {
|
|
197
210
|
gzipSync,
|
|
198
211
|
strToU8
|
|
199
212
|
} = await import( /* webpackChunkName: "compressor" */'fflate');
|
|
200
|
-
gzipper = gzipSync;
|
|
201
|
-
u8 = strToU8;
|
|
213
|
+
this.gzipper = gzipSync;
|
|
214
|
+
this.u8 = strToU8;
|
|
202
215
|
} catch (err) {
|
|
203
216
|
// compressor failed to load, but we can still record without compression as a last ditch effort
|
|
204
|
-
this.shouldCompress = false;
|
|
205
217
|
}
|
|
206
|
-
if (!this.recorder.recording) this.recorder.startRecording();
|
|
207
|
-
this.syncWithSessionManager({
|
|
208
|
-
sessionReplayMode: this.mode
|
|
209
|
-
});
|
|
210
218
|
}
|
|
211
219
|
prepareHarvest() {
|
|
220
|
+
let {
|
|
221
|
+
opts
|
|
222
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
212
223
|
if (!this.recorder) return;
|
|
213
224
|
const recorderEvents = this.recorder.getEvents();
|
|
214
225
|
// get the event type and use that to trigger another harvest if needed
|
|
@@ -219,8 +230,8 @@ export class Aggregate extends AggregateBase {
|
|
|
219
230
|
return;
|
|
220
231
|
}
|
|
221
232
|
let len = 0;
|
|
222
|
-
if (this.
|
|
223
|
-
payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
233
|
+
if (!!this.gzipper && !!this.u8) {
|
|
234
|
+
payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
224
235
|
len = payload.body.length;
|
|
225
236
|
this.scheduler.opts.gzip = true;
|
|
226
237
|
} else {
|
|
@@ -246,7 +257,7 @@ export class Aggregate extends AggregateBase {
|
|
|
246
257
|
sessionReplaySentFirstChunk: true
|
|
247
258
|
});
|
|
248
259
|
this.recorder.clearBuffer();
|
|
249
|
-
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
|
|
260
|
+
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
|
|
250
261
|
return [payload];
|
|
251
262
|
}
|
|
252
263
|
getHarvestContents(recorderEvents) {
|
|
@@ -277,7 +288,7 @@ export class Aggregate extends AggregateBase {
|
|
|
277
288
|
const relativeNow = now();
|
|
278
289
|
const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
|
|
279
290
|
const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
|
|
280
|
-
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
|
|
291
|
+
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
|
|
281
292
|
const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
|
|
282
293
|
return {
|
|
283
294
|
qs: {
|
|
@@ -288,7 +299,7 @@ export class Aggregate extends AggregateBase {
|
|
|
288
299
|
attributes: encodeObj({
|
|
289
300
|
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
290
301
|
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
291
|
-
...(this.
|
|
302
|
+
...(!!this.gzipper && !!this.u8 && {
|
|
292
303
|
content_encoding: 'gzip'
|
|
293
304
|
}),
|
|
294
305
|
'replay.firstTimestamp': firstTimestamp,
|
|
@@ -188,7 +188,7 @@ export class Recorder {
|
|
|
188
188
|
* https://staging.onenr.io/037jbJWxbjy
|
|
189
189
|
* */
|
|
190
190
|
estimateCompression(data) {
|
|
191
|
-
if (this.
|
|
191
|
+
if (!!this.parent.gzipper && !!this.parent.u8) return data * AVG_COMPRESSION;
|
|
192
192
|
return data;
|
|
193
193
|
}
|
|
194
194
|
}
|
|
@@ -54,33 +54,38 @@ class StylesheetEvaluator {
|
|
|
54
54
|
* @returns {Promise}
|
|
55
55
|
*/
|
|
56
56
|
async #fetchAndOverride(target, href) {
|
|
57
|
-
const stylesheetContents = await originals.FETCH.bind(window)(href);
|
|
58
|
-
if (!stylesheetContents.ok) {
|
|
59
|
-
this.failedToFix = true;
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const stylesheetText = await stylesheetContents.text();
|
|
63
57
|
try {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
const stylesheetContents = await originals.FETCH.bind(window)(href);
|
|
59
|
+
if (!stylesheetContents.ok) {
|
|
60
|
+
this.failedToFix = true;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const stylesheetText = await stylesheetContents.text();
|
|
64
|
+
try {
|
|
65
|
+
const cssSheet = new CSSStyleSheet();
|
|
66
|
+
await cssSheet.replace(stylesheetText);
|
|
67
|
+
Object.defineProperty(target, 'cssRules', {
|
|
68
|
+
get() {
|
|
69
|
+
return cssSheet.cssRules;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
Object.defineProperty(target, 'rules', {
|
|
73
|
+
get() {
|
|
74
|
+
return cssSheet.rules;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
|
|
79
|
+
// this is appended in prep of forking rrweb
|
|
80
|
+
Object.defineProperty(target, 'cssText', {
|
|
81
|
+
get() {
|
|
82
|
+
return stylesheetText;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
this.failedToFix = true;
|
|
86
|
+
}
|
|
76
87
|
} catch (err) {
|
|
77
|
-
//
|
|
78
|
-
// this is appended in prep of forking rrweb
|
|
79
|
-
Object.defineProperty(target, 'cssText', {
|
|
80
|
-
get() {
|
|
81
|
-
return stylesheetText;
|
|
82
|
-
}
|
|
83
|
-
});
|
|
88
|
+
// failed to fetch
|
|
84
89
|
this.failedToFix = true;
|
|
85
90
|
}
|
|
86
91
|
}
|
|
@@ -5,8 +5,10 @@ export class Aggregate extends AggregateBase {
|
|
|
5
5
|
harvestTimeSeconds: any;
|
|
6
6
|
/** Set once the recorder has fully initialized after flag checks and sampling */
|
|
7
7
|
initialized: boolean;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/** populated with the gzipper lib async */
|
|
9
|
+
gzipper: typeof import("fflate").gzipSync | undefined;
|
|
10
|
+
/** populated with the u8 string lib async */
|
|
11
|
+
u8: typeof import("fflate").strToU8 | undefined;
|
|
10
12
|
mode: any;
|
|
11
13
|
/** set by BCS response */
|
|
12
14
|
entitled: boolean;
|
|
@@ -23,7 +25,10 @@ export class Aggregate extends AggregateBase {
|
|
|
23
25
|
* @returns {void}
|
|
24
26
|
*/
|
|
25
27
|
initializeRecording(errorSample: boolean, fullSample: boolean, ignoreSession: boolean): void;
|
|
26
|
-
|
|
28
|
+
prepUtils(): Promise<void>;
|
|
29
|
+
prepareHarvest({ opts }?: {
|
|
30
|
+
opts: any;
|
|
31
|
+
}): {
|
|
27
32
|
qs: {
|
|
28
33
|
browser_monitoring_key: any;
|
|
29
34
|
type: string;
|
|
@@ -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":"AA8BA;IACE,2BAAiC;IAEjC,8DAsGC;IApGC,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAGnB,UAAuD;IAEvD,0BAA0B;IAC1B,kBAAqB;IAErB,cAA8B;IAkC5B,wCAKQ;IAyBN,sBAAwB;IAqB9B,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CAsDhB;IAED,2BASC;IAED;;;;;;;;;;;oBAiCC;IAED;;;;;;;;;MAmEC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBAUC;IAED,yCAGC;CACF;8BA7V6B,4BAA4B;iCAHzB,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"AAwFA,sDAA4D;AArF5D;IAGE;;;QAGI;IACJ,oCAAkC;IAClC,qBAAmB;IAEnB;;;OAGG;IACH,mBAmBC;IAED;;;OAGG;IACH,oBAMC;;CAsCF"}
|
package/package.json
CHANGED
|
@@ -102,7 +102,7 @@ export class HarvestScheduler extends SharedContext {
|
|
|
102
102
|
if (!submitMethod) return false
|
|
103
103
|
|
|
104
104
|
const retry = !opts?.unload && submitMethod === submitData.xhr
|
|
105
|
-
payload = this.opts.getPayload({ retry })
|
|
105
|
+
payload = this.opts.getPayload({ retry, opts })
|
|
106
106
|
|
|
107
107
|
if (!payload) {
|
|
108
108
|
if (this.started) {
|
|
@@ -102,7 +102,7 @@ export class Aggregate extends AggregateBase {
|
|
|
102
102
|
|
|
103
103
|
// [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
|
|
104
104
|
windowAddEventListener('pageshow', (evt) => {
|
|
105
|
-
if (evt
|
|
105
|
+
if (evt?.persisted) { this.storeSupportabilityMetrics('Generic/BFCache/PageRestored') }
|
|
106
106
|
})
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -28,8 +28,6 @@ import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/sessi
|
|
|
28
28
|
import { stringify } from '../../../common/util/stringify'
|
|
29
29
|
import { stylesheetEvaluator } from '../shared/stylesheet-evaluator'
|
|
30
30
|
|
|
31
|
-
let gzipper, u8
|
|
32
|
-
|
|
33
31
|
export class Aggregate extends AggregateBase {
|
|
34
32
|
static featureName = FEATURE_NAME
|
|
35
33
|
// pass the recorder into the aggregator
|
|
@@ -41,8 +39,10 @@ export class Aggregate extends AggregateBase {
|
|
|
41
39
|
this.initialized = false
|
|
42
40
|
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
43
41
|
this.blocked = false
|
|
44
|
-
/**
|
|
45
|
-
this.
|
|
42
|
+
/** populated with the gzipper lib async */
|
|
43
|
+
this.gzipper = undefined
|
|
44
|
+
/** populated with the u8 string lib async */
|
|
45
|
+
this.u8 = undefined
|
|
46
46
|
/** the mode to start in. Defaults to off */
|
|
47
47
|
const { session } = getRuntime(this.agentIdentifier)
|
|
48
48
|
this.mode = session.state.sessionReplayMode || MODE.OFF
|
|
@@ -91,6 +91,12 @@ export class Aggregate extends AggregateBase {
|
|
|
91
91
|
raw: true
|
|
92
92
|
}, this)
|
|
93
93
|
|
|
94
|
+
if (this.recorder?.getEvents().type === 'preloaded') {
|
|
95
|
+
this.prepUtils().then(() => {
|
|
96
|
+
this.scheduler.runHarvest()
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
registerHandler('recordReplay', () => {
|
|
95
101
|
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
96
102
|
if (this.blocked || !this.entitled) return
|
|
@@ -197,21 +203,25 @@ export class Aggregate extends AggregateBase {
|
|
|
197
203
|
this.scheduler.startTimer(this.harvestTimeSeconds)
|
|
198
204
|
}
|
|
199
205
|
|
|
206
|
+
await this.prepUtils()
|
|
207
|
+
|
|
208
|
+
if (!this.recorder.recording) this.recorder.startRecording()
|
|
209
|
+
|
|
210
|
+
this.syncWithSessionManager({ sessionReplayMode: this.mode })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async prepUtils () {
|
|
200
214
|
try {
|
|
201
215
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
202
216
|
const { gzipSync, strToU8 } = await import(/* webpackChunkName: "compressor" */'fflate')
|
|
203
|
-
gzipper = gzipSync
|
|
204
|
-
u8 = strToU8
|
|
217
|
+
this.gzipper = gzipSync
|
|
218
|
+
this.u8 = strToU8
|
|
205
219
|
} catch (err) {
|
|
206
220
|
// compressor failed to load, but we can still record without compression as a last ditch effort
|
|
207
|
-
this.shouldCompress = false
|
|
208
221
|
}
|
|
209
|
-
if (!this.recorder.recording) this.recorder.startRecording()
|
|
210
|
-
|
|
211
|
-
this.syncWithSessionManager({ sessionReplayMode: this.mode })
|
|
212
222
|
}
|
|
213
223
|
|
|
214
|
-
prepareHarvest () {
|
|
224
|
+
prepareHarvest ({ opts } = {}) {
|
|
215
225
|
if (!this.recorder) return
|
|
216
226
|
const recorderEvents = this.recorder.getEvents()
|
|
217
227
|
// get the event type and use that to trigger another harvest if needed
|
|
@@ -224,8 +234,8 @@ export class Aggregate extends AggregateBase {
|
|
|
224
234
|
}
|
|
225
235
|
|
|
226
236
|
let len = 0
|
|
227
|
-
if (this.
|
|
228
|
-
payload.body = gzipper(u8(`[${payload.body.map(e => e.__serialized).join(',')}]`))
|
|
237
|
+
if (!!this.gzipper && !!this.u8) {
|
|
238
|
+
payload.body = this.gzipper(this.u8(`[${payload.body.map(e => e.__serialized).join(',')}]`))
|
|
229
239
|
len = payload.body.length
|
|
230
240
|
this.scheduler.opts.gzip = true
|
|
231
241
|
} else {
|
|
@@ -242,7 +252,7 @@ export class Aggregate extends AggregateBase {
|
|
|
242
252
|
const { session } = getRuntime(this.agentIdentifier)
|
|
243
253
|
if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
|
|
244
254
|
this.recorder.clearBuffer()
|
|
245
|
-
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest()
|
|
255
|
+
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts)
|
|
246
256
|
return [payload]
|
|
247
257
|
}
|
|
248
258
|
|
|
@@ -276,7 +286,7 @@ export class Aggregate extends AggregateBase {
|
|
|
276
286
|
|
|
277
287
|
const firstEventTimestamp = events[0]?.timestamp // from rrweb node
|
|
278
288
|
const lastEventTimestamp = events[events.length - 1]?.timestamp // from rrweb node
|
|
279
|
-
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp
|
|
289
|
+
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp // from rrweb node || from when the harvest cycle started
|
|
280
290
|
const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow
|
|
281
291
|
|
|
282
292
|
return {
|
|
@@ -288,7 +298,7 @@ export class Aggregate extends AggregateBase {
|
|
|
288
298
|
attributes: encodeObj({
|
|
289
299
|
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
290
300
|
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
291
|
-
...(this.
|
|
301
|
+
...(!!this.gzipper && !!this.u8 && { content_encoding: 'gzip' }),
|
|
292
302
|
'replay.firstTimestamp': firstTimestamp,
|
|
293
303
|
'replay.firstTimestampOffset': firstTimestamp - agentOffset,
|
|
294
304
|
'replay.lastTimestamp': lastTimestamp,
|
|
@@ -184,7 +184,7 @@ export class Recorder {
|
|
|
184
184
|
* https://staging.onenr.io/037jbJWxbjy
|
|
185
185
|
* */
|
|
186
186
|
estimateCompression (data) {
|
|
187
|
-
if (this.
|
|
187
|
+
if (!!this.parent.gzipper && !!this.parent.u8) return data * AVG_COMPRESSION
|
|
188
188
|
return data
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -55,27 +55,32 @@ class StylesheetEvaluator {
|
|
|
55
55
|
* @returns {Promise}
|
|
56
56
|
*/
|
|
57
57
|
async #fetchAndOverride (target, href) {
|
|
58
|
-
const stylesheetContents = await originals.FETCH.bind(window)(href)
|
|
59
|
-
if (!stylesheetContents.ok) {
|
|
60
|
-
this.failedToFix = true
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
const stylesheetText = await stylesheetContents.text()
|
|
64
58
|
try {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
const stylesheetContents = await originals.FETCH.bind(window)(href)
|
|
60
|
+
if (!stylesheetContents.ok) {
|
|
61
|
+
this.failedToFix = true
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
const stylesheetText = await stylesheetContents.text()
|
|
65
|
+
try {
|
|
66
|
+
const cssSheet = new CSSStyleSheet()
|
|
67
|
+
await cssSheet.replace(stylesheetText)
|
|
68
|
+
Object.defineProperty(target, 'cssRules', {
|
|
69
|
+
get () { return cssSheet.cssRules }
|
|
70
|
+
})
|
|
71
|
+
Object.defineProperty(target, 'rules', {
|
|
72
|
+
get () { return cssSheet.rules }
|
|
73
|
+
})
|
|
74
|
+
} catch (err) {
|
|
74
75
|
// cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
|
|
75
76
|
// this is appended in prep of forking rrweb
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
Object.defineProperty(target, 'cssText', {
|
|
78
|
+
get () { return stylesheetText }
|
|
79
|
+
})
|
|
80
|
+
this.failedToFix = true
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
// failed to fetch
|
|
79
84
|
this.failedToFix = true
|
|
80
85
|
}
|
|
81
86
|
}
|