@newrelic/browser-agent 1.296.0-rc.3 → 1.296.0-rc.5

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.
Files changed (33) hide show
  1. package/dist/cjs/common/constants/env.cdn.js +1 -1
  2. package/dist/cjs/common/constants/env.npm.js +1 -1
  3. package/dist/cjs/features/session_replay/aggregate/index.js +10 -41
  4. package/dist/cjs/features/session_replay/instrument/index.js +6 -7
  5. package/dist/cjs/features/session_replay/shared/recorder-events.js +2 -2
  6. package/dist/cjs/features/session_replay/shared/recorder.js +41 -61
  7. package/dist/cjs/features/session_replay/shared/utils.js +0 -13
  8. package/dist/cjs/features/utils/event-buffer.js +3 -2
  9. package/dist/esm/common/constants/env.cdn.js +1 -1
  10. package/dist/esm/common/constants/env.npm.js +1 -1
  11. package/dist/esm/features/session_replay/aggregate/index.js +10 -41
  12. package/dist/esm/features/session_replay/instrument/index.js +7 -8
  13. package/dist/esm/features/session_replay/shared/recorder-events.js +2 -2
  14. package/dist/esm/features/session_replay/shared/recorder.js +42 -62
  15. package/dist/esm/features/session_replay/shared/utils.js +0 -12
  16. package/dist/esm/features/utils/event-buffer.js +3 -2
  17. package/dist/types/features/session_replay/aggregate/index.d.ts +0 -1
  18. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  19. package/dist/types/features/session_replay/shared/recorder-events.d.ts +1 -1
  20. package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
  21. package/dist/types/features/session_replay/shared/recorder.d.ts +10 -8
  22. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  23. package/dist/types/features/session_replay/shared/utils.d.ts +0 -8
  24. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
  25. package/dist/types/features/utils/event-buffer.d.ts +2 -1
  26. package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/features/session_replay/aggregate/index.js +8 -35
  29. package/src/features/session_replay/instrument/index.js +4 -4
  30. package/src/features/session_replay/shared/recorder-events.js +2 -2
  31. package/src/features/session_replay/shared/recorder.js +39 -67
  32. package/src/features/session_replay/shared/utils.js +0 -13
  33. 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 { buildNRMetaNode, customMasker } from './utils';
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
- /** Event Buffers */
36
- this.#events = new RecorderEvents(this.shouldFix);
37
- this.#backloggedEvents = new RecorderEvents(this.shouldFix);
38
- this.#preloaded = [new RecorderEvents(this.shouldFix)];
39
- /** The pointer to the current bucket holding rrweb events */
40
- this.currentBufferTarget = this.#events;
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.#backloggedEvents.events, ...this.#events.events].filter(x => x),
49
+ events: [...this.backloggedEvents.events, ...this.events.events].filter(x => x),
61
50
  type: 'standard',
