Skip to content

♻️ Rewrite SessionManager with reactive strategy interface#4348

Draft
thomas-lebeau wants to merge 31 commits intov7from
thomas.lebeau/v7-new-sessionStore-api
Draft

♻️ Rewrite SessionManager with reactive strategy interface#4348
thomas-lebeau wants to merge 31 commits intov7from
thomas.lebeau/v7-new-sessionStore-api

Conversation

@thomas-lebeau
Copy link
Collaborator

Motivation

The existing SessionManager and SessionStore architecture has accumulated complexity through layered abstractions (sessionStore.ts, sessionStoreOperations.ts, product-specific wrappers). This makes it difficult to adopt modern browser APIs like the CookieStore API and Web Locks, and the synchronous mutex-based approach creates contention issues.

This PR rewrites the session management layer with a reactive strategy interface, preparing the foundation for async-first storage (CookieStore API) in v7.

Changes

  • Redefine SessionStoreStrategy interface — strategies now own their state and expose it reactively via an observable, replacing the synchronous read/write/mutex pattern
  • Rewrite all three storage strategies (cookie, localStorage, memory) to implement the new reactive interface
  • Cookie strategy uses Web Locks for cross-tab coordination and cookieObservable for change detection, replacing the polling-based mutex
  • Introduce CookieAccess abstraction to decouple cookie read/write from the document.cookie API, preparing for CookieStore API adoption
  • Merge SessionStore into SessionManager — remove sessionStore.ts and sessionStoreOperations.ts, consolidating session lifecycle logic into a single module
  • Make SessionManager initialization fully asyncstartSessionManager now returns Promise<SessionManager>, with setSessionState also async across all strategies
  • Remove product-specific session manager wrappers (rumSessionManager.ts, logsSessionManager.ts) — the generic SessionManager now handles all products
  • Remove TrackingType generic parameter from SessionManager — session tracking decisions are no longer encoded in the session manager type

Test instructions

yarn test:unit --spec packages/core/src/domain/session/sessionManager.spec.ts
yarn test:unit --spec packages/core/src/domain/session/storeStrategies/sessionInCookie.spec.ts
yarn test:unit --spec packages/core/src/domain/session/storeStrategies/sessionInLocalStorage.spec.ts
yarn test:unit --spec packages/core/src/domain/session/storeStrategies/sessionInMemory.spec.ts

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

@cit-pr-commenter-54b7da
Copy link

cit-pr-commenter-54b7da bot commented Mar 17, 2026

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 170.83 KiB 169.31 KiB -1.52 KiB -0.89%
Rum Profiler 6.18 KiB 6.18 KiB 0 B 0.00%
Rum Recorder 27.48 KiB 27.70 KiB +227 B +0.81%
Logs 56.45 KiB 54.53 KiB -1.91 KiB -3.39%
Rum Slim 128.07 KiB 126.68 KiB -1.39 KiB -1.09%
Worker 23.63 KiB 23.63 KiB 0 B 0.00%
🚀 CPU Performance

Pending...

🧠 Memory Performance

Pending...

🔗 RealWorld

@datadog-prod-us1-4
Copy link

datadog-prod-us1-4 bot commented Mar 17, 2026

⚠️ Tests

Fix all issues with BitsAI or with Cursor

⚠️ Warnings

🧪 10 Tests failed

