@newrelic/browser-agent 1.287.0 → 1.288.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/session/session-entity.js +10 -3
  5. package/dist/cjs/features/logging/aggregate/index.js +4 -2
  6. package/dist/cjs/features/session_replay/aggregate/index.js +1 -1
  7. package/dist/cjs/features/session_replay/shared/recorder.js +5 -0
  8. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +7 -0
  9. package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -0
  10. package/dist/cjs/features/spa/aggregate/index.js +1 -0
  11. package/dist/esm/common/constants/env.cdn.js +1 -1
  12. package/dist/esm/common/constants/env.npm.js +1 -1
  13. package/dist/esm/common/session/session-entity.js +10 -3
  14. package/dist/esm/features/logging/aggregate/index.js +4 -2
  15. package/dist/esm/features/session_replay/aggregate/index.js +1 -1
  16. package/dist/esm/features/session_replay/shared/recorder.js +5 -0
  17. package/dist/esm/features/session_trace/aggregate/trace/storage.js +7 -0
  18. package/dist/esm/features/soft_navigations/aggregate/index.js +1 -0
  19. package/dist/esm/features/spa/aggregate/index.js +1 -0
  20. package/dist/types/common/session/session-entity.d.ts +3 -1
  21. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  22. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  23. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  24. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  25. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -1
  26. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  27. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +1 -0
  28. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  29. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  30. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/common/session/session-entity.js +10 -3
  33. package/src/features/logging/aggregate/index.js +4 -2
  34. package/src/features/session_replay/aggregate/index.js +1 -1
  35. package/src/features/session_replay/shared/recorder.js +5 -0
  36. package/src/features/session_trace/aggregate/trace/storage.js +8 -0
  37. package/src/features/soft_navigations/aggregate/index.js +1 -0
  38. package/src/features/spa/aggregate/index.js +1 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
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.288.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.287.0...v1.288.0) (2025-04-16)
7
+
8
+
9
+ ### Features
10
+
11
+ * Introduce isFirstOfSession attribute on InitialPageLoad events ([#1432](https://github.com/newrelic/newrelic-browser-agent/issues/1432)) ([8d7bdd6](https://github.com/newrelic/newrelic-browser-agent/commit/8d7bdd63abe0b6d9dfccc46f63845bde0fd099a4))
12
+ * Prevent storing session data past session expiry ([#1426](https://github.com/newrelic/newrelic-browser-agent/issues/1426)) ([5819b64](https://github.com/newrelic/newrelic-browser-agent/commit/5819b649792b6227d49173ecb326f918b1f39bdb))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * Reset `notified` when replay stops recording ([#1435](https://github.com/newrelic/newrelic-browser-agent/issues/1435)) ([7dd8b62](https://github.com/newrelic/newrelic-browser-agent/commit/7dd8b6217763c57c382737b6ff90667066437b75))
18
+ * Solve race condition in logging abort ([#1445](https://github.com/newrelic/newrelic-browser-agent/issues/1445)) ([ab315dc](https://github.com/newrelic/newrelic-browser-agent/commit/ab315dc009a45911911f5d176978fac0cf066be3))
19
+
6
20
  ## [1.287.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.286.0...v1.287.0) (2025-04-10)
7
21
 
8
22
 
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.287.0";
20
+ const VERSION = exports.VERSION = "1.288.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.287.0";
20
+ const VERSION = exports.VERSION = "1.288.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -38,7 +38,8 @@ const model = {
38
38
  loggingMode: _constants3.LOGGING_MODE.OFF,
39
39
  serverTimeDiff: null,
40
40
  // set by TimeKeeper; "undefined" value will not be stringified and stored but "null" will
41
- custom: {}
41
+ custom: {},
42
+ numOfResets: 0
42
43
  };
43
44
  class SessionEntity {
44
45
  /**
@@ -84,7 +85,8 @@ class SessionEntity {
84
85
  setup({
85
86
  value = (0, _uniqueId.generateRandomHexString)(16),
86
87
  expiresMs = _constants.DEFAULT_EXPIRES_MS,
87
- inactiveMs = _constants.DEFAULT_INACTIVE_MS
88
+ inactiveMs = _constants.DEFAULT_INACTIVE_MS,
89
+ numOfResets = 0
88
90
  }) {
89
91
  /** Ensure that certain properties are preserved across a reset if already set */
90
92
  const persistentAttributes = {
@@ -113,6 +115,7 @@ class SessionEntity {
113
115
  // this gets ignored if the value is falsy, allowing for session entities that do not expire
114
116
  if (expiresMs) {
115
117
  this.state.expiresAt = initialRead?.expiresAt || this.getFutureTimestamp(expiresMs);
118
+ this.state.numOfResets = initialRead?.numOfResets || numOfResets;
116
119
  this.expiresTimer = new _timer.Timer({
117
120
  // When the inactive timer ends, collect a SM and reset the session
118
121
  onEnd: () => {
@@ -247,7 +250,8 @@ class SessionEntity {
247
250
  key: this.key,
248
251
  storage: this.storage,
249
252
  expiresMs: this.expiresMs,
250
- inactiveMs: this.inactiveMs
253
+ inactiveMs: this.inactiveMs,
254
+ numOfResets: ++this.state.numOfResets
251
255
  });
252
256
  return this.read();
253
257
  } catch (e) {
@@ -266,6 +270,9 @@ class SessionEntity {
266
270
  inactiveAt: this.getFutureTimestamp(this.inactiveMs)
267
271
  });
268
272
  }
273
+ isAfterSessionExpiry(timestamp) {
274
+ return this.state.numOfResets > 0 || typeof timestamp === 'number' && typeof this.state.expiresAt === 'number' && timestamp >= this.state.expiresAt;
275
+ }
269
276
 
270
277
  /**
271
278
  * @param {number} timestamp
@@ -160,8 +160,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
160
160
  abort(reason = {}) {
161
161
  this.reportSupportabilityMetric("Logging/Abort/".concat(reason.sm));
162
162
  this.blocked = true;
163
- this.events.clear();
164
- this.events.clearSave();
163
+ if (this.events) {
164
+ this.events.clear();
165
+ this.events.clearSave();
166
+ }
165
167
  this.updateLoggingMode(_constants.LOGGING_MODE.OFF);
166
168
  this.deregisterDrain();
167
169
  }
@@ -50,7 +50,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
50
50
  this.recorder = args?.recorder;
51
51
  this.errorNoticed = args?.errorNoticed || false;
52
52
  this.harvestOpts.raw = true;
53
- this.isSessionTrackingEnabled = (0, _featureGates.canEnableSessionTracking)(this.agentIdentifier) && this.agentRef.runtime.session;
53
+ this.isSessionTrackingEnabled = (0, _featureGates.canEnableSessionTracking)(this.agentIdentifier) && !!this.agentRef.runtime.session;
54
54
  this.reportSupportabilityMetric('Config/SessionReplay/Enabled');
55
55
 
56
56
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
@@ -117,6 +117,7 @@ class Recorder {
117
117
  });
118
118
  this.stopRecording = () => {
119
119
  this.recording = false;
120
+ this.notified = false;
120
121
  this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode]);
121
122
  stop?.();
122
123
  };
@@ -164,6 +165,10 @@ class Recorder {
164
165
  /** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
165
166
  store(event, isCheckout) {
166
167
  if (!event) return;
168
+ if (this.parent.agentRef.runtime?.session?.isAfterSessionExpiry(event.timestamp)) {
169
+ this.parent.reportSupportabilityMetric('Session/Expired/SessionReplay/Seen');
170
+ return;
171
+ }
167
172
  if (!(this.parent instanceof _aggregateBase.AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
168
173
  if (this.parent.blocked) return;
169
174
  if (!this.notified) {
@@ -53,6 +53,9 @@ class TraceStorage {
53
53
  constructor(parent) {
54
54
  this.parent = parent;
55
55
  }
56
+ isAfterSessionExpiry(entryTimestamp) {
57
+ return this.parent.agentRef.runtime?.session?.isAfterSessionExpiry((this.parent.timeKeeper?.ready && this.parent.timeKeeper.convertRelativeTimestamp(entryTimestamp)) ?? undefined);
58
+ }
56
59
 
57
60
  /** Central function called by all the other store__ & addToTrace API to append a trace node. */
58
61
  storeSTN(stn) {
@@ -63,6 +66,10 @@ class TraceStorage {
63
66
  const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
64
67
  if (openedSpace === 0) return;
65
68
  }
69
+ if (this.isAfterSessionExpiry(stn.s)) {
70
+ this.parent.reportSupportabilityMetric('Session/Expired/SessionTrace/Seen');
71
+ return;
72
+ }
66
73
  if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
67
74
  if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
68
75
  if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
@@ -30,6 +30,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
30
30
  this.initialPageLoadInteraction = new _initialPageLoadInteraction.InitialPageLoadInteraction(agentRef.agentIdentifier);
31
31
  this.initialPageLoadInteraction.onDone.push(() => {
32
32
  // this ensures the .end() method also works with iPL
33
+ if (agentRef.runtime.session?.isNew) this.initialPageLoadInteraction.customAttributes.isFirstOfSession = true; // mark the hard page load as first of its session
33
34
  this.initialPageLoadInteraction.forceSave = true; // unless forcibly ignored, iPL always finish by default
34
35
  const ixn = this.initialPageLoadInteraction;
35
36
  /** this.events (ixns to harvest) has already been set up, use it immediately */
@@ -127,6 +127,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
127
127
  if (agentRef.init.spa.enabled !== true) return;
128
128
  state.initialPageLoad = new _interaction.Interaction('initialPageLoad', 0, state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef);
129
129
  state.initialPageLoad.save = true;
130
+ if (agentRef.runtime.session?.isNew) state.initialPageLoad.root.attrs.custom.isFirstOfSession = true; // mark the hard page load as first of its session
130
131
  state.prevInteraction = state.initialPageLoad;
131
132
  state.currentNode = state.initialPageLoad.root; // hint
132
133
  // ensure that checkFinish calls are safe during initialPageLoad
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.287.0";
14
+ export const VERSION = "1.288.0";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.287.0";
14
+ export const VERSION = "1.288.0";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -32,7 +32,8 @@ const model = {
32
32
  loggingMode: LOGGING_MODE.OFF,
33
33
  serverTimeDiff: null,
34
34
  // set by TimeKeeper; "undefined" value will not be stringified and stored but "null" will
35
- custom: {}
35
+ custom: {},
36
+ numOfResets: 0
36
37
  };
37
38
  export class SessionEntity {
38
39
  /**
@@ -78,7 +79,8 @@ export class SessionEntity {
78
79
  setup({
79
80
  value = generateRandomHexString(16),
80
81
  expiresMs = DEFAULT_EXPIRES_MS,
81
- inactiveMs = DEFAULT_INACTIVE_MS
82
+ inactiveMs = DEFAULT_INACTIVE_MS,
83
+ numOfResets = 0
82
84
  }) {
83
85
  /** Ensure that certain properties are preserved across a reset if already set */
84
86
  const persistentAttributes = {
@@ -107,6 +109,7 @@ export class SessionEntity {
107
109
  // this gets ignored if the value is falsy, allowing for session entities that do not expire
108
110
  if (expiresMs) {
109
111
  this.state.expiresAt = initialRead?.expiresAt || this.getFutureTimestamp(expiresMs);
112
+ this.state.numOfResets = initialRead?.numOfResets || numOfResets;
110
113
  this.expiresTimer = new Timer({
111
114
  // When the inactive timer ends, collect a SM and reset the session
112
115
  onEnd: () => {
@@ -241,7 +244,8 @@ export class SessionEntity {
241
244
  key: this.key,
242
245
  storage: this.storage,
243
246
  expiresMs: this.expiresMs,
244
- inactiveMs: this.inactiveMs
247
+ inactiveMs: this.inactiveMs,
248
+ numOfResets: ++this.state.numOfResets
245
249
  });
246
250
  return this.read();
247
251
  } catch (e) {
@@ -260,6 +264,9 @@ export class SessionEntity {
260
264
  inactiveAt: this.getFutureTimestamp(this.inactiveMs)
261
265
  });
262
266
  }
267
+ isAfterSessionExpiry(timestamp) {
268
+ return this.state.numOfResets > 0 || typeof timestamp === 'number' && typeof this.state.expiresAt === 'number' && timestamp >= this.state.expiresAt;
269
+ }
263
270
 
264
271
  /**
265
272
  * @param {number} timestamp
@@ -153,8 +153,10 @@ export class Aggregate extends AggregateBase {
153
153
  abort(reason = {}) {
154
154
  this.reportSupportabilityMetric("Logging/Abort/".concat(reason.sm));
155
155
  this.blocked = true;
156
- this.events.clear();
157
- this.events.clearSave();
156
+ if (this.events) {
157
+ this.events.clear();
158
+ this.events.clearSave();
159
+ }
158
160
  this.updateLoggingMode(LOGGING_MODE.OFF);
159
161
  this.deregisterDrain();
160
162
  }
@@ -45,7 +45,7 @@ export class Aggregate extends AggregateBase {
45
45
  this.recorder = args?.recorder;
46
46
  this.errorNoticed = args?.errorNoticed || false;
47
47
  this.harvestOpts.raw = true;
48
- this.isSessionTrackingEnabled = canEnableSessionTracking(this.agentIdentifier) && this.agentRef.runtime.session;
48
+ this.isSessionTrackingEnabled = canEnableSessionTracking(this.agentIdentifier) && !!this.agentRef.runtime.session;
49
49
  this.reportSupportabilityMetric('Config/SessionReplay/Enabled');
50
50
 
51
51
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
@@ -110,6 +110,7 @@ export class Recorder {
110
110
  });
111
111
  this.stopRecording = () => {
112
112
  this.recording = false;
113
+ this.notified = false;
113
114
  this.parent.ee.emit(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode]);
114
115
  stop?.();
115
116
  };
@@ -157,6 +158,10 @@ export class Recorder {
157
158
  /** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
158
159
  store(event, isCheckout) {
159
160
  if (!event) return;
161
+ if (this.parent.agentRef.runtime?.session?.isAfterSessionExpiry(event.timestamp)) {
162
+ this.parent.reportSupportabilityMetric('Session/Expired/SessionReplay/Seen');
163
+ return;
164
+ }
160
165
  if (!(this.parent instanceof AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
161
166
  if (this.parent.blocked) return;
162
167
  if (!this.notified) {
@@ -46,6 +46,9 @@ export class TraceStorage {
46
46
  constructor(parent) {
47
47
  this.parent = parent;
48
48
  }
49
+ isAfterSessionExpiry(entryTimestamp) {
50
+ return this.parent.agentRef.runtime?.session?.isAfterSessionExpiry((this.parent.timeKeeper?.ready && this.parent.timeKeeper.convertRelativeTimestamp(entryTimestamp)) ?? undefined);
51
+ }
49
52
 
50
53
  /** Central function called by all the other store__ & addToTrace API to append a trace node. */
51
54
  storeSTN(stn) {
@@ -56,6 +59,10 @@ export class TraceStorage {
56
59
  const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
57
60
  if (openedSpace === 0) return;
58
61
  }
62
+ if (this.isAfterSessionExpiry(stn.s)) {
63
+ this.parent.reportSupportabilityMetric('Session/Expired/SessionTrace/Seen');
64
+ return;
65
+ }
59
66
  if (this.trace[stn.n]) this.trace[stn.n].push(stn);else this.trace[stn.n] = [stn];
60
67
  if (stn.s < this.earliestTimeStamp) this.earliestTimeStamp = stn.s;
61
68
  if (stn.s > this.latestTimeStamp) this.latestTimeStamp = stn.s;
@@ -23,6 +23,7 @@ export class Aggregate extends AggregateBase {
23
23
  this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef.agentIdentifier);
24
24
  this.initialPageLoadInteraction.onDone.push(() => {
25
25
  // this ensures the .end() method also works with iPL
26
+ if (agentRef.runtime.session?.isNew) this.initialPageLoadInteraction.customAttributes.isFirstOfSession = true; // mark the hard page load as first of its session
26
27
  this.initialPageLoadInteraction.forceSave = true; // unless forcibly ignored, iPL always finish by default
27
28
  const ixn = this.initialPageLoadInteraction;
28
29
  /** this.events (ixns to harvest) has already been set up, use it immediately */
@@ -118,6 +118,7 @@ export class Aggregate extends AggregateBase {
118
118
  if (agentRef.init.spa.enabled !== true) return;
119
119
  state.initialPageLoad = new Interaction('initialPageLoad', 0, state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef);
120
120
  state.initialPageLoad.save = true;
121
+ if (agentRef.runtime.session?.isNew) state.initialPageLoad.root.attrs.custom.isFirstOfSession = true; // mark the hard page load as first of its session
121
122
  state.prevInteraction = state.initialPageLoad;
122
123
  state.currentNode = state.initialPageLoad.root; // hint
123
124
  // ensure that checkFinish calls are safe during initialPageLoad
@@ -11,10 +11,11 @@ export class SessionEntity {
11
11
  state: {};
12
12
  key: any;
13
13
  ee: any;
14
- setup({ value, expiresMs, inactiveMs }: {
14
+ setup({ value, expiresMs, inactiveMs, numOfResets }: {
15
15
  value?: string | undefined;
16
16
  expiresMs?: number | undefined;
17
17
  inactiveMs?: number | undefined;
18
+ numOfResets?: number | undefined;
18
19
  }): void;
19
20
  expiresMs: number | undefined;
20
21
  inactiveMs: number | undefined;
@@ -41,6 +42,7 @@ export class SessionEntity {
41
42
  * Refresh the inactivity timer data
42
43
  */
43
44
  refresh(): void;
45
+ isAfterSessionExpiry(timestamp: any): boolean;
44
46
  /**
45
47
  * @param {number} timestamp
46
48
  * @returns {boolean}
@@ -1 +1 @@
1
- {"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":"AAoCA;IACE;;;;;OAKG;IACH,uBA+BC;IAzBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAoBnC;;;;aA4EC;IAnEC,8BAA0B;IAC1B,+BAA4B;IAc1B,gCAOqC;IAUrC,4CAkBsC;IAexC,iCAAuB;IAKzB,wBAEC;IAED,sBAEC;IAED;;;OAGG;IACH,QAFa,MAAM,CA6BlB;IAED;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,MAAM,CAkBlB;IAED,gBAuBC;IAED;;OAEG;IACH,gBAIC;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAED,yDAUC;IAED,6DAIC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBAhTqB,gBAAgB;iCAGL,4BAA4B"}
1
+ {"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":"AAqCA;IACE;;;;;OAKG;IACH,uBA+BC;IAzBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAoBnC;;;;;aA6EC;IApEC,8BAA0B;IAC1B,+BAA4B;IAe1B,gCAOqC;IAUrC,4CAkBsC;IAexC,iCAAuB;IAKzB,wBAEC;IAED,sBAEC;IAED;;;OAGG;IACH,QAFa,MAAM,CA6BlB;IAED;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,MAAM,CAkBlB;IAED,gBAwBC;IAED;;OAEG;IACH,gBAIC;IAED,8CAEC;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAED,yDAUC;IAED,6DAIC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBAvTqB,gBAAgB;iCAGL,4BAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/logging/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IACjC,2BAmCC;IAjCC,8BAA+G;IAUxG,iBAAmC;IAyB5C,0CAKC;IAED,4HAwDC;IAED;;YAKM,0FAA0F;;;QAoB5F,0DAA0D;;QAM7D;IAED;;MAGC;IAED,yDAAyD;IACzD,yBAOC;IAED,yCAIC;CACF;8BAxK6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/logging/aggregate/index.js"],"names":[],"mappings":"AAkBA;IACE,2BAAiC;IACjC,2BAmCC;IAjCC,8BAA+G;IAUxG,iBAAmC;IAyB5C,0CAKC;IAED,4HAwDC;IAED;;YAKM,0FAA0F;;;QAoB5F,0DAA0D;;QAM7D;IAED;;MAGC;IAED,yDAAyD;IACzD,yBASC;IAED,yCAIC;CACF;8BA1K6B,4BAA4B"}
@@ -14,7 +14,7 @@ export class Aggregate extends AggregateBase {
14
14
  timeKeeper: any;
15
15
  recorder: any;
16
16
  errorNoticed: any;
17
- isSessionTrackingEnabled: any;
17
+ isSessionTrackingEnabled: boolean;
18
18
  replayIsActive(): boolean;
19
19
  handleError(e: any): void;
20
20
  switchToFull(): void;
@@ -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,8BAA+G;IAoEjH,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;OAKG;IACH,4BAJW,OAAO,iBACP,OAAO,GACL,IAAI,CAuDhB;IAED,2BASC;IAED;;;oBAmDC;IAED,sCAIC;IAED;;;;;;;;;;MAuEC;IAED,sCAKC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;IAED,yCAIC;CACF;8BApX6B,4BAA4B"}
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,kCAAiH;IAoEnH,0BAEC;IAED,0BAMC;IAED,qBAUC;IAED;;;;;OAKG;IACH,4BAJW,OAAO,iBACP,OAAO,GACL,IAAI,CAuDhB;IAED,2BASC;IAED;;;oBAmDC;IAED,sCAIC;IAED;;;;;;;;;;MAuEC;IAED,sCAKC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,+CASC;IAED,yCAIC;CACF;8BApX6B,4BAA4B"}
@@ -28,6 +28,7 @@ export class Recorder {
28
28
  clearBuffer(): void;
29
29
  /** Begin recording using configured recording lib */
30
30
  startRecording(): void;
31
+ notified: boolean | undefined;
31
32
  /**
32
33
  * audit - Checks if the event node payload is missing certain attributes
33
34
  * will forward on to the "store" method if nothing needs async fixing
@@ -37,7 +38,6 @@ export class Recorder {
37
38
  audit(event: any, isCheckout: any): void;
38
39
  /** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
39
40
  store(event: any, isCheckout: any): void;
40
- notified: boolean | undefined;
41
41
  /** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
42
42
  takeFullSnapshot(): void;
43
43
  clearTimestamps(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAmBA;IAYE,yBAmBC;IAlBC,sDAAsD;IACtD,YAAoB;IACpB,0FAA0F;IAC1F,eAAyE;IAKzE,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,+IAA+I;IAC/I,yBAA4B;IAC5B,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAmBC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBA6BC;IAED;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,0HAA0H;IAC1H,yCAoDC;IA1CG,8BAAoB;IA4CxB,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BApO8B,mBAAmB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAmBA;IAYE,yBAmBC;IAlBC,sDAAsD;IACtD,YAAoB;IACpB,0FAA0F;IAC1F,eAAyE;IAKzE,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,+IAA+I;IAC/I,yBAA4B;IAC5B,kIAAkI;IAClI,kBAAqB;IACrB,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAmBC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBA8BC;IAJG,8BAAqB;IAMzB;;;;;OAKG;IACH,aAHW,GAAC,cACD,GAAC,QAiCX;IAED,0HAA0H;IAC1H,yCAwDC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BAzO8B,mBAAmB"}
@@ -7,6 +7,7 @@ export class TraceStorage {
7
7
  latestTimeStamp: number;
8
8
  prevStoredEvents: Set<any>;
9
9
  parent: any;
10
+ isAfterSessionExpiry(entryTimestamp: any): any;
10
11
  /** Central function called by all the other store__ & addToTrace API to append a trace node. */
11
12
  storeSTN(stn: any): void;
12
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AA8BA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,2BAA4B;IAI1B,YAAoB;IAGtB,gGAAgG;IAChG,yBAcC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;MAgBC;IAED,mEA6BC;IAED,oDAEC;IAED,mEAuBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAGC;IAID,mBAEC;IAED,aAEC;IAED;;;;;;;QAEC;IAED,cAMC;IAED,mBAEC;IAED,kBAEC;;CACF"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../../../src/features/session_trace/aggregate/trace/storage.js"],"names":[],"mappings":"AA8BA,+HAA+H;AAC/H;IAQE,yBAEC;IATD,kBAAa;IACb,UAAU;IACV,0BAA4B;IAC5B,wBAAmB;IACnB,2BAA4B;IAI1B,YAAoB;IAGtB,+CAEC;IAED,gGAAgG;IAChG,yBAkBC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAED,oEAAoE;IACpE;;;;MAgBC;IAED,mEA6BC;IAED,oDAEC;IAED,mEAuBC;IAGD,uEAcC;IAED,oDAKC;IAED,wBAwBC;IAGD,gDAEC;IAID,qCAaC;IAGD,qEAGC;IAGD,mEAGC;IAID,mBAEC;IAED,aAEC;IAED;;;;;;;QAEC;IAED,cAMC;IAED,mBAEC;IAED,kBAEC;;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IACjC;;OA2DC;IAxDC,2BAAwC;IACxC,iBAA8B;IAE9B,uDAA0F;IAsB1F,yBAA+B;IAC/B,0CAAiC;IACjC,sBAA4B;IA+B9B,qCAUC;IAED,0EAiBC;IAED,2BAiBC;IAED;;;;;;;OAOG;IACH,6BAHW,mBAAmB,OAqB7B;;CA6FF;8BA9O6B,4BAA4B;2CAGf,iCAAiC;4BAChD,eAAe"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IACjC;;OA4DC;IAzDC,2BAAwC;IACxC,iBAA8B;IAE9B,uDAA0F;IAuB1F,yBAA+B;IAC/B,0CAAiC;IACjC,sBAA4B;IA+B9B,qCAUC;IAED,0EAiBC;IAED,2BAiBC;IAED;;;;;;;OAOG;IACH,6BAHW,mBAAmB,OAqB7B;;CA6FF;8BA/O6B,4BAA4B;2CAGf,iCAAiC;4BAChD,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA6BA;IACE,2BAAiC;IACjC,2BAwrBC;IArrBe;;;;;;;;;;;;;;MAeb;IACD,+BAA8C;IAuqBhD,qCAEC;CACF;8BA5sB6B,4BAA4B;2BAJ/B,cAAc"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA6BA;IACE,2BAAiC;IACjC,2BAyrBC;IAtrBe;;;;;;;;;;;;;;MAeb;IACD,+BAA8C;IAwqBhD,qCAEC;CACF;8BA7sB6B,4BAA4B;2BAJ/B,cAAc"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.287.0",
3
+ "version": "1.288.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -31,7 +31,8 @@ const model = {
31
31
  traceHarvestStarted: false,
32
32
  loggingMode: LOGGING_MODE.OFF,
33
33
  serverTimeDiff: null, // set by TimeKeeper; "undefined" value will not be stringified and stored but "null" will
34
- custom: {}
34
+ custom: {},
35
+ numOfResets: 0
35
36
  }
36
37
 
37
38
  export class SessionEntity {
@@ -74,7 +75,7 @@ export class SessionEntity {
74
75
  }
75
76
  }
76
77
 
77
- setup ({ value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS }) {
78
+ setup ({ value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS, numOfResets = 0 }) {
78
79
  /** Ensure that certain properties are preserved across a reset if already set */
79
80
  const persistentAttributes = { serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff }
80
81
  this.state = {}
@@ -98,6 +99,7 @@ export class SessionEntity {
98
99
  // this gets ignored if the value is falsy, allowing for session entities that do not expire
99
100
  if (expiresMs) {
100
101
  this.state.expiresAt = initialRead?.expiresAt || this.getFutureTimestamp(expiresMs)
102
+ this.state.numOfResets = initialRead?.numOfResets || numOfResets
101
103
  this.expiresTimer = new Timer({
102
104
  // When the inactive timer ends, collect a SM and reset the session
103
105
  onEnd: () => {
@@ -236,7 +238,8 @@ export class SessionEntity {
236
238
  key: this.key,
237
239
  storage: this.storage,
238
240
  expiresMs: this.expiresMs,
239
- inactiveMs: this.inactiveMs
241
+ inactiveMs: this.inactiveMs,
242
+ numOfResets: ++this.state.numOfResets
240
243
  })
241
244
  return this.read()
242
245
  } catch (e) {
@@ -253,6 +256,10 @@ export class SessionEntity {
253
256
  this.write({ ...existingData, inactiveAt: this.getFutureTimestamp(this.inactiveMs) })
254
257
  }
255
258
 
259
+ isAfterSessionExpiry (timestamp) {
260
+ return this.state.numOfResets > 0 || (typeof timestamp === 'number' && typeof this.state.expiresAt === 'number' && timestamp >= this.state.expiresAt)
261
+ }
262
+
256
263
  /**
257
264
  * @param {number} timestamp
258
265
  * @returns {boolean}
@@ -162,8 +162,10 @@ export class Aggregate extends AggregateBase {
162
162
  abort (reason = {}) {
163
163
  this.reportSupportabilityMetric(`Logging/Abort/${reason.sm}`)
164
164
  this.blocked = true
165
- this.events.clear()
166
- this.events.clearSave()
165
+ if (this.events) {
166
+ this.events.clear()
167
+ this.events.clearSave()
168
+ }
167
169
  this.updateLoggingMode(LOGGING_MODE.OFF)
168
170
  this.deregisterDrain()
169
171
  }
@@ -48,7 +48,7 @@ export class Aggregate extends AggregateBase {
48
48
  this.errorNoticed = args?.errorNoticed || false
49
49
  this.harvestOpts.raw = true
50
50
 
51
- this.isSessionTrackingEnabled = canEnableSessionTracking(this.agentIdentifier) && this.agentRef.runtime.session
51
+ this.isSessionTrackingEnabled = canEnableSessionTracking(this.agentIdentifier) && !!this.agentRef.runtime.session
52
52
 
53
53
  this.reportSupportabilityMetric('Config/SessionReplay/Enabled')
54
54
 
@@ -106,6 +106,7 @@ export class Recorder {
106
106
 
107
107
  this.stopRecording = () => {
108
108
  this.recording = false
109
+ this.notified = false
109
110
  this.parent.ee.emit(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode])
110
111
  stop?.()
111
112
  }
@@ -153,6 +154,10 @@ export class Recorder {
153
154
  /** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
154
155
  store (event, isCheckout) {
155
156
  if (!event) return
157
+ if (this.parent.agentRef.runtime?.session?.isAfterSessionExpiry(event.timestamp)) {
158
+ this.parent.reportSupportabilityMetric('Session/Expired/SessionReplay/Seen')
159
+ return
160
+ }
156
161
 
157
162
  if (!(this.parent instanceof AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1]
158
163
  else this.currentBufferTarget = this.#events
@@ -41,6 +41,10 @@ export class TraceStorage {
41
41
  this.parent = parent
42
42
  }
43
43
 
44
+ isAfterSessionExpiry (entryTimestamp) {
45
+ return this.parent.agentRef.runtime?.session?.isAfterSessionExpiry((this.parent.timeKeeper?.ready && this.parent.timeKeeper.convertRelativeTimestamp(entryTimestamp)) ?? undefined)
46
+ }
47
+
44
48
  /** Central function called by all the other store__ & addToTrace API to append a trace node. */
45
49
  storeSTN (stn) {
46
50
  if (this.parent.blocked) return
@@ -49,6 +53,10 @@ export class TraceStorage {
49
53
  const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW) // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
50
54
  if (openedSpace === 0) return
51
55
  }
56
+ if (this.isAfterSessionExpiry(stn.s)) {
57
+ this.parent.reportSupportabilityMetric('Session/Expired/SessionTrace/Seen')
58
+ return
59
+ }
52
60
 
53
61
  if (this.trace[stn.n]) this.trace[stn.n].push(stn)
54
62
  else this.trace[stn.n] = [stn]
@@ -23,6 +23,7 @@ export class Aggregate extends AggregateBase {
23
23
 
24
24
  this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef.agentIdentifier)
25
25
  this.initialPageLoadInteraction.onDone.push(() => { // this ensures the .end() method also works with iPL
26
+ if (agentRef.runtime.session?.isNew) this.initialPageLoadInteraction.customAttributes.isFirstOfSession = true // mark the hard page load as first of its session
26
27
  this.initialPageLoadInteraction.forceSave = true // unless forcibly ignored, iPL always finish by default
27
28
  const ixn = this.initialPageLoadInteraction
28
29
  /** this.events (ixns to harvest) has already been set up, use it immediately */
@@ -110,6 +110,7 @@ export class Aggregate extends AggregateBase {
110
110
 
111
111
  state.initialPageLoad = new Interaction('initialPageLoad', 0, state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef)
112
112
  state.initialPageLoad.save = true
113
+ if (agentRef.runtime.session?.isNew) state.initialPageLoad.root.attrs.custom.isFirstOfSession = true // mark the hard page load as first of its session
113
114
  state.prevInteraction = state.initialPageLoad
114
115
  state.currentNode = state.initialPageLoad.root // hint
115
116
  // ensure that checkFinish calls are safe during initialPageLoad