@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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/session/session-entity.js +10 -3
- package/dist/cjs/features/logging/aggregate/index.js +4 -2
- package/dist/cjs/features/session_replay/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/shared/recorder.js +5 -0
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +7 -0
- package/dist/cjs/features/soft_navigations/aggregate/index.js +1 -0
- package/dist/cjs/features/spa/aggregate/index.js +1 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/session/session-entity.js +10 -3
- package/dist/esm/features/logging/aggregate/index.js +4 -2
- package/dist/esm/features/session_replay/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/shared/recorder.js +5 -0
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +7 -0
- package/dist/esm/features/soft_navigations/aggregate/index.js +1 -0
- package/dist/esm/features/spa/aggregate/index.js +1 -0
- package/dist/types/common/session/session-entity.d.ts +3 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +1 -0
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/session/session-entity.js +10 -3
- package/src/features/logging/aggregate/index.js +4 -2
- package/src/features/session_replay/aggregate/index.js +1 -1
- package/src/features/session_replay/shared/recorder.js +5 -0
- package/src/features/session_trace/aggregate/trace/storage.js +8 -0
- package/src/features/soft_navigations/aggregate/index.js +1 -0
- 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.
|
|
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.
|
|
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
|
|
164
|
-
|
|
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
|
|
@@ -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
|
|
157
|
-
|
|
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":"
|
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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;;
|
|
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,
|
|
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
|
@@ -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
|
|
166
|
-
|
|
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
|