Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
53e2f3f
test: point the profiling test helpers at the packages they actually …
Fiona2016 Aug 19, 2026
a40a542
feat(rum): add sessionReplayOnErrorSampleRate
Fiona2016 Aug 19, 2026
5486c5d
feat(rum): add sessionOnErrorSampleRate
Fiona2016 Aug 20, 2026
5912e3e
refactor(rum): name the session a withheld segment belongs to just once
Fiona2016 Aug 20, 2026
8f33eee
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Aug 20, 2026
9cd24fa
refactor(rum): trim the withheld event buffer
Fiona2016 Aug 20, 2026
8ca8bca
fix(rum): keep a withheld replay buffer when the page is only hidden
Fiona2016 Aug 20, 2026
9b30590
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Aug 20, 2026
db44a19
fix(rum): spread releases properly, and release on exit when the erro…
Fiona2016 Aug 20, 2026
6698ae6
fix(rum): count buffered bytes as bytes, and stop calling a tier that…
Fiona2016 Aug 20, 2026
30dcbe0
fix(rum): settle the buffer when the session ends, and let stale view…
Fiona2016 Aug 20, 2026
f395239
style: drop an import left unused by the buffer wiring
Fiona2016 Aug 20, 2026
2c2c4f5
docs(rum): record the window in which a session can take its own rele…
Fiona2016 Aug 20, 2026
35beb20
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Aug 20, 2026
f516c9e
fix(rum): stop a dropped buffer leaving its segment index behind
Fiona2016 Aug 21, 2026
7549deb
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Aug 21, 2026
078a46e
fix(rum): keep the event buffer across a tab switch, and make the det…
Fiona2016 Aug 21, 2026
734001f
docs(rum): record why error tracking subscribes before the batch
Fiona2016 Aug 21, 2026
32ade09
feat(rum): mark a replay that is only kept because the session errored
Fiona2016 Aug 21, 2026
385fb36
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Aug 21, 2026
f84fd00
refactor(rum): resolve the rates a draw would use in one place
Fiona2016 Aug 31, 2026
2acc5d1
feat(rum): end the session when new settings decide its fate
Fiona2016 Aug 31, 2026
f6d9b5c
refactor(rum): keep the fork's lifecycle event out of upstream's numb…
Fiona2016 Aug 31, 2026
9cf3404
fix(rum): leave sessions that withhold nothing out of the session store
Fiona2016 Sep 1, 2026
a85a57e
fix(rum): give a dropped segment's index back before another one can …
Fiona2016 Sep 1, 2026
d0309f4
fix(rum): decide what to withhold by the event's own session, not the…
Fiona2016 Sep 1, 2026
a48e5a5
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
1a52a70
fix(rum): drop a withheld buffer as soon as its own session is gone
Fiona2016 Sep 1, 2026
c370e92
fix(rum): stop ending the sessions of visitors who are not collected
Fiona2016 Sep 1, 2026
738ef96
test(rum): cover the decision paths nothing was holding
Fiona2016 Sep 1, 2026
38fe32b
refactor(core): let a session store update see the state it would lan…
Fiona2016 Sep 1, 2026
4509cd4
fix(rum): keep the error mark on the session that reported the error
Fiona2016 Sep 1, 2026
3f3d27f
fix(rum): leave a stopped recorder alone when a late flush lands
Fiona2016 Sep 1, 2026
69d4e7f
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
2d54f00
fix(rum): hold the released window at the error, and remember more th…
Fiona2016 Sep 1, 2026
20fbc0b
docs(changelog): say what a decisive publish does to a running visit
Fiona2016 Sep 1, 2026
248f275
test(rum): follow the session id through the session manager mock
Fiona2016 Sep 1, 2026
1cfaad2
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
b77b205
fix(rum): send a withheld replay only when its own session earned it
Fiona2016 Sep 1, 2026
9c76b20
fix(rum): let a rate of a hundred through to a forced page that colle…
Fiona2016 Sep 1, 2026
1f01296
fix(rum): release withheld events only when their own session earned it
Fiona2016 Sep 1, 2026
082b9aa
fix(rum): do not offer a replay a dropped buffer took with it
Fiona2016 Sep 1, 2026
e8ba65f
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
c56015c
fix(rum): release a buffer as a session that reads back the way it ha…
Fiona2016 Sep 1, 2026
19b0367
fix(rum): let forcing a replay reach a session that is withholding one
Fiona2016 Sep 1, 2026
436f347
feat(rum): say so when a sampling rate cannot draw a single session
Fiona2016 Sep 1, 2026
e99479e
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
7b1c083
feat(rum): say so when sessionOnErrorSampleRate cannot draw a session…
Fiona2016 Sep 1, 2026
b808998
fix(rum): tell a withheld session's events that their replay is comin…
Fiona2016 Sep 1, 2026
ec33201
fix(rum): let forcing a replay reach a session that is withholding one
Fiona2016 Sep 1, 2026
d1e2f3e
feat(rum): say so when a sampling rate cannot draw a single session
Fiona2016 Sep 1, 2026
c3a5fce
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
53e3c64
fix(rum): drop a duplicate copy of the sampling warnings left by a merge
Fiona2016 Sep 1, 2026
725cb02
test(rum): hold the error-replay sampling to the promises it makes
Fiona2016 Sep 1, 2026
cec1c04
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
aac59e1
test(rum): hold the on-error session sampling to the promises it makes
Fiona2016 Sep 1, 2026
dec56f0
refactor(rum): drop a withholding default nothing withholds by
Fiona2016 Sep 1, 2026
06a2617
test(rum): name the session when marking it in the last spec that did…
Fiona2016 Sep 1, 2026
193bd91
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
6e1903c
refactor(rum): keep the withheld window as one number, and stop guard…
Fiona2016 Sep 1, 2026
a140bca
fix(rum): finish the half-handled cases the earlier fixes left behind
Fiona2016 Sep 1, 2026
659f0e0
fix(rum): do not claim a replay whose fate is not decided yet
Fiona2016 Sep 1, 2026
ba45ec6
fix(rum): count records, not segments, when deciding a view has a replay
Fiona2016 Sep 1, 2026
6d00bf7
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 1, 2026
f02d659
Merge pull request #40 from flashcatcloud/feat/end-session-on-new-set…
Fiona2016 Sep 1, 2026
9be78e7
refactor(rum): stop acting on a rate that rises to a hundred
Fiona2016 Sep 2, 2026
7f2c44a
Merge pull request #41 from flashcatcloud/refactor/drop-the-hundred-rule
Fiona2016 Sep 2, 2026
2ff548f
v0.2.1
Fiona2016 Sep 2, 2026
ff4ce8a
chore: refresh the lockfile for the 0.2.1 workspace versions
Fiona2016 Sep 2, 2026
fc3ed2f
refactor(rum): stop shipping a number the stored data already answers
Fiona2016 Sep 2, 2026
0f866c1
fix(rum): sweep the settings entries of releases nobody runs
Fiona2016 Sep 2, 2026
9acdb7a
docs(rum): record that a withdrawn consent releases what it already e…
Fiona2016 Sep 2, 2026
6ff981f
fix(rum): keep settings entries without a write time
Fiona2016 Sep 4, 2026
5b31603
Merge pull request #43 from flashcatcloud/feat/remote-config-cache-sweep
Fiona2016 Sep 4, 2026
01ad2d7
v0.2.2
Fiona2016 Sep 4, 2026
06bb967
feat(rum): apply a rate that leaves zero to the running session
Fiona2016 Sep 4, 2026
89af49c
feat(rum): make sessionReplayOnError a switch, not a sample rate
Fiona2016 Sep 5, 2026
649bc09
feat(rum): make sessionOnError a switch, not a sample rate
Fiona2016 Sep 5, 2026
97b061a
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 5, 2026
7aef035
test(rum): name the session replay on error specs after the switch
Fiona2016 Sep 5, 2026
55e9fec
test(rum): name the session on error specs after the switch
Fiona2016 Sep 5, 2026
ff76071
Merge branch 'publish' into feat/error-session-replay-sampling
Fiona2016 Sep 6, 2026
378981e
fix(rum): narrow the next creation reason where the compiler can see it
Fiona2016 Sep 6, 2026
a83b13d
test(rum): store the released session with the expiry a session now h…
Fiona2016 Sep 6, 2026
fc3f727
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 6, 2026
4ad41c0
Merge branch 'feat/error-session-replay-sampling' into feat/error-ses…
Fiona2016 Sep 6, 2026
6413227
feat(rum): read sessionReplayOnError from remote configuration
Fiona2016 Sep 7, 2026
b2b2a70
feat(rum): read sessionOnError from remote configuration
Fiona2016 Sep 7, 2026
1ef917a
fix(rum): let a forced session release events it withholds without a …
Fiona2016 Sep 7, 2026
175bf28
fix(rum): decide on a sampled-out session from the draw record in sto…
Fiona2016 Sep 7, 2026
fb94a28
Merge pull request #44 from flashcatcloud/feat/remote-config-open-gate
Fiona2016 Sep 7, 2026
224beef
v0.2.3
Fiona2016 Sep 7, 2026
057815b
chore: restore the trailing newline lerna dropped from lerna.json
Fiona2016 Sep 7, 2026
3054135
fix(rum): restore remotely enabled replay and oversized snapshot base…
Fiona2016 Sep 7, 2026
1460277
fix(rum): preserve oversized errors and restore replay baselines
Fiona2016 Sep 7, 2026
645c9a0
Merge branch feat/error-session-replay-sampling into feat/error-sessi…
Fiona2016 Sep 7, 2026
2dceabf
fix(rum): preserve conditional replay across session and worker races
Fiona2016 Sep 7, 2026
19b8b42
fix(rum): synchronize conditional sessions and drain released events
Fiona2016 Sep 7, 2026
856cad3
Merge branch feat/error-session-replay-sampling into feat/error-sessi…
Fiona2016 Sep 7, 2026
4bb1f42
fix(rum): keep an on-error session alive when a zero rate is decided
Fiona2016 Sep 8, 2026
02cfde4
docs(rum): correct the manual-start default doc and cover two withhel…
Fiona2016 Sep 8, 2026
2aa92a7
Merge remote-tracking branch 'origin/feat/error-session-replay-sampli…
Fiona2016 Sep 8, 2026
d434326
docs(rum): changelog and doc entries for the on-error switches, and f…
Fiona2016 Sep 8, 2026
fcd653d
fix(rum): correct on-error session sampling reporting, warnings, and …
Fiona2016 Sep 8, 2026
9eef19d
fix(rum): narrow the zero-rate keep, honour beforeSampling 0, and bou…
Fiona2016 Sep 9, 2026
1a43e67
docs(rum): document the on-error switch limitations
Fiona2016 Sep 9, 2026
f09512d
Merge error-session capture onto publish
Fiona2016 Sep 10, 2026
1d0b8c7
fix(rum): do not blacklist the live session an older bundle redrew un…
Fiona2016 Sep 10, 2026
3135c45
fix(rum-legacy): drop the modern write lock instead of carrying it
Fiona2016 Sep 10, 2026
8722c9e
Merge the error-session review follow-ups onto publish
Fiona2016 Sep 10, 2026
ec73117
fix(rum): report an on-error session as unscaled from its tracking type
Fiona2016 Sep 15, 2026
94fc01a
Merge the on-error session sample rate fix onto publish
Fiona2016 Sep 15, 2026
b86b1fe
test(rum): pin which on-error sessions report an unscaled sample rate
Fiona2016 Sep 15, 2026
26173d5
Merge the on-error sample rate test coverage onto publish
Fiona2016 Sep 15, 2026
0d532cc
fix(rum): keep the error and its replay claim in a released on-error …
Fiona2016 Sep 15, 2026
a189901
Merge the on-error release ordering and replay claim fixes onto publish
Fiona2016 Sep 15, 2026
7464969
fix(rum): claim a replay on the releasing error only when its view ke…
Fiona2016 Sep 15, 2026
d7c73cf
Merge the per-view replay claim fix onto publish
Fiona2016 Sep 15, 2026
0444d30
Merge error-session capture onto publish
Fiona2016 Sep 18, 2026
acb3515
v0.3.0
Fiona2016 Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,117 @@