startSessionManager initialization should fire onReady after initialization from Chrome 63.0.3239.84 (Windows 10) (Datadog) (Fix with Cursor)
Error: Expected spy onReady to have been called once. It was called 0 times.
    at <Jasmine>
    at UserContext.it (webpack:///packages/core/src/domain/session/sessionManager.spec.ts:149:26 <- /tmp/_karma_webpack_568744/commons.js:43707:32)
    at <anonymous>
microfrontend › Logs › expose handling stack for console.log from microfrontend.scenario.ts (Datadog) (Fix with Cursor)
microfrontend.scenario.ts:422:15 expose handling stack for console.log

[Firefox] › microfrontend.scenario.ts:422:15 › microfrontend › Logs › expose handling stack for console.log 

    Error: expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 6

      Object {
...
microfrontend › Logs › expose handling stack for DD_LOGS.logger.log from microfrontend.scenario.ts (Datadog) (Fix with Cursor)
microfrontend.scenario.ts:444:15 expose handling stack for DD_LOGS.logger.log

[Firefox] › microfrontend.scenario.ts:444:15 › microfrontend › Logs › expose handling stack for DD_LOGS.logger.log 

    Error: expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 6

      Object {
...
View all

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 83.12%
Overall Coverage: 77.62% (+0.33%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: d7aa09b | Docs | Datadog PR Page | Was this helpful? React with 👍/👎 or give us feedback!

@thomas-lebeau thomas-lebeau force-pushed the thomas.lebeau/v7-new-sessionStore-api branch 2 times, most recently from 918db7f to 741db9f Compare March 17, 2026 11:47
New interface: setSessionState(fn) + sessionObservable.
Removes isLockEnabled, persistSession, retrieveSession, expireSession.
- setSessionState(fn) reads global, applies fn, writes back, notifies
- Shared observable across SDK instances via global object
- Updated fake strategy for new interface
- setSessionState(fn) reads localStorage, applies fn, writes back
- Cross-tab detection via native storage event
- Observable with onFirstSubscribe for lazy event listener setup
- setSessionState(fn) uses Web Locks API for atomic read-modify-write
- Falls back to no-lock (last-write-wins) when Web Locks unavailable
- BroadcastChannel for cross-tab notification, polling as fallback
- Handles c=xxx cookie options encoding internally
- Cookie expiration respects trackAnonymousUser setting
Merge sessionStore and sessionManager into a single cohesive unit that
reacts to strategy emissions instead of polling. The new SessionManager
subscribes to strategy.sessionObservable for all state changes and uses
setSessionState(fn) for all writes, eliminating the old sessionStore
layer, sessionStoreOperations, and polling loop.
- configuration.ts imports selectSessionStoreStrategyType from sessionManager
- Remove STORAGE_POLL_DELAY export (now internal to cookie strategy)
These are replaced by the merged SessionManager and per-strategy
queuing (Web Locks for cookies).
Replace old cookie-based tests with comprehensive tests using the fake
session store strategy. Make getSessionStoreStrategy mockable so tests
can inject the fake strategy.
STORAGE_POLL_DELAY was removed from core exports as polling is now
internal to the cookie strategy.
- Fix import order in sessionInMemory.spec.ts
- Add void to navigator.locks.request promise in sessionInCookie.ts
- Disable Zone.js lint rule for localStorage storage event listener
- Fix type imports in sessionManager.spec.ts
- Remove unnecessary type assertion
The init call now also expands the session (creates ID + expire
timestamp) so it's immediately usable. Previously, initializeSession
only created an expired state, requiring user activity before the
session became active — breaking the async Web Locks flow where
onReady fired with an expired, ID-less session.
Move cookieObservable from rum-core to core and use it for cross-tab
session change detection instead of BroadcastChannel + polling fallback.
- Extract CookieAccess interface that unifies CookieStore API and document.cookie behind async get/getAll/set/delete methods
- Move CookieStoreWindow type to browser.types.ts alongside CookieStore
- Refactor sessionInCookie to use CookieAccess instead of raw cookie helpers
- Simplify queue processing: use Web Locks directly per call with promise chain fallback
- Update session cookie tests to be async (flush via Web Locks)
- Change SessionStoreStrategy interface to return Promise<void> from setSessionState
- Update cookie strategy to await navigator.locks and pendingChain instead of fire-and-forget
- Update localStorage and memory strategies to return Promise.resolve() for consistency
- Add void annotations at all fire-and-forget call sites in sessionManager
- Simplify cookie tests by awaiting setSessionState directly instead of using flushAsyncQueue
Replace the synchronous first-emission pattern (isFirstEmission flag +
sessionObservable subscription) with an async initialization flow that
awaits the initial setSessionState promise before subscribing to
subsequent changes. This ensures consent revocation during async cookie
lock acquisition is handled correctly.

- Remove `stop` property from SessionManager interface (already handled
  by stopSessionManager)
- Update collectAsyncCalls to preserve spy behavior when chaining
- Align all session manager tests and mocks with async initialization
- Apply expired state to in-memory tracking before async storage
  persist so events stop being collected immediately
- Prevents race where events could still be collected between
  expire() call and async setSessionState completion
- Consolidate get/getAll/set/delete into a single getAllAndSet(cb) method
  that reads all cookie values and writes atomically via a callback
- Adapt sessionInCookie to use the callback pattern, moving session string
  building and expire delay computation into the callback
- Remove now-unnecessary writeSessionState function and CookieAccessItem type usage
…nd cleanup

- Rename motifyCookieValieIfChanged → notifyCookieValueIfChanged (typo fix)
- Rewrite cookieAccess.spec.ts to cover getAllAndSet and observable behavior
- Add telemetrySessionContext.spec.ts for new extracted context function
- Move pendingChain to module scope with eslint-disable for side-effects rule
- Remove @only tag and stale TODO comment from microfrontend scenario
- Remove AssembleTelemetry hook tests from userContext, sessionContext (logs/rum-core), and defaultContext specs
- Remove unused DefaultTelemetryEventAttributes type imports and SKIPPED import from context source files
@thomas-lebeau thomas-lebeau force-pushed the thomas.lebeau/v7-new-sessionStore-api branch from e523edd to bfefbe6 Compare March 20, 2026 14:27
…d fix async test assertions

- Simplify normalizePersistenceList to default to cookie-only, removing the legacy allowFallbackToLocalStorage fallback and the unused initConfiguration parameter
- Add async/await with collectAsyncCalls in preStartRum and rumPublicApi specs where spy assertions now require awaiting async session initialization
- Replace inline mockSessionManager helper with shared createSessionManagerMock utility
- Use MOCK_SESSION_ID and setNotTracked() for consistent test patterns
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant