Session Timeout Handling
Long-lived single-page applications need to react when the user’s backend session is about to expire. The @yuuvis/client-framework library ships a turnkey solution for this: a SessionService that tracks user and HTTP activity, shows a warning before expiry, lets the user extend the session from a snackbar, and keeps every open browser tab in sync via BroadcastChannel.
The service is wired up by calling provideSession() in your application config. Once provided, it runs automatically — no component code is required for the standard flow. The service:
- Persists the session deadline in
AppCacheService(alocalStorage-backed cache from@yuuvis/client-core) so every open tab reads the same expiry value - Subscribes to
BackendService.httpCommunicationOccurred$(a stream emitted by the@yuuvis/client-coreHTTP wrapper on every backend call) and treats every HTTP call as activity - Listens to
mousemove,keydown,click, andscrollevents during a short window before expiry - Shows a Material snackbar one minute before expiry with an Extend session action
- Broadcasts
SessionExtendedandSessionLogoutmessages across tabs
provideSession() is a client-level concern. In yuuvis terminology, a shell client is the top-level Angular application that bootstraps the shell, while feature apps are Angular libraries loaded into that client as extensions at runtime. The session provider belongs in the shell client’s app.config.ts and must be registered exactly once per browser context.
Prerequisites
Section titled “Prerequisites”- A working yuuvis shell client project (see Quick Start)
@yuuvis/client-frameworkand@yuuvis/client-coreinstalled- Familiarity with Angular standalone application configuration (
app.config.ts)
Registering the Session Provider
Section titled “Registering the Session Provider”Add provideSession() to the providers array of your client’s ApplicationConfig:
import { ApplicationConfig } from '@angular/core';import { provideSession } from '@yuuvis/client-framework';
export const appConfig: ApplicationConfig = { providers: [ // ... other providers provideSession(30 * 60 * 1000) // 30 minutes ]};provideSession() registers SessionService and runs session.init() via provideAppInitializer at boot. init() starts the session, attaches the cross-tab listener, subscribes to HTTP activity, and installs the DOM event listeners.
Setting the Session Duration
Section titled “Setting the Session Duration”There are two ways to define how long a session lasts.
Option 1: Known duration at startup
Section titled “Option 1: Known duration at startup”If the duration is fixed and known before the app starts, pass it directly to provideSession():
providers: [ provideSession(45 * 60 * 1000) // 45 minutes]Option 2: Dynamic duration from the backend
Section titled “Option 2: Dynamic duration from the backend”If the duration is only known after authentication (e.g. returned in a login response), call provideSession() without arguments and update the duration later via SessionService.startSession():
providers: [ provideSession() // defaults to 30 minutes until startSession() is called]// After login in AuthService:login().subscribe(response => { sessionService.startSession(response.sessionExpiresIn); // Set actual duration});startSession(duration) overwrites the current duration, resets the expiry timestamp to Date.now() + duration, and restarts all timers.
How Session Extension Works
Section titled “How Session Extension Works”The service models a session as three consecutive phases before the configured expiry timestamp:
Time: T-start ───────────────► T-2min ──────────► T-1min ──────► T=0 │ │ │ │Phase: │ Idle phase │ Activity window │ Popup │ Expiry │ │ (2 minutes) │ (1 minute) │ │ │ │ │ │ Watches the clock │ Tracks mouse, │ Warning │ Auto- │ (HTTP still extends │ keys, clicks, │ snackbar │ logout │ the session) │ scroll │ with Extend │ actionIdle phase. No activity tracking. The service tracks the HTTP calls.
Activity window (default: 2 minutes before expiry). The service starts listening for DOM events and HTTP traffic. Any detected activity at the end of the window triggers an auto-extension and the cycle restarts. HTTP traffic is always tracked through BackendService.httpCommunicationOccurred$ (debounced 500 ms) — an HTTP request at any time triggers an extension, even outside the activity window.
Popup phase (default: 1 minute before expiry). If no activity was detected during the activity window, a Material snackbar appears warning the user that the session will expire. Clicking Extend session calls extendSession(); ignoring it leads to automatic logout when the timer reaches zero.
Cross-Tab Synchronization
Section titled “Cross-Tab Synchronization”The service uses two mechanisms to keep multiple tabs consistent:
- Shared expiry timestamp. The
expiresAtvalue is stored under thesession-expires-atkey inAppCacheService(backed bylocalStorage). Every tab independently reads this value and schedules its own timers — there is no master/slave tab. BroadcastChannelnotifications. When one tab extends or logs out, it postsSessionExtendedorSessionLogouton thesession_channelchannel. Other tabs receive the message and re-sync their timers (or perform logout) without making redundant backend calls.
Backend Extension
Section titled “Backend Extension”Calling extendSession() issues a GET /idm/whoami request to keep the backend session alive. The yuuvis backend treats any authenticated request as session activity, so whoami doubles as a lightweight keep-alive — there is no dedicated “extend session” endpoint. This call is skipped when:
- The extension was triggered by detected HTTP activity (the request that triggered it already counts as keep-alive)
- The extension was triggered by a
BroadcastChannelmessage from another tab
An internal guard prevents recursive whoami calls when the whoami response itself fires httpCommunicationOccurred$.
Configuration Constants
Section titled “Configuration Constants”The session timing constants are exported from @yuuvis/client-framework and act as a read-only reference for the defaults the service uses. You cannot override these at runtime — to change the activity window or popup lead time, the constants must be changed at the framework level. The session duration, by contrast, is set per application via provideSession() or startSession().
| Constant | Default | Purpose |
|---|---|---|
sessionDefaultDuration | 30 min (1 800 000 ms) | Fallback total session length when provideSession() is called without an argument and before startSession() runs. |
sessionActivityWindowBeforeEnd | 2 min (120 000 ms) | Lead time before expiry at which the activity window opens. Must be greater than sessionPopupBeforeEnd. |
sessionPopupBeforeEnd | 1 min (60 000 ms) | Lead time before expiry at which the warning popup appears if no activity was detected. |
SessionService API
Section titled “SessionService API”SessionService is provided in 'root' and can be injected anywhere. For the standard flow you only need two methods.
startSession(duration: number): void
Section titled “startSession(duration: number): void”Sets the session duration and (re)starts the lifecycle. Use this when the duration becomes known after startup — typically after a successful login.
sessionService.startSession(15 * 60 * 1000); // 15 minutesextendSession(broadcast?, expiresAt?, skipBackendCall?): void
Section titled “extendSession(broadcast?, expiresAt?, skipBackendCall?): void”Pushes the expiry forward and resets all timers. In normal use you do not need to call this — the service extends the session automatically based on activity, and the popup’s Extend session action calls it for you. Call it manually only when your app has a custom signal that should count as activity (e.g. a long-running operation).
| Parameter | Type | Default | Description |
|---|---|---|---|
broadcast | boolean | true | When true, broadcasts SessionExtended so other tabs sync. |
expiresAt | number | Date.now() + duration | Optional absolute expiry timestamp (ms). When omitted, the duration most recently set by provideSession() or startSession() is added to the current time. |
skipBackendCall | boolean | false | When true, skips the whoami backend call. |
Examples
Section titled “Examples”Fixed 30-minute session
Section titled “Fixed 30-minute session”The simplest setup: a static 30-minute window with all defaults.
import { ApplicationConfig } from '@angular/core';import { provideSession } from '@yuuvis/client-framework';
export const appConfig: ApplicationConfig = { providers: [ provideSession(30 * 60 * 1000) ]};Backend-driven duration after login
Section titled “Backend-driven duration after login”Defer duration setup until the backend reports it. The service uses the 30-minute default until startSession() is called.
providers: [ provideSession()]// After login in AuthService:login().subscribe(response => { sessionService.startSession(response.sessionExpiresIn); // Set actual duration});Manually extending after a custom event
Section titled “Manually extending after a custom event”Trigger an extension when your app finishes a long-running operation that does not produce HTTP traffic (e.g. a long client-side computation).
import { inject } from '@angular/core';import { SessionService } from '@yuuvis/client-framework';
export class ReportComponent { private session = inject(SessionService);
onLongReportFinished() { this.session.extendSession(); }}