---

## v0.3.0

- ✨ Two new init options keep only the sessions that report an error, for customers who want every
error investigated without storing and paying for every session. `sessionOnError` keeps the
events of a session the plain `sessionSampleRate` draw missed: it records from the start, uploads
nothing, and is never stored unless it reports an error — on the first error the withheld history,
up to the last minute of it, is uploaded and collection continues. `sessionReplayOnError` does the
same for the Session Replay of a session the plain `sessionReplaySampleRate` draw missed. Both are
switches, default off, and apply only to what the plain rate did not already draw, so a session is
never counted twice. Both can also be set from the console when `remoteConfigurationEnabled` is on.
View events of such a session carry `sampled_for_error` / `sampled_for_error_replay` so a stored
error session can be told apart from an ordinary one.

Known limitations of the on-error switches:

- A site that gates recording on consent by calling `startSessionReplayRecording()` itself must set
`startSessionReplayRecordingManually: true` explicitly. With `remoteConfigurationEnabled` on and an
init replay rate of 0, the recorder now starts on its own so a console-delivered rate has something
to withhold — which would otherwise begin recording before the consent call.
- On a single-page app, the released replay reaches back only to the start of the view the error
happened in, while the released events reach back the full minute across views.
- Leaving the page within a few seconds of a session's first error can lose part of a large release
(roughly above 64 KiB, such as a busy minute of requests), compressed or not: a browser only
guarantees a bounded amount of data at page exit. The views and the errors are sent first, so what
is lost is the oldest of the other events, and possibly the page's final view update.
- On a page with a very large DOM that changes constantly, a withheld replay re-takes a full snapshot
each time its buffer overflows. Every visitor the plain replay rate did not draw pays that main
thread cost, and the replay released before the error gets shorter. Prefer `sessionOnError` alone
on such pages.
- Withdrawing tracking consent after a session has reported its error still uploads what was withheld
for it, all of which was collected while consent stood.

