@newrelic/browser-agent 1.287.0 → 1.288.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +22 -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 +2 -8
  10. package/dist/cjs/features/spa/aggregate/index.js +1 -0
  11. package/dist/cjs/features/utils/aggregate-base.js +7 -5
  12. package/dist/cjs/features/utils/event-store-manager.js +15 -5
  13. package/dist/esm/common/constants/env.cdn.js +1 -1
  14. package/dist/esm/common/constants/env.npm.js +1 -1
  15. package/dist/esm/common/session/session-entity.js +10 -3
  16. package/dist/esm/features/logging/aggregate/index.js +4 -2
  17. package/dist/esm/features/session_replay/aggregate/index.js +1 -1
  18. package/dist/esm/features/session_replay/shared/recorder.js +5 -0
  19. package/dist/esm/features/session_trace/aggregate/trace/storage.js +7 -0
  20. package/dist/esm/features/soft_navigations/aggregate/index.js +2 -8
  21. package/dist/esm/features/spa/aggregate/index.js +1 -0
  22. package/dist/esm/features/utils/aggregate-base.js +7 -5
  23. package/dist/esm/features/utils/event-store-manager.js +15 -6
  24. package/dist/types/common/session/session-entity.d.ts +3 -1
  25. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  26. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  27. package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
  28. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  29. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -1
  30. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  31. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +1 -0
  32. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  33. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  34. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  35. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  36. package/dist/types/features/utils/event-store-manager.d.ts +6 -4
  37. package/dist/types/features/utils/event-store-manager.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/common/session/session-entity.js +10 -3
  40. package/src/features/logging/aggregate/index.js +4 -2
  41. package/src/features/session_replay/aggregate/index.js +1 -1
  42. package/src/features/session_replay/shared/recorder.js +5 -0
  43. package/src/features/session_trace/aggregate/trace/storage.js +8 -0
  44. package/src/features/soft_navigations/aggregate/index.js +2 -9
  45. package/src/features/spa/aggregate/index.js +1 -0
  46. package/src/features/utils/aggregate-base.js +7 -5
  47. package/src/features/utils/event-store-manager.js +18 -5
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
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.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.288.0...v1.288.1) (2025-04-18)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Call handle directly for submitting SM from SR recorder ([#1453](https://github.com/newrelic/newrelic-browser-agent/issues/1453)) ([4920580](https://github.com/newrelic/newrelic-browser-agent/commit/49205807b6038afb32e3f04ebabedfe5fc11456b))
12
+ * Ensure event buffer always exists ([#1452](https://github.com/newrelic/newrelic-browser-agent/issues/1452)) ([56e75b2](https://github.com/newrelic/newrelic-browser-agent/commit/56e75b243b87d1d44e8014ac0ad5f408f0d145c5))
13
+
14
+ ## [1.288.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.287.0...v1.288.0) (2025-04-16)
15
+
16
+
17
+ ### Features
18
+
19
+ * 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))
20
+ * 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))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * 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))
26
+ * 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))
27
+
6
28
  ## [1.287.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.286.0...v1.287.0) (2025-04-10)
7
29
 
8
30
 
@@ -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.1";
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.1";
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
+ (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['Session/Expired/SessionReplay/Seen'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
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,16 +30,10 @@ 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
- /** this.events (ixns to harvest) has already been set up, use it immediately */
36
- if (this.interactionsToHarvest) this.interactionsToHarvest.add(ixn);else {
37
- /** this.events (ixns to harvest) hasnt been initialized yet... wait for it */
38
- this.ee.on('entity-added', () => {
39
- this.interactionsToHarvest = this.events;
40
- this.interactionsToHarvest.add(ixn);
41
- });
42
- }
36
+ this.interactionsToHarvest.add(ixn);
43
37
  this.initialPageLoadInteraction = null;
44
38
  });