62
- cycleTimestamp: Math.min(this.#backloggedEvents.cycleTimestamp, this.#events.cycleTimestamp),
63
- payloadBytesEstimation: this.#backloggedEvents.payloadBytesEstimation + this.#events.payloadBytesEstimation,
64
- hasError: this.#backloggedEvents.hasError || this.#events.hasError,
65
- hasMeta: this.#backloggedEvents.hasMeta || this.#events.hasMeta,
66
- hasSnapshot: this.#backloggedEvents.hasSnapshot || this.#events.hasSnapshot,
67
- inlinedAllStylesheets: !!this.#backloggedEvents.events.length && this.#backloggedEvents.inlinedAllStylesheets || this.#events.inlinedAllStylesheets
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.#events), and resets all payload metadata properties */
60
+ /** Clears the buffer (this.events), and resets all payload metadata properties */
72
61
  clearBuffer() {
73
- if (this.#preloaded[0]?.events.length) this.#preloaded.shift();else if (this.parent.mode === MODE.ERROR) this.#backloggedEvents = this.#events;else this.#backloggedEvents = new RecorderEvents(this.shouldFix);
74
- this.#events = new RecorderEvents(this.shouldFix);
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: this.audit.bind(this),
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.currentBufferTarget.inlinedAllStylesheets = false;
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.currentBufferTarget.inlinedAllStylesheets = false;
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.#events). This should be the callback to the recording lib noticing a mutation */
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
- if (!(this.parent instanceof AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
165
- if (this.parent.blocked) return;
166
- if (this.parent.timeKeeper?.ready && !event.__newrelic) {
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
- if (event.type === RRWEB_EVENT_TYPES.Meta) {
185
- this.currentBufferTarget.hasMeta = true;
186
- }
172
+ this.events.hasMeta ||= event.type === RRWEB_EVENT_TYPES.Meta;
187
173
  // snapshot event
188
- if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
189
- this.currentBufferTarget.hasSnapshot = true;
190
- this.hasSeenSnapshot = true;
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 ((event.type === RRWEB_EVENT_TYPES.FullSnapshot && this.currentBufferTarget.hasMeta || payloadSize > IDEAL_PAYLOAD_SIZE) && this.parent.mode === MODE.FULL) {
197
- // if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
198
- if (this.parent instanceof AggregateBase) {
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.currentBufferTarget.cycleTimestamp = undefined;
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.currentBufferTarget.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING;
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') {
@@ -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
@@ -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":"AAyBA;IACE,2BAAiC;IAIjC,sCAsFC;IAzFD,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;IAoEvG,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;OAKG;IACH,4BAJW,OAAO,iBACP,OAAO,GACL,IAAI,CAuDhB;IAED,2BASC;IAED;;;oBAuDC;IAED,sCAIC;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;8BAtY6B,4BAA4B"}
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,sBAEC;IAED,oBAEC;IAED,qGAAqG;IACrG,qCAEC;;CACF"}
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
- /** The pointer to the current bucket holding rrweb events */
8
- currentBufferTarget: RecorderEvents;
9
- /** 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 */
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
- hasSnapshot: boolean;
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.#events), and resets all payload metadata properties */
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.#events). This should be the callback to the recording lib noticing a mutation */
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":"AAmBA;IAYE,yBAiBC;IAhBC,sDAAsD;IACtD,YAAoB;IACpB,0FAA0F;IAC1F,eAAyE;IAKzE,6DAA6D;IAC7D,oCAAuC;IACvC,+IAA+I;IAC/I,yBAA4B;IAC5B,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAA+E;IAGjF;;;;;;;;;MAmBC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAiCC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,0HAA0H;IAC1H,yCA+CC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BAjO8B,mBAAmB"}
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":"AAQA,+DAIC;AAED,0DAEC;AAED;;;;;;;EAUC;AAED,2DAUC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/utils.js"],"names":[],"mappings":"AAOA,+DAIC;AAED,0DAEC;AAED,2DAUC"}
@@ -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;;;;OAIG;IACH,WAHW,GAAG,WAeb;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"}
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.296.0-rc.3",
3
+ "version": "1.296.0-rc.5",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -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.currentBufferTarget.hasError = true
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
- // Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
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.currentBufferTarget.hasError = this.errorNoticed
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, ...e }) => {
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 = payload.body.map(({ __serialized, ...node }) => {
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 = this.getCorrectedTimestamp(firstEvent) || Math.floor(this.timeKeeper.correctAbsoluteTimestamp(recorderEvents.cycleTimestamp))
322
- const lastTimestamp = this.getCorrectedTimestamp(lastEvent) || Math.floor(this.timeKeeper.correctRelativeTimestamp(relativeNow))
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
 
@@ -10,7 +10,7 @@ import { handle } from '../../../common/event-emitter/handle'
10
10
  import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants'
11
11
  import { InstrumentBase } from '../../utils/instrument-base'
12
12
  import { hasReplayPrerequisite, isPreloadAllowed } from '../shared/utils'
13
- import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants'
13
+ import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES } from '../constants'
14
14
  import { setupRecordReplayAPI } from '../../../loaders/api/recordReplay'
15
15
  import { setupPauseReplayAPI } from '../../../loaders/api/pauseReplay'
16
16
 
@@ -69,7 +69,7 @@ export class Instrument extends InstrumentBase {
69
69
  /**
70
70
  * This func is use for early pre-load recording prior to replay feature (agg) being loaded onto the page. It should only setup once, including if already called and in-progress.
71
71
  */
72
- async #preloadStartRecording (trigger) {
72
+ async #preloadStartRecording () {
73
73
  if (this.#alreadyStarted) return
74
74
  this.#alreadyStarted = true
75
75
 
@@ -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, agentIdentifier: this.agentIdentifier, trigger, ee: this.ee, agentRef: this.#agentRef })
81
+ this.recorder ??= new Recorder({ ...this, mode: this.#mode, agentRef: this.#agentRef, 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) {
@@ -95,7 +95,7 @@ export class Instrument extends InstrumentBase {
95
95
  if (this.featAggregate.mode !== MODE.FULL) this.featAggregate.initializeRecording(MODE.FULL, true)
96
96
  } else { // pre-load
97
97
  this.#mode = MODE.FULL
98
- this.#preloadStartRecording(TRIGGERS.API)
98
+ this.#preloadStartRecording()
99
99
  // There's a race here wherein either:
100
100
  // a. Recorder has not been initialized, and we've set the enforced mode, so we're good, or;
101
101
  // b. Record has been initialized, possibly with the "wrong" mode, so we have to correct that + restart.
@@ -26,8 +26,8 @@ export class RecorderEvents {
26
26
  this.inlinedAllStylesheets = shouldInlineStylesheets
27
27
  }
28
28
 
29
- add (event) {
30
- this.#events.add(event)
29
+ add (event, evaluatedSize) {
30
+ this.#events.add(event, evaluatedSize)
31
31
  }
32
32
 
33
33
  get events () {