- 🐛 With `remoteConfigurationEnabled` on and an init `sessionReplaySampleRate` of 0 (or none), a Session
Replay rate set in the console now takes effect: recording starts, and replays are uploaded and
billed. Before, the rate was delivered but never started the recorder. Pass
`startSessionReplayRecordingManually: true` to keep recording off until you start it yourself.

## v0.2.3

- ✨ A session sample rate published from the console that rises above 0 now ends the running
session of a visitor whose session was drawn at 0, so collection starts at their next interaction
instead of waiting for that session to end on its own — up to four hours. This is the case where
waiting shows an operator who has just switched collection on nothing at all, which is
indistinguishable from a broken integration. It joins the two changes that already did not wait:
a stricter Session Replay privacy level, and a rate of 0. Nothing here happens without
`remoteConfigurationEnabled: true`.
- 📝 Only a session drawn AT 0 is re-drawn, not every session that is not being collected. Those
are different populations: a visitor who lost a draw at 30 had a coin flipped for them, and
re-rolling the losers while the winners keep their sessions would put the real rate above the
published one. While a rate of 0 is in force nothing is collected and no coin is flipped, so
re-drawing everyone lands exactly on the new rate. A rate rising from one real value to another
therefore still waits for the next session, as before.
- 📝 The rate a sampled-out session was drawn at is now recorded alongside the one a collected
session was drawn at, in the same single `localStorage` entry this SDK already keeps for the
draw. No new entry, no extra request. Without it a page that did not perform the draw — the
second page of a visit, or another tab — could not tell the two populations above apart. The
decision reads that record straight off storage rather than off what the page last saw of the
draw: two sampled-out sessions look alike to the session store, so a tab can miss another tab
ending one and drawing the next, and storage is the one place the current draw is always found.
- 📝 What you will see on the day you lift a rate off 0: visitors who were invisible start
appearing within seconds of loading a page rather than at their next session, so collected volume
climbs the same day rather than the next. That is the change taking effect, not a defect.

---

## v0.2.2

- 🐛 The settings cache no longer grows by one entry per release of your site. Entries are keyed by
application version, because two releases served at the same time are entitled to different rates
— but the entry a previous release used was never read or removed again, so on a site that
deploys often they accumulated in the storage quota the page shares. New entries now record when
they were refreshed, and one left untouched for two days is removed when the SDK starts. Entries
written by older SDK builds are kept because they carry no refresh time, leaving a finite legacy
residue while preventing the cache from growing without bound.

## v0.2.1

- ✨ Two changes published from the console now end the running session, so they reach the visitor
at their next interaction instead of waiting for that session to end on its own: a stricter
Session Replay privacy level, and a session sample rate of 0 — the emergency stop, which took up
to four hours to stop anything before this. Both apply only while the visitor is being collected;
one who is not records nothing and sends nothing, so neither change has anything to act on there.
The session that ends is collected to its end as it began, so no recording is left masked in one
half and plain in the other. Every other change still waits for the next session, including a
loosening privacy level and a rate rising to 100 — for "collect this visitor now" there is
`setForcedSession()`. Custom values wait on their own too, but not once `beforeSampling` turns
them into a rate of 0. Nothing here happens without `remoteConfigurationEnabled: true`.
- 📝 How soon "does not wait" is depends on when this client next hears of the change, and it hears
only at page load and at each new session — there is no timer. A visitor who keeps loading pages
hears within seconds of the publish and their session ends there. A single tab that is never
reloaded hears nothing until its session reaches the four-hour cap, so an always-on screen is the
case this does least for; any other tab the same visitor loads ends the session they share.
- 📝 What you will see on the day you publish one of the two: session counts rise and average
session length drops, because each affected visitor's running session is split at that moment,
and a replay in progress ends at the split — the session that follows draws again, so it carries
a new recording only if that draw keeps one. That is the change taking effect, not a defect.
- 📝 The two act on what actually changed, not on the activation mode recorded with the publish: a
change the console files as "next session" still ends the running session if it is one of them.
- 📝 `beforeSampling` is now also consulted when settings arrive, away from any draw, to work out
which rate would apply. It must stay free of side effects and answer the same way for the same
input: a callback that draws its own lottery — answering 0 at random — can end a session that a
steady answer would have left running.
- 📝 A session forced with `setForcedSession()` is not ended by a rate: forcing decides whether this
visitor is collected, and every draw the page makes is collected whatever the console says, so
ending it would only produce the same session again. A stricter privacy level still ends it,
because forcing says nothing about how much of the page may be uploaded in the clear. The page
forces the next session on its own, so the visit continues as two sessions.
- 📝 Turning remote configuration off is itself a change: the rates go back to the ones passed to
`init`. On a site whose init rate is 0, switching it off stops collection at once rather than at
the next session.

## v0.2.0

- 💥 **Breaking**: `remoteConfigurationId` is gone from `RumInitConfiguration`. It fetched a
Expand Down
2 changes: 1 addition & 1 deletion developer-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flashcatcloud/browser-sdk-developer-extension",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"scripts": {
"build": "rm -rf dist && webpack --mode production",
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"npmClient": "yarn",
"version": "0.2.0"
"version": "0.3.0"
}
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flashcatcloud/browser-core",
"version": "0.2.0",
"version": "0.3.0",
"license": "Apache-2.0",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/domain/session/sessionManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ describe('startSessionManager', () => {
const sessionManager = startSessionManagerWithDefaults()
sessionManager.sessionStateUpdateObservable.subscribe(sessionStateUpdateSpy)

sessionManager.updateSessionState({ extra: 'extra' })
sessionManager.updateSessionState(() => ({ extra: 'extra' }))

expectSessionIdToBeDefined(sessionManager)
expect(sessionStateUpdateSpy).toHaveBeenCalledTimes(1)
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/domain/session/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ export interface SessionManager<TrackingType extends string> {
expireObservable: Observable<void>
sessionStateUpdateObservable: Observable<{ previousState: SessionState; newState: SessionState }>
expire: () => void
updateSessionState: (state: Partial<SessionState>) => void
updateSessionState: (update: (state: SessionState) => Partial<SessionState> | undefined) => void
}

export interface SessionContext<TrackingType extends string> extends Context {
id: string
trackingType: TrackingType
isReplayForced: boolean
/**
* Whether an error has already been reported during this session. Persisted in the session store
* so it survives page navigation: an error session must not go back to withholding its replay
* just because the user moved to another page.
*/
hasError: boolean
anonymousId: string | undefined
}

Expand Down Expand Up @@ -92,6 +98,7 @@ export function startSessionManager<TrackingType extends string>(
id: sessionStore.getSession().id!,
trackingType: sessionStore.getSession()[productKey] as TrackingType,
isReplayForced: !!sessionStore.getSession().forcedReplay,
hasError: !!sessionStore.getSession().hasError,
anonymousId: sessionStore.getSession().anonymousId,
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/domain/session/sessionStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ describe('session store', () => {
sessionStoreManager = setupSessionStore(updateSpy)
otherSessionStoreManager = setupSessionStore(otherUpdateSpy)

sessionStoreManager.updateSessionState({ extra: 'extra' })
sessionStoreManager.updateSessionState(() => ({ extra: 'extra' }))

expect(updateSpy).toHaveBeenCalledTimes(1)

Expand Down
14 changes: 11 additions & 3 deletions packages/core/src/domain/session/sessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ export interface SessionStore {
sessionStateUpdateObservable: Observable<{ previousState: SessionState; newState: SessionState }>
expire: () => void
stop: () => void
updateSessionState: (state: Partial<SessionState>) => void
/**
* Applies a change to the stored session under the store lock. The producer sees the state the
* change would land on and returns `undefined` to make it a no-op - which is how a write meant for
* one session avoids landing on the one that replaced it while the write was waiting for the lock.
*/
updateSessionState: (update: (state: SessionState) => Partial<SessionState> | undefined) => void
}

/**
Expand Down Expand Up @@ -216,10 +221,13 @@ export function startSessionStore<TrackingType extends string>(
renewObservable.notify()
}

function updateSessionState(partialSessionState: Partial<SessionState>) {
function updateSessionState(update: (state: SessionState) => Partial<SessionState> | undefined) {
processSessionStoreOperations(
{
process: (sessionState) => ({ ...sessionState, ...partialSessionState }),
process: (sessionState) => {
const partialSessionState = update(sessionState)
return partialSessionState && { ...sessionState, ...partialSessionState }
},
after: synchronizeSession,
},
sessionStoreStrategy
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { startFakeTelemetry } from '../telemetry'
import type { MockStorage } from '../../../test'
import { mockClock, mockCookie, mockLocalStorage } from '../../../test'
import type { CookieOptions } from '../../browser/cookie'
Expand Down Expand Up @@ -232,6 +233,7 @@ const DEFAULT_INIT_CONFIGURATION = { trackAnonymousUser: true } as Configuration

it('should abort after a max number of retry', () => {
const clock = mockClock()
const telemetry = startFakeTelemetry()

sessionStoreStrategy.persistSession(initialSession)
storage.setSpy.calls.reset()
Expand All @@ -246,6 +248,7 @@ const DEFAULT_INIT_CONFIGURATION = { trackAnonymousUser: true } as Configuration
expect(processSpy).not.toHaveBeenCalled()
expect(afterSpy).not.toHaveBeenCalled()
expect(storage.setSpy).not.toHaveBeenCalled()
expect(telemetry).toContain(jasmine.objectContaining({ message: 'Session store lock retries exhausted' }))

clock.cleanup()
})
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/domain/session/sessionStoreOperations.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { addTelemetryDebug } from '../telemetry'
import { setTimeout } from '../../tools/timer'
import { generateUUID } from '../../tools/utils/stringUtils'
import type { SessionStoreStrategy } from './storeStrategies/sessionStoreStrategy'
Expand Down Expand Up @@ -43,6 +44,7 @@ export function processSessionStoreOperations(
return
}
if (isLockEnabled && numberOfRetries >= LOCK_MAX_TRIES) {
addTelemetryDebug('Session store lock retries exhausted', { retries: numberOfRetries })
next(sessionStoreStrategy)
return
}
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/domain/telemetry/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { NO_ERROR_STACK_PRESENT_MESSAGE, isError } from '../error/error'
import { toStackTraceString } from '../../tools/stackTrace/handlingStack'
import { getExperimentalFeatures } from '../../tools/experimentalFeatures'
import type { Configuration } from '../configuration'
import { INTAKE_SITE_STAGING } from '../configuration'
// Import the constant without loading configuration construction, which uses session storage.
// eslint-disable-next-line local-rules/disallow-protected-directory-import
import { INTAKE_SITE_STAGING } from '../configuration/intakeSites'
import { Observable } from '../../tools/observable'
import { timeStampNow } from '../../tools/utils/timeUtils'
import { displayIfDebugEnabled, startMonitorErrorCollection } from '../../tools/monitor'
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/transport/flushController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function createFlushController({
}

return {
flush,
flushObservable,
get messagesCount() {
return currentMessagesCount
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/transport/startBatchWithReplica.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { RawError } from '../domain/error/error.types'
import type { Encoder } from '../tools/encoder'
import { createBatch } from './batch'
import { createHttpRequest } from './httpRequest'
import type { FlushReason } from './flushController'
import { createFlushController } from './flushController'

export interface BatchConfiguration {
Expand Down Expand Up @@ -45,6 +46,10 @@ export function startBatchWithReplica<T extends Context>(
}

return {
flush: (reason: FlushReason) => {
primaryBatch.flushController.flush(reason)
replicaBatch?.flushController.flush(reason)
},
flushObservable: primaryBatch.flushController.flushObservable,

add(message: T, replicated = true) {
Expand Down
8 changes: 7 additions & 1 deletion packages/core/test/emulate/mockFlushController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function createMockFlushController() {
let currentMessagesCount = 0
let currentBytesCount = 0

return {
const controller = {
notifyBeforeAddMessage: jasmine
.createSpy<FlushController['notifyBeforeAddMessage']>()
.and.callFake((messageBytesCount) => {
Expand All @@ -33,6 +33,11 @@ export function createMockFlushController() {
return currentBytesCount
},
flushObservable,
flush(reason: FlushReason) {
if (currentMessagesCount > 0) {
controller.notifyFlush(reason)
}
},
notifyFlush(reason: FlushReason = 'bytes_limit') {
if (currentMessagesCount === 0) {
throw new Error(
Expand All @@ -53,4 +58,5 @@ export function createMockFlushController() {
})
},
} satisfies Record<any, any> & FlushController
return controller
}
4 changes: 2 additions & 2 deletions packages/flagging/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flashcatcloud/browser-flagging",
"version": "0.2.0",
"version": "0.3.0",
"license": "Apache-2.0",
"private": true,
"main": "cjs/entries/main.js",
Expand All @@ -18,7 +18,7 @@
"@flashcatcloud/browser-core": "workspace:*"
},
"peerDependencies": {
"@flashcatcloud/browser-rum": "0.2.0"
"@flashcatcloud/browser-rum": "0.3.0"
},
"peerDependenciesMeta": {
"@flashcatcloud/browser-rum": {
Expand Down
4 changes: 2 additions & 2 deletions packages/logs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flashcatcloud/browser-logs",
"version": "0.2.0",
"version": "0.3.0",
"license": "Apache-2.0",
"main": "cjs/entries/main.js",
"module": "esm/entries/main.js",
Expand All @@ -17,7 +17,7 @@
"@flashcatcloud/browser-core": "workspace:*"
},
"peerDependencies": {
"@flashcatcloud/browser-rum": "0.2.0"
"@flashcatcloud/browser-rum": "0.3.0"
},
"peerDependenciesMeta": {
"@flashcatcloud/browser-rum": {
Expand Down
2 changes: 1 addition & 1 deletion packages/rum-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flashcatcloud/browser-rum-core",
"version": "0.2.0",
"version": "0.3.0",
"license": "Apache-2.0",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
Loading
Loading