45
39
  _timeToFirstByte.timeToFirstByte.subscribe(({
@@ -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
@@ -39,11 +39,13 @@ class AggregateBase extends _featureBase.FeatureBase {
39
39
  this.harvestOpts = {}; // features aggregate classes can define custom opts for when their harvest is called
40
40
 
41
41
  const agentEntityGuid = this.agentRef?.runtime?.appMetadata?.agents?.[0]?.entityGuid;
42
- if (agentEntityGuid) {
43
- this.#setupEventStore(agentEntityGuid); // if there's no entity guid, wont dont anything, and will wait for rum flags
44
- } else {
42
+ this.#setupEventStore(agentEntityGuid);
43
+ if (!agentEntityGuid) {
44
+ /** wait for the entity guid from the rum response and use to it to further configure things to set the default entity to share an indexed entity with entityGuid */
45
45
  this.ee.on('entity-added', entity => {
46
- this.#setupEventStore(entity.entityGuid);
46
+ // not all event managers have this fn, like ST and SR
47
+ // this allows the lookup to work for the default and an entityGuid without creating two separate buffers
48
+ this.events?.setEventStore?.(entity.entityGuid);
47
49
  });
48
50
  }
49
51
  }
@@ -54,7 +56,7 @@ class AggregateBase extends _featureBase.FeatureBase {
54
56
  * @returns {void}
55
57
  */
56
58
  #setupEventStore(entityGuid) {
57
- if (this.events || !entityGuid) return;
59
+ if (this.events) return;
58
60
  switch (this.featureName) {
59
61
  // SessionTrace + Replay have their own storage mechanisms.
60
62
  case _features.FEATURE_NAMES.sessionTrace:
@@ -6,11 +6,13 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.EventStoreManager = void 0;
7
7
  var _globalEvent = require("../../common/dispatch/global-event");
8
8
  var _featureFlags = require("../../common/util/feature-flags");
9
+ var _target = require("../../common/util/target");
9
10
  /**
10
11
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
11
12
  * SPDX-License-Identifier: Apache-2.0
12
13
  */
13
14
 
15
+ const DEFAULT_KEY = 'NR_CONTAINER_AGENT'; // this is the default entity guid used for the default storage instance
14
16
  /**
15
17
  * This layer allows multiple browser entity apps, or "target", to each have their own segregated storage instance.
16
18
  * The purpose is so the harvester can send data to different apps within the same agent. Each feature should have a manager if it needs this capability.
@@ -19,14 +21,16 @@ class EventStoreManager {
19
21
  /**
20
22
  * @param {object} agentRef - reference to base agent class
21
23
  * @param {EventBuffer|EventAggregator} storageClass - the type of storage to use in this manager; 'EventBuffer' (1), 'EventAggregator' (2)
24
+ * @param {string} [defaultEntityGuid] - the entity guid to use as the default storage instance; if not provided, a new one is created
25
+ * @param {string} featureName - the name of the feature this manager is for; used for event dispatching
22
26
  */
23
27
  constructor(agentRef, storageClass, defaultEntityGuid, featureName) {
24
28
  this.agentRef = agentRef;
25
29
  this.entityManager = agentRef.runtime.entityManager;
26
30
  this.StorageClass = storageClass;
27
- this.appStorageMap = new Map();
28
- this.defaultEntity = this.#getEventStore(defaultEntityGuid);
31
+ this.appStorageMap = new Map([[DEFAULT_KEY, new this.StorageClass()]]);
29
32
  this.featureName = featureName;
33
+ this.setEventStore(defaultEntityGuid);
30
34
  }
31
35
 
32
36
  /**
@@ -34,11 +38,17 @@ class EventStoreManager {
34
38
  * @param {string=} targetEntityGuid the lookup
35
39
  * @returns {*} ALWAYS returns a storage instance
36
40
  */
37
- #getEventStore(targetEntityGuid) {
38
- if (!targetEntityGuid) return this.defaultEntity;
39
- if (!this.appStorageMap.has(targetEntityGuid)) this.appStorageMap.set(targetEntityGuid, new this.StorageClass());
41
+ #getEventStore(targetEntityGuid = DEFAULT_KEY) {
42
+ if (!this.appStorageMap.has(targetEntityGuid)) this.setEventStore(targetEntityGuid);
40
43
  return this.appStorageMap.get(targetEntityGuid);
41
44
  }
45
+ setEventStore(targetEntityGuid) {
46
+ /** we should already have an event store for the default */
47
+ if (!targetEntityGuid) return;
48
+ /** if the target is the container agent, SHARE the default storage -- otherwise create a new event store */
49
+ const eventStorage = (0, _target.isContainerAgentTarget)(this.entityManager.get(targetEntityGuid), this.agentRef) ? this.appStorageMap.get(DEFAULT_KEY) : new this.StorageClass();
50
+ this.appStorageMap.set(targetEntityGuid, eventStorage);
51
+ }
42
52
 
43
53
  // This class must contain an union of all methods from all supported storage classes and conceptualize away the target app argument.
44
54
 
@@ -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.1";
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.1";
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
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Session/Expired/SessionReplay/Seen'], undefined, FEATURE_NAMES.metrics, this.ee);
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,16 +23,10 @@ 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
- /** this.events (ixns to harvest) has already been set up, use it immediately */
29
- if (this.interactionsToHarvest) this.interactionsToHarvest.add(ixn);else {
30
- /** this.events (ixns to harvest) hasnt been initialized yet... wait for it */
31
- this.ee.on('entity-added', () => {
32
- this.interactionsToHarvest = this.events;
33
- this.interactionsToHarvest.add(ixn);
34
- });
35
- }
29
+ this.interactionsToHarvest.add(ixn);
36
30
  this.initialPageLoadInteraction = null;
37
31
  });
38
32
  timeToFirstByte.subscribe(({
@@ -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
@@ -32,11 +32,13 @@ export class AggregateBase extends FeatureBase {
32
32
  this.harvestOpts = {}; // features aggregate classes can define custom opts for when their harvest is called
33
33
 
34
34
  const agentEntityGuid = this.agentRef?.runtime?.appMetadata?.agents?.[0]?.entityGuid;
35
- if (agentEntityGuid) {
36
- this.#setupEventStore(agentEntityGuid); // if there's no entity guid, wont dont anything, and will wait for rum flags
37
- } else {
35
+ this.#setupEventStore(agentEntityGuid);
36
+ if (!agentEntityGuid) {
37
+ /** wait for the entity guid from the rum response and use to it to further configure things to set the default entity to share an indexed entity with entityGuid */
38
38
  this.ee.on('entity-added', entity => {
39
- this.#setupEventStore(entity.entityGuid);
39
+ // not all event managers have this fn, like ST and SR
40
+ // this allows the lookup to work for the default and an entityGuid without creating two separate buffers
41
+ this.events?.setEventStore?.(entity.entityGuid);
40
42
  });
41
43
  }
42
44
  }
@@ -47,7 +49,7 @@ export class AggregateBase extends FeatureBase {
47
49
  * @returns {void}
48
50
  */
49
51
  #setupEventStore(entityGuid) {
50
- if (this.events || !entityGuid) return;
52
+ if (this.events) return;
51
53
  switch (this.featureName) {
52
54
  // SessionTrace + Replay have their own storage mechanisms.
53
55
  case FEATURE_NAMES.sessionTrace:
@@ -4,7 +4,8 @@
4
4
  */
5
5
  import { dispatchGlobalEvent } from '../../common/dispatch/global-event';
6
6
  import { activatedFeatures } from '../../common/util/feature-flags';
7
-
7
+ import { isContainerAgentTarget } from '../../common/util/target';
8
+ const DEFAULT_KEY = 'NR_CONTAINER_AGENT'; // this is the default entity guid used for the default storage instance
8
9
  /**
9
10
  * This layer allows multiple browser entity apps, or "target", to each have their own segregated storage instance.
10
11
  * The purpose is so the harvester can send data to different apps within the same agent. Each feature should have a manager if it needs this capability.
@@ -13,14 +14,16 @@ export class EventStoreManager {
13
14
  /**
14
15
  * @param {object} agentRef - reference to base agent class
15
16
  * @param {EventBuffer|EventAggregator} storageClass - the type of storage to use in this manager; 'EventBuffer' (1), 'EventAggregator' (2)
17
+ * @param {string} [defaultEntityGuid] - the entity guid to use as the default storage instance; if not provided, a new one is created
18
+ * @param {string} featureName - the name of the feature this manager is for; used for event dispatching
16
19
  */
17
20
  constructor(agentRef, storageClass, defaultEntityGuid, featureName) {
18
21
  this.agentRef = agentRef;
19
22
  this.entityManager = agentRef.runtime.entityManager;
20
23
  this.StorageClass = storageClass;
21
- this.appStorageMap = new Map();
22
- this.defaultEntity = this.#getEventStore(defaultEntityGuid);
24
+ this.appStorageMap = new Map([[DEFAULT_KEY, new this.StorageClass()]]);
23
25
  this.featureName = featureName;
26
+ this.setEventStore(defaultEntityGuid);
24
27
  }
25
28
 
26
29
  /**
@@ -28,11 +31,17 @@ export class EventStoreManager {
28
31
  * @param {string=} targetEntityGuid the lookup
29
32
  * @returns {*} ALWAYS returns a storage instance
30
33
  */
31
- #getEventStore(targetEntityGuid) {
32
- if (!targetEntityGuid) return this.defaultEntity;
33
- if (!this.appStorageMap.has(targetEntityGuid)) this.appStorageMap.set(targetEntityGuid, new this.StorageClass());
34
+ #getEventStore(targetEntityGuid = DEFAULT_KEY) {
35
+ if (!this.appStorageMap.has(targetEntityGuid)) this.setEventStore(targetEntityGuid);
34
36
  return this.appStorageMap.get(targetEntityGuid);
35
37
  }
38
+ setEventStore(targetEntityGuid) {
39
+ /** we should already have an event store for the default */
40
+ if (!targetEntityGuid) return;
41
+ /** if the target is the container agent, SHARE the default storage -- otherwise create a new event store */
42
+ const eventStorage = isContainerAgentTarget(this.entityManager.get(targetEntityGuid), this.agentRef) ? this.appStorageMap.get(DEFAULT_KEY) : new this.StorageClass();
43
+ this.appStorageMap.set(targetEntityGuid, eventStorage);
44
+ }
36
45
 
37
46
  // This class must contain an union of all methods from all supported storage classes and conceptualize away the target app argument.
38
47
 
@@ -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;;OAoDC;IAjDC,2BAAwC;IACxC,iBAA8B;IAE9B,uDAA0F;IAe1F,yBAA+B;IAC/B,0CAAiC;IACjC,sBAA4B;IA+B9B,qCAUC;IAED,0EAiBC;IAED,2BAiBC;IAED;;;;;;;OAOG;IACH,6BAHW,mBAAmB,OAqB7B;;CA6FF;8BAvO6B,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"AAqBA;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EAkBhB;IAdC,iBAAwB;IAIxB,gBAAqB;IA2BjB,YAAuI;IAW7I;;;;OAIG;IACH,yBAHW,MAAM,EAAE,gBAwBlB;IAED;;OAEG;IACH,cAGC;IADC,6BAAmB;IAGrB,qCAEC;IAED;;;;;;OAMG;IACH,uDAJW,MAAM,GAAC,SAAS,SAyB1B;IAED;;;;;;;OAOG;IACH,4BALG;QAAwB,SAAS,GAAzB,MAAM,YAAC;KACf,QAQF;IAED;;;OAGG;IACH,6CAsBC;IAED;;;OAGG;IACH,2CAOC;IALC,gBAA6C;IAO/C;;;;OAIG;IACH,uCAHW,GAAC,UACD,GAAC,QAIX;;CACF;4BAxM2B,gBAAgB"}
1
+ {"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"AAqBA;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EAoBhB;IAhBC,iBAAwB;IAIxB,gBAAqB;IA6BjB,YAAuI;IAW7I;;;;OAIG;IACH,yBAHW,MAAM,EAAE,gBAwBlB;IAED;;OAEG;IACH,cAGC;IADC,6BAAmB;IAGrB,qCAEC;IAED;;;;;;OAMG;IACH,uDAJW,MAAM,GAAC,SAAS,SAyB1B;IAED;;;;;;;OAOG;IACH,4BALG;QAAwB,SAAS,GAAzB,MAAM,YAAC;KACf,QAQF;IAED;;;OAGG;IACH,6CAsBC;IAED;;;OAGG;IACH,2CAOC;IALC,gBAA6C;IAO/C;;;;OAIG;IACH,uCAHW,GAAC,UACD,GAAC,QAIX;;CACF;4BA1M2B,gBAAgB"}
@@ -6,14 +6,16 @@ export class EventStoreManager {
6
6
  /**
7
7
  * @param {object} agentRef - reference to base agent class
8
8
  * @param {EventBuffer|EventAggregator} storageClass - the type of storage to use in this manager; 'EventBuffer' (1), 'EventAggregator' (2)
9
+ * @param {string} [defaultEntityGuid] - the entity guid to use as the default storage instance; if not provided, a new one is created
10
+ * @param {string} featureName - the name of the feature this manager is for; used for event dispatching
9
11
  */
10
- constructor(agentRef: object, storageClass: EventBuffer | EventAggregator, defaultEntityGuid: any, featureName: any);
12
+ constructor(agentRef: object, storageClass: EventBuffer | EventAggregator, defaultEntityGuid?: string, featureName: string);
11
13
  agentRef: object;
12
14
  entityManager: any;
13
15
  StorageClass: any;
14
- appStorageMap: Map<any, any>;
15
- defaultEntity: any;
16
- featureName: any;
16
+ appStorageMap: Map<string, any>;
17
+ featureName: string;
18
+ setEventStore(targetEntityGuid: any): void;
17
19
  /**
18
20
  * Calls the isEmpty method on the underlying storage class. If target is provided, runs just for the target, otherwise runs for all apps.
19
21
  * @param {object} optsIfPresent - exists if called during harvest interval, @see AggregateBase.makeHarvestPayload
@@ -1 +1 @@
1
- {"version":3,"file":"event-store-manager.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/event-store-manager.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH;IACE;;;OAGG;IACH,sBAHW,MAAM,gBACN,WAAW,GAAC,eAAe,4CASrC;IANC,iBAAwB;IACxB,mBAAmD;IACnD,kBAAgC;IAChC,6BAA8B;IAC9B,mBAA2D;IAC3D,iBAA8B;IAgBhC;;;;;OAKG;IACH,uBAJW,MAAM,0BAEJ,OAAO,CASnB;IAED;;;;;OAKG;IACH,WAJW,MAAM,oBACN,MAAM,GACJ,OAAO,CAYnB;IAED,0GAA0G;IAC1G,8DAEC;IAED;;;;;OAKG;IACH,WAJW,MAAM,YAAC,gCAYjB;IAED;;;;OAIG;IACH,2BAHW,GAAC,OAKX;IAED;;;;;OAKG;IACH,iCAJW,GAAC,oBACD,GAAC,OAKX;IAED;;;;;OAKG;IACH,oBAJW,GAAC,oBACD,GAAC,OAMX;IAED;;;;;OAKG;IACH,qBAJW,GAAC,oBACD,GAAC,OAMX;IAGD,2DAEC;IAED,0DAEC;;CACF"}
1
+ {"version":3,"file":"event-store-manager.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/event-store-manager.js"],"names":[],"mappings":"AASA;;;GAGG;AACH;IACE;;;;;OAKG;IACH,sBALW,MAAM,gBACN,WAAW,GAAC,eAAe,sBAC3B,MAAM,eACN,MAAM,EAShB;IANC,iBAAwB;IACxB,mBAAmD;IACnD,kBAAgC;IAChC,gCAAsE;IACtE,oBAA8B;IAchC,2CAQC;IAID;;;;;OAKG;IACH,uBAJW,MAAM,0BAEJ,OAAO,CASnB;IAED;;;;;OAKG;IACH,WAJW,MAAM,oBACN,MAAM,GACJ,OAAO,CAYnB;IAED,0GAA0G;IAC1G,8DAEC;IAED;;;;;OAKG;IACH,WAJW,MAAM,YAAC,gCAYjB;IAED;;;;OAIG;IACH,2BAHW,GAAC,OAKX;IAED;;;;;OAKG;IACH,iCAJW,GAAC,oBACD,GAAC,OAKX;IAED;;;;;OAKG;IACH,oBAJW,GAAC,oBACD,GAAC,OAMX;IAED;;;;;OAKG;IACH,qBAJW,GAAC,oBACD,GAAC,OAMX;IAGD,2DAEC;IAED,0DAEC;;CACF"}
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.1",
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
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Session/Expired/SessionReplay/Seen'], undefined, FEATURE_NAMES.metrics, this.ee)
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,17 +23,10 @@ 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
- /** this.events (ixns to harvest) has already been set up, use it immediately */
29
- if (this.interactionsToHarvest) this.interactionsToHarvest.add(ixn)
30
- else {
31
- /** this.events (ixns to harvest) hasnt been initialized yet... wait for it */
32
- this.ee.on('entity-added', () => {
33
- this.interactionsToHarvest = this.events
34
- this.interactionsToHarvest.add(ixn)
35
- })
36
- }
29
+ this.interactionsToHarvest.add(ixn)
37
30
  this.initialPageLoadInteraction = null
38
31
  })
39
32
  timeToFirstByte.subscribe(({ attrs }) => {
@@ -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
@@ -34,11 +34,13 @@ export class AggregateBase extends FeatureBase {
34
34
  this.harvestOpts = {} // features aggregate classes can define custom opts for when their harvest is called
35
35
 
36
36
  const agentEntityGuid = this.agentRef?.runtime?.appMetadata?.agents?.[0]?.entityGuid
37
- if (agentEntityGuid) {
38
- this.#setupEventStore(agentEntityGuid) // if there's no entity guid, wont dont anything, and will wait for rum flags
39
- } else {
37
+ this.#setupEventStore(agentEntityGuid)
38
+ if (!agentEntityGuid) {
39
+ /** wait for the entity guid from the rum response and use to it to further configure things to set the default entity to share an indexed entity with entityGuid */
40
40
  this.ee.on('entity-added', entity => {
41
- this.#setupEventStore(entity.entityGuid)
41
+ // not all event managers have this fn, like ST and SR
42
+ // this allows the lookup to work for the default and an entityGuid without creating two separate buffers
43
+ this.events?.setEventStore?.(entity.entityGuid)
42
44
  })
43
45
  }
44
46
  }
@@ -49,7 +51,7 @@ export class AggregateBase extends FeatureBase {
49
51
  * @returns {void}
50
52
  */
51
53
  #setupEventStore (entityGuid) {
52
- if (this.events || !entityGuid) return
54
+ if (this.events) return
53
55
  switch (this.featureName) {
54
56
  // SessionTrace + Replay have their own storage mechanisms.
55
57
  case FEATURE_NAMES.sessionTrace:
@@ -4,7 +4,9 @@
4
4
  */
5
5
  import { dispatchGlobalEvent } from '../../common/dispatch/global-event'
6
6
  import { activatedFeatures } from '../../common/util/feature-flags'
7
+ import { isContainerAgentTarget } from '../../common/util/target'
7
8
 
9
+ const DEFAULT_KEY = 'NR_CONTAINER_AGENT' // this is the default entity guid used for the default storage instance
8
10
  /**
9
11
  * This layer allows multiple browser entity apps, or "target", to each have their own segregated storage instance.
10
12
  * The purpose is so the harvester can send data to different apps within the same agent. Each feature should have a manager if it needs this capability.
@@ -13,14 +15,16 @@ export class EventStoreManager {
13
15
  /**
14
16
  * @param {object} agentRef - reference to base agent class
15
17
  * @param {EventBuffer|EventAggregator} storageClass - the type of storage to use in this manager; 'EventBuffer' (1), 'EventAggregator' (2)
18
+ * @param {string} [defaultEntityGuid] - the entity guid to use as the default storage instance; if not provided, a new one is created
19
+ * @param {string} featureName - the name of the feature this manager is for; used for event dispatching
16
20
  */
17
21
  constructor (agentRef, storageClass, defaultEntityGuid, featureName) {
18
22
  this.agentRef = agentRef
19
23
  this.entityManager = agentRef.runtime.entityManager
20
24
  this.StorageClass = storageClass
21
- this.appStorageMap = new Map()
22
- this.defaultEntity = this.#getEventStore(defaultEntityGuid)
25
+ this.appStorageMap = new Map([[DEFAULT_KEY, new this.StorageClass()]])
23
26
  this.featureName = featureName
27
+ this.setEventStore(defaultEntityGuid)
24
28
  }
25
29
 
26
30
  /**
@@ -28,12 +32,21 @@ export class EventStoreManager {
28
32
  * @param {string=} targetEntityGuid the lookup
29
33
  * @returns {*} ALWAYS returns a storage instance
30
34
  */
31
- #getEventStore (targetEntityGuid) {
32
- if (!targetEntityGuid) return this.defaultEntity
33
- if (!this.appStorageMap.has(targetEntityGuid)) this.appStorageMap.set(targetEntityGuid, new this.StorageClass())
35
+ #getEventStore (targetEntityGuid = DEFAULT_KEY) {
36
+ if (!this.appStorageMap.has(targetEntityGuid)) this.setEventStore(targetEntityGuid)
34
37
  return this.appStorageMap.get(targetEntityGuid)
35
38
  }
36
39
 
40
+ setEventStore (targetEntityGuid) {
41
+ /** we should already have an event store for the default */
42
+ if (!targetEntityGuid) return
43
+ /** if the target is the container agent, SHARE the default storage -- otherwise create a new event store */
44
+ const eventStorage = (isContainerAgentTarget(this.entityManager.get(targetEntityGuid), this.agentRef))
45
+ ? this.appStorageMap.get(DEFAULT_KEY)
46
+ : new this.StorageClass()
47
+ this.appStorageMap.set(targetEntityGuid, eventStorage)
48
+ }
49
+
37
50
  // This class must contain an union of all methods from all supported storage classes and conceptualize away the target app argument.
38
51
 
39
52
  /**