@yuuvis/client-framework 2.10.3 → 2.11.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.
- package/autocomplete/lib/autocomplete.component.d.ts +4 -4
- package/autocomplete/lib/autocomplete.interface.d.ts +2 -2
- package/common/lib/services/index.d.ts +1 -0
- package/common/lib/services/layout-settings/layout-settings.service.d.ts +15 -0
- package/common/lib/services/theme/index.d.ts +3 -0
- package/common/lib/services/theme/theme.models.d.ts +16 -0
- package/common/lib/services/theme/theme.provider.d.ts +4 -0
- package/common/lib/services/theme/theme.service.d.ts +16 -0
- package/fesm2022/yuuvis-client-framework-common.mjs +213 -4
- package/fesm2022/yuuvis-client-framework-common.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-datepicker.mjs +1 -1
- package/fesm2022/yuuvis-client-framework-datepicker.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-forms.mjs +60 -37
- package/fesm2022/yuuvis-client-framework-forms.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-metadata-form-defaults.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-object-flavor.mjs +2 -2
- package/fesm2022/yuuvis-client-framework-object-flavor.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-object-relationship.mjs +135 -52
- package/fesm2022/yuuvis-client-framework-object-relationship.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-object-versions.mjs +4 -3
- package/fesm2022/yuuvis-client-framework-object-versions.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-query-list.mjs +5 -4
- package/fesm2022/yuuvis-client-framework-query-list.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-tile-list.mjs +20 -4
- package/fesm2022/yuuvis-client-framework-tile-list.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework.mjs +616 -98
- package/fesm2022/yuuvis-client-framework.mjs.map +1 -1
- package/forms/lib/elements/datetime/datetime.component.d.ts +0 -1
- package/forms/lib/elements/datetime-range/datetime-range.component.d.ts +6 -5
- package/forms/lib/elements/organization/organization.component.d.ts +1 -1
- package/forms/lib/elements/organization-set/organization-set.component.d.ts +1 -1
- package/index.d.ts +5 -2
- package/lib/config/index.d.ts +1 -0
- package/lib/config/session/index.d.ts +3 -0
- package/lib/config/session/session-activity-window-before-end.const.d.ts +43 -0
- package/lib/config/session/session-default-duration.const.d.ts +47 -0
- package/lib/config/session/session-popup-before-end.const.d.ts +47 -0
- package/lib/enums/channel-message.enum.d.ts +4 -0
- package/lib/enums/index.d.ts +1 -0
- package/lib/models/index.d.ts +2 -0
- package/lib/models/session/channel-payload.model.d.ts +5 -0
- package/lib/models/session/index.d.ts +1 -0
- package/lib/models/snack-bar/index.d.ts +3 -0
- package/lib/models/snack-bar/snack-bar-data.model.d.ts +6 -0
- package/lib/models/snack-bar/snack-bar-level.model.d.ts +1 -0
- package/lib/{services/snack-bar/snack-bar.interface.d.ts → models/snack-bar/snack-bar-options.model.d.ts} +1 -6
- package/lib/providers/index.d.ts +1 -0
- package/lib/providers/session/index.d.ts +1 -0
- package/lib/providers/session/provide-session.provider.d.ts +43 -0
- package/lib/services/index.d.ts +2 -2
- package/lib/services/session/session.service.d.ts +113 -0
- package/lib/services/snack-bar/snack-bar.service.d.ts +5 -5
- package/object-relationship/index.d.ts +1 -0
- package/object-relationship/lib/actions/add-relationship/add-relationship.component.d.ts +10 -0
- package/object-relationship/lib/actions/relationship-target-search/relationship-target-search.component.d.ts +17 -4
- package/object-relationship/lib/object-relationship.const.d.ts +0 -1
- package/object-versions/lib/object-versions.component.d.ts +1 -0
- package/package.json +8 -8
- package/query-list/lib/query-list.component.d.ts +8 -7
- package/tile-list/lib/tile-list/tile-list.component.d.ts +4 -2
- package/lib/assets/i18n/de.json +0 -202
- package/lib/assets/i18n/en.json +0 -202
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { inject, Injectable, signal, Component, makeEnvironmentProviders, provideAppInitializer, NgZone, NgModule } from '@angular/core';
|
|
3
|
+
import { finalize, timer, switchMap, map, debounceTime } from 'rxjs';
|
|
4
|
+
import { TranslateService } from '@ngx-translate/core';
|
|
4
5
|
import { MatSnackBar, MatSnackBarRef, MAT_SNACK_BAR_DATA, MatSnackBarLabel, MatSnackBarActions, MatSnackBarAction } from '@angular/material/snack-bar';
|
|
5
6
|
import * as i1 from '@angular/material/button';
|
|
6
7
|
import { MatButtonModule } from '@angular/material/button';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
10
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, imports: [CommonModule] }); }
|
|
11
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, imports: [CommonModule] }); }
|
|
12
|
-
}
|
|
13
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, decorators: [{
|
|
14
|
-
type: NgModule,
|
|
15
|
-
args: [{
|
|
16
|
-
imports: [CommonModule],
|
|
17
|
-
}]
|
|
18
|
-
}] });
|
|
8
|
+
import { AppCacheService, BackendService, UserService } from '@yuuvis/client-core';
|
|
9
|
+
import { CommonModule } from '@angular/common';
|
|
19
10
|
|
|
20
11
|
/**
|
|
21
12
|
* List of element selectors that should be excluded from halo focus
|
|
@@ -132,6 +123,152 @@ const haloFocusStyles = {
|
|
|
132
123
|
transform: 'translateZ(0)'
|
|
133
124
|
};
|
|
134
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Default session duration (in milliseconds) when no explicit duration is provided.
|
|
128
|
+
*
|
|
129
|
+
* This value is used as the fallback session lifetime when:
|
|
130
|
+
* - `provideSession()` is called without a duration parameter
|
|
131
|
+
* - Before `startSession(duration)` is called with a backend-provided value
|
|
132
|
+
*
|
|
133
|
+
* **Timeline:**
|
|
134
|
+
* ```
|
|
135
|
+
* [Session Start] ──────────────────────────────► [Session Expires]
|
|
136
|
+
* ← sessionDefaultDuration →
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* **Override Methods:**
|
|
140
|
+
* - **At startup (known duration)**: `provideSession(customDuration)`
|
|
141
|
+
* - **After login (dynamic duration)**: `sessionService.startSession(backendDuration)`
|
|
142
|
+
*
|
|
143
|
+
* **Usage Scenarios:**
|
|
144
|
+
*
|
|
145
|
+
* Scenario 1 - Fixed duration:
|
|
146
|
+
* ```ts
|
|
147
|
+
* // app.config.ts
|
|
148
|
+
* providers: [
|
|
149
|
+
* provideSession(45 * 60 * 1000) // Override to 45 minutes
|
|
150
|
+
* ]
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* Scenario 2 - Backend-provided duration:
|
|
154
|
+
* ```ts
|
|
155
|
+
* // app.config.ts
|
|
156
|
+
* providers: [
|
|
157
|
+
* provideSession() // Uses default 30 minutes initially
|
|
158
|
+
* ]
|
|
159
|
+
*
|
|
160
|
+
* // auth.service.ts (after login)
|
|
161
|
+
* login().subscribe(response => {
|
|
162
|
+
* sessionService.startSession(response.sessionExpiresIn); // Update to backend value
|
|
163
|
+
* });
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* **Related Constants:**
|
|
167
|
+
* - {@link sessionActivityWindowBeforeEnd} - When to start tracking user activity
|
|
168
|
+
* - {@link sessionPopupBeforeEnd} - When to show the expiry warning popup
|
|
169
|
+
*
|
|
170
|
+
* @default 30 minutes (1800000 milliseconds)
|
|
171
|
+
*/
|
|
172
|
+
const sessionDefaultDuration = 30 * 60 * 1000; // 30 minutes
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Time window (in milliseconds) before session expiry when user activity triggers auto-extension.
|
|
176
|
+
*
|
|
177
|
+
* During this window, the service tracks user interactions (mouse, keyboard, scroll) and HTTP activity.
|
|
178
|
+
* If any activity is detected, the session is automatically extended, preventing the expiry warning popup.
|
|
179
|
+
*
|
|
180
|
+
* **Session Timeline:**
|
|
181
|
+
* ```
|
|
182
|
+
* [Session Start] ──────────────────────────────► [Session Expires]
|
|
183
|
+
* ▲ ▲
|
|
184
|
+
* │ └─ sessionPopupBeforeEnd (1 min)
|
|
185
|
+
* └─ Activity window starts (2 min)
|
|
186
|
+
*
|
|
187
|
+
* ┌─────────────────────────────────────────────┐
|
|
188
|
+
* │ Activity Window (2 minutes) │
|
|
189
|
+
* │ • Tracks: mousemove, keydown, click, scroll │
|
|
190
|
+
* │ • Tracks: HTTP requests (debounced) │
|
|
191
|
+
* │ • If activity detected → auto-extend │
|
|
192
|
+
* │ • If no activity → show warning popup │
|
|
193
|
+
* └─────────────────────────────────────────────┘
|
|
194
|
+
* ```
|
|
195
|
+
*
|
|
196
|
+
* **Why This Matters:**
|
|
197
|
+
* - Prevents unnecessary interruptions for active users
|
|
198
|
+
* - Reduces server-side session renewal traffic by batching extensions
|
|
199
|
+
* - Provides a grace period before showing the expiry warning
|
|
200
|
+
*
|
|
201
|
+
* **Tracked Activities:**
|
|
202
|
+
* - **User interactions**: `mousemove`, `keydown`, `click`, `scroll` on `window`
|
|
203
|
+
* - **HTTP traffic**: Any request via `BackendService.httpCommunicationOccurred$` (debounced 500ms)
|
|
204
|
+
*
|
|
205
|
+
* **Interaction with Other Constants:**
|
|
206
|
+
* - Must be **greater than** {@link sessionPopupBeforeEnd} to allow the activity window before the popup
|
|
207
|
+
* - Typical relationship: `activityWindow > popupWarning` (e.g., 2 min > 1 min)
|
|
208
|
+
*
|
|
209
|
+
* **Performance Note:**
|
|
210
|
+
* Activity tracking is only active during this window, not throughout the entire session,
|
|
211
|
+
* minimizing performance impact of event listeners.
|
|
212
|
+
*
|
|
213
|
+
* @default 2 minutes (120000 milliseconds)
|
|
214
|
+
* @see {@link SessionService.setupUserActivityTracking} for implementation details
|
|
215
|
+
*/
|
|
216
|
+
const sessionActivityWindowBeforeEnd = 2 * 60 * 1000; // 2 minutes
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Time (in milliseconds) before session expiry when the warning popup is displayed.
|
|
220
|
+
*
|
|
221
|
+
* When this threshold is reached without detected user activity during the activity window,
|
|
222
|
+
* a snackbar notification appears, warning the user that their session will expire soon
|
|
223
|
+
* and providing an "Extend session" action button.
|
|
224
|
+
*
|
|
225
|
+
* **Session Timeline:**
|
|
226
|
+
* ```
|
|
227
|
+
* [Session Start] ──────────────────────────────► [Session Expires]
|
|
228
|
+
* ▲ ▲
|
|
229
|
+
* │ └─ Popup appears (1 min before expiry)
|
|
230
|
+
* └─ Activity window starts (2 min before expiry)
|
|
231
|
+
*
|
|
232
|
+
* User has no activity during the 2-minute window:
|
|
233
|
+
* ├─ 2 min before: Activity tracking starts
|
|
234
|
+
* ├─ 1 min before: ⚠️ Popup shows "Session expires in one minute"
|
|
235
|
+
* └─ 0 min: Auto-logout if no action taken
|
|
236
|
+
* ```
|
|
237
|
+
*
|
|
238
|
+
* **Popup Behavior:**
|
|
239
|
+
* - Displays a warning message: "Session expires in one minute"
|
|
240
|
+
* - Provides an action button: "Extend session"
|
|
241
|
+
* - Clicking "Extend session" → extends session by the full session duration
|
|
242
|
+
* - Ignoring the popup → automatic logout when timer reaches zero
|
|
243
|
+
* - Only one popup is shown at a time (subsequent extensions dismiss the existing popup)
|
|
244
|
+
*
|
|
245
|
+
* **Cross-Tab Synchronization:**
|
|
246
|
+
* If the user extends the session in one browser tab, all other tabs:
|
|
247
|
+
* - Automatically dismiss their popups (if shown)
|
|
248
|
+
* - Reset their timers to the new expiry time
|
|
249
|
+
* - Avoid showing redundant warnings
|
|
250
|
+
*
|
|
251
|
+
* **Interaction with Other Constants:**
|
|
252
|
+
* - Must be **less than** {@link sessionActivityWindowBeforeEnd}
|
|
253
|
+
* - Typical relationship: `activityWindow > popupWarning` (e.g., 2 min > 1 min)
|
|
254
|
+
* - The gap between them determines how long the activity window runs before the popup
|
|
255
|
+
*
|
|
256
|
+
* **UX Considerations:**
|
|
257
|
+
* - **1 minute** gives users enough time to react without being too intrusive
|
|
258
|
+
* - Too short (e.g., 10 seconds): Users may not have time to respond
|
|
259
|
+
* - Too long (e.g., 5 minutes): Users may be unnecessarily interrupted if they return
|
|
260
|
+
*
|
|
261
|
+
* @default 1 minute (60000 milliseconds)
|
|
262
|
+
* @see {@link SessionService.showPopup} for popup implementation
|
|
263
|
+
*/
|
|
264
|
+
const sessionPopupBeforeEnd = 60 * 1000; // 1 minute
|
|
265
|
+
|
|
266
|
+
var ChannelMessage;
|
|
267
|
+
(function (ChannelMessage) {
|
|
268
|
+
ChannelMessage["SessionExtended"] = "SessionExtended";
|
|
269
|
+
ChannelMessage["SessionLogout"] = "SessionLogout";
|
|
270
|
+
})(ChannelMessage || (ChannelMessage = {}));
|
|
271
|
+
|
|
135
272
|
/**
|
|
136
273
|
* Service that creates and manages a visual "halo" border around keyboard-focused elements
|
|
137
274
|
* to enhance navigation visibility and improve accessibility.
|
|
@@ -374,89 +511,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
|
|
|
374
511
|
}]
|
|
375
512
|
}] });
|
|
376
513
|
|
|
377
|
-
class SnackBarService {
|
|
378
|
-
#snackBar = inject(MatSnackBar);
|
|
379
|
-
info(message, action) {
|
|
380
|
-
return this.snack(message, { action, level: 'info' });
|
|
381
|
-
}
|
|
382
|
-
success(message, action) {
|
|
383
|
-
return this.snack(message, { action, level: 'success' });
|
|
384
|
-
}
|
|
385
|
-
warning(message, action) {
|
|
386
|
-
return this.snack(message, { action, level: 'warning' });
|
|
387
|
-
}
|
|
388
|
-
danger(message, action) {
|
|
389
|
-
return this.snack(message, { action, level: 'danger' });
|
|
390
|
-
}
|
|
391
|
-
snack(message, options) {
|
|
392
|
-
return this.#snackBar.openFromComponent(SnackBarComponent, {
|
|
393
|
-
data: {
|
|
394
|
-
level: options.level,
|
|
395
|
-
message,
|
|
396
|
-
action: options.action
|
|
397
|
-
},
|
|
398
|
-
duration: options.duration || 3000,
|
|
399
|
-
horizontalPosition: options.horizontalPosition,
|
|
400
|
-
verticalPosition: options.verticalPosition,
|
|
401
|
-
panelClass: ['yuv-snack-bar', 'level-' + options.level]
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
405
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarService, providedIn: 'root' }); }
|
|
406
|
-
}
|
|
407
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarService, decorators: [{
|
|
408
|
-
type: Injectable,
|
|
409
|
-
args: [{
|
|
410
|
-
providedIn: 'root'
|
|
411
|
-
}]
|
|
412
|
-
}] });
|
|
413
|
-
class SnackBarComponent {
|
|
414
|
-
constructor() {
|
|
415
|
-
this.#snackBarRef = inject(MatSnackBarRef);
|
|
416
|
-
this.#snackData = inject(MAT_SNACK_BAR_DATA);
|
|
417
|
-
this.level = signal(this.#snackData.level);
|
|
418
|
-
this.message = signal(this.#snackData.message);
|
|
419
|
-
this.action = signal(this.#snackData.action);
|
|
420
|
-
}
|
|
421
|
-
#snackBarRef;
|
|
422
|
-
#snackData;
|
|
423
|
-
dismiss(withAction = false) {
|
|
424
|
-
if (withAction) {
|
|
425
|
-
this.#snackBarRef.dismissWithAction();
|
|
426
|
-
}
|
|
427
|
-
else if (!this.action()) {
|
|
428
|
-
this.#snackBarRef.dismiss();
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
432
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: SnackBarComponent, isStandalone: true, selector: "yuv-snack-bar-component", host: { properties: { "class.info": "level() === 'info'", "class.success": "level() === 'success'", "class.warning": "level() === 'warning'", "class.danger": "level() === 'danger'" } }, ngImport: i0, template: `
|
|
433
|
-
<span matSnackBarLabel (click)="dismiss()"> {{ message() }} </span>
|
|
434
|
-
@let a = action();
|
|
435
|
-
@if (a) {
|
|
436
|
-
<span matSnackBarActions>
|
|
437
|
-
<button mat-button matSnackBarAction (click)="dismiss(!!a)">{{ action() }}</button>
|
|
438
|
-
</span>
|
|
439
|
-
}
|
|
440
|
-
`, isInline: true, styles: [":host{display:flex}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "directive", type: MatSnackBarLabel, selector: "[matSnackBarLabel]" }, { kind: "directive", type: MatSnackBarActions, selector: "[matSnackBarActions]" }, { kind: "directive", type: MatSnackBarAction, selector: "[matSnackBarAction]" }] }); }
|
|
441
|
-
}
|
|
442
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarComponent, decorators: [{
|
|
443
|
-
type: Component,
|
|
444
|
-
args: [{ selector: 'yuv-snack-bar-component', template: `
|
|
445
|
-
<span matSnackBarLabel (click)="dismiss()"> {{ message() }} </span>
|
|
446
|
-
@let a = action();
|
|
447
|
-
@if (a) {
|
|
448
|
-
<span matSnackBarActions>
|
|
449
|
-
<button mat-button matSnackBarAction (click)="dismiss(!!a)">{{ action() }}</button>
|
|
450
|
-
</span>
|
|
451
|
-
}
|
|
452
|
-
`, imports: [MatButtonModule, MatSnackBarLabel, MatSnackBarActions, MatSnackBarAction], host: {
|
|
453
|
-
'[class.info]': "level() === 'info'",
|
|
454
|
-
'[class.success]': "level() === 'success'",
|
|
455
|
-
'[class.warning]': "level() === 'warning'",
|
|
456
|
-
'[class.danger]': "level() === 'danger'"
|
|
457
|
-
}, styles: [":host{display:flex}\n"] }]
|
|
458
|
-
}] });
|
|
459
|
-
|
|
460
514
|
/**
|
|
461
515
|
* Utility service providing helper methods for the Halo Focus feature.
|
|
462
516
|
*
|
|
@@ -854,6 +908,408 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
|
|
|
854
908
|
type: Injectable
|
|
855
909
|
}] });
|
|
856
910
|
|
|
911
|
+
class SnackBarService {
|
|
912
|
+
#snackBar = inject(MatSnackBar);
|
|
913
|
+
info(message, action, duration) {
|
|
914
|
+
return this.snack(message, { action, level: 'info', ...(duration ? { duration } : {}) });
|
|
915
|
+
}
|
|
916
|
+
success(message, action, duration) {
|
|
917
|
+
return this.snack(message, { action, level: 'success', ...(duration ? { duration } : {}) });
|
|
918
|
+
}
|
|
919
|
+
warning(message, action, duration) {
|
|
920
|
+
return this.snack(message, { action, level: 'warning', ...(duration ? { duration } : {}) });
|
|
921
|
+
}
|
|
922
|
+
danger(message, action, duration) {
|
|
923
|
+
return this.snack(message, { action, level: 'danger', ...(duration ? { duration } : {}) });
|
|
924
|
+
}
|
|
925
|
+
snack(message, options) {
|
|
926
|
+
return this.#snackBar.openFromComponent(SnackBarComponent, {
|
|
927
|
+
data: {
|
|
928
|
+
level: options.level,
|
|
929
|
+
message,
|
|
930
|
+
action: options.action
|
|
931
|
+
},
|
|
932
|
+
...(options.duration ? { duration: options.duration } : {}),
|
|
933
|
+
horizontalPosition: options.horizontalPosition,
|
|
934
|
+
verticalPosition: options.verticalPosition,
|
|
935
|
+
panelClass: ['yuv-snack-bar', 'level-' + options.level]
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
939
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarService, providedIn: 'root' }); }
|
|
940
|
+
}
|
|
941
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarService, decorators: [{
|
|
942
|
+
type: Injectable,
|
|
943
|
+
args: [{
|
|
944
|
+
providedIn: 'root'
|
|
945
|
+
}]
|
|
946
|
+
}] });
|
|
947
|
+
class SnackBarComponent {
|
|
948
|
+
constructor() {
|
|
949
|
+
this.#snackBarRef = inject(MatSnackBarRef);
|
|
950
|
+
this.#snackData = inject(MAT_SNACK_BAR_DATA);
|
|
951
|
+
this.level = signal(this.#snackData.level);
|
|
952
|
+
this.message = signal(this.#snackData.message);
|
|
953
|
+
this.action = signal(this.#snackData.action);
|
|
954
|
+
}
|
|
955
|
+
#snackBarRef;
|
|
956
|
+
#snackData;
|
|
957
|
+
dismiss(withAction = false) {
|
|
958
|
+
if (withAction) {
|
|
959
|
+
this.#snackBarRef.dismissWithAction();
|
|
960
|
+
}
|
|
961
|
+
else if (!this.action()) {
|
|
962
|
+
this.#snackBarRef.dismiss();
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
966
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: SnackBarComponent, isStandalone: true, selector: "yuv-snack-bar-component", host: { properties: { "class.info": "level() === 'info'", "class.success": "level() === 'success'", "class.warning": "level() === 'warning'", "class.danger": "level() === 'danger'" } }, ngImport: i0, template: `
|
|
967
|
+
<span matSnackBarLabel (click)="dismiss()"> {{ message() }} </span>
|
|
968
|
+
@let a = action();
|
|
969
|
+
@if (a) {
|
|
970
|
+
<span matSnackBarActions>
|
|
971
|
+
<button mat-button matSnackBarAction (click)="dismiss(!!a)">{{ action() }}</button>
|
|
972
|
+
</span>
|
|
973
|
+
}
|
|
974
|
+
`, isInline: true, styles: [":host{display:flex}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "directive", type: MatSnackBarLabel, selector: "[matSnackBarLabel]" }, { kind: "directive", type: MatSnackBarActions, selector: "[matSnackBarActions]" }, { kind: "directive", type: MatSnackBarAction, selector: "[matSnackBarAction]" }] }); }
|
|
975
|
+
}
|
|
976
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SnackBarComponent, decorators: [{
|
|
977
|
+
type: Component,
|
|
978
|
+
args: [{ selector: 'yuv-snack-bar-component', template: `
|
|
979
|
+
<span matSnackBarLabel (click)="dismiss()"> {{ message() }} </span>
|
|
980
|
+
@let a = action();
|
|
981
|
+
@if (a) {
|
|
982
|
+
<span matSnackBarActions>
|
|
983
|
+
<button mat-button matSnackBarAction (click)="dismiss(!!a)">{{ action() }}</button>
|
|
984
|
+
</span>
|
|
985
|
+
}
|
|
986
|
+
`, imports: [MatButtonModule, MatSnackBarLabel, MatSnackBarActions, MatSnackBarAction], host: {
|
|
987
|
+
'[class.info]': "level() === 'info'",
|
|
988
|
+
'[class.success]': "level() === 'success'",
|
|
989
|
+
'[class.warning]': "level() === 'warning'",
|
|
990
|
+
'[class.danger]': "level() === 'danger'"
|
|
991
|
+
}, styles: [":host{display:flex}\n"] }]
|
|
992
|
+
}] });
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Manages client-side session expiry: persists expiration, tracks user and HTTP activity,
|
|
996
|
+
* shows a pre-expiry popup with an extend CTA, and syncs state across tabs via BroadcastChannel.
|
|
997
|
+
*
|
|
998
|
+
* Key behaviors
|
|
999
|
+
* - Persists `expiresAt` in AppCacheService so multiple tabs share the same deadline
|
|
1000
|
+
* - Automatically listens to BackendService HTTP activity (debounced) to extend sessions
|
|
1001
|
+
* - Extends backend session by calling `/api-web/api/idm/whoami` endpoint (skipped when triggered by HTTP activity)
|
|
1002
|
+
* - User activity (mouse, keyboard, scroll) inside a defined window will auto-extend the session
|
|
1003
|
+
* - Displays a snack popup shortly before expiry; user can extend from the popup
|
|
1004
|
+
* - Broadcasts `SessionExtended` / `SessionLogout` to keep tabs in sync
|
|
1005
|
+
*
|
|
1006
|
+
* Setup options
|
|
1007
|
+
*
|
|
1008
|
+
* Option 1: Known session duration at startup
|
|
1009
|
+
* Use when the session duration is known in advance and remains constant.
|
|
1010
|
+
* ```ts
|
|
1011
|
+
* // app.config.ts
|
|
1012
|
+
* export const appConfig: ApplicationConfig = {
|
|
1013
|
+
* providers: [
|
|
1014
|
+
* provideSession(30 * 60 * 1000), // 30 minutes - set at app startup
|
|
1015
|
+
* ]
|
|
1016
|
+
* };
|
|
1017
|
+
* ```
|
|
1018
|
+
*
|
|
1019
|
+
* Option 2: Dynamic session duration from backend
|
|
1020
|
+
* Use when the session duration is determined after login (e.g., from backend response).
|
|
1021
|
+
* ```ts
|
|
1022
|
+
* // app.config.ts
|
|
1023
|
+
* export const appConfig: ApplicationConfig = {
|
|
1024
|
+
* providers: [
|
|
1025
|
+
* provideSession(), // Initialize without duration (defaults to 30 minutes)
|
|
1026
|
+
* ]
|
|
1027
|
+
* };
|
|
1028
|
+
*
|
|
1029
|
+
* // After login in AuthService:
|
|
1030
|
+
* login().subscribe(response => {
|
|
1031
|
+
* const sessionDuration = response.sessionExpiresIn; // from backend
|
|
1032
|
+
* sessionService.startSession(sessionDuration);
|
|
1033
|
+
* });
|
|
1034
|
+
* ```
|
|
1035
|
+
* Note: If no duration is provided to provideSession(), a default of 30 minutes is used until startSession() is called.
|
|
1036
|
+
*
|
|
1037
|
+
* Lifecycle notes
|
|
1038
|
+
* - HTTP activity is automatically tracked via BackendService.httpCommunicationOccurred$
|
|
1039
|
+
* - Backend session is extended via whoami endpoint, except when triggered by HTTP activity or cross-tab sync
|
|
1040
|
+
* - Internal flag prevents recursive whoami calls when the endpoint itself triggers httpCommunicationOccurred$
|
|
1041
|
+
* - Warning and logout timers are reset on every extend
|
|
1042
|
+
* - All timers and the popup are cleared on logout
|
|
1043
|
+
* - Broadcast messages are listened for and mirrored across tabs
|
|
1044
|
+
* - For UI activity, this service listens to DOM events (mousemove, keydown, click, scroll)
|
|
1045
|
+
*/
|
|
1046
|
+
class SessionService {
|
|
1047
|
+
constructor() {
|
|
1048
|
+
//#region Dependencies
|
|
1049
|
+
/**
|
|
1050
|
+
* AppCacheService is used to persist the session expiry timestamp in localStorage.
|
|
1051
|
+
*
|
|
1052
|
+
* Why persist to storage instead of runtime memory:
|
|
1053
|
+
* - Single source of truth: All browser tabs can read the same expiry value directly
|
|
1054
|
+
* from storage without inter-tab communication
|
|
1055
|
+
* - Avoids master/slave tab pattern: No need to elect one "master" tab to hold the
|
|
1056
|
+
* authoritative expiry time and sync it to others
|
|
1057
|
+
* - Survives page refresh: Users don't lose their session when refreshing the page
|
|
1058
|
+
* - Simplifies synchronization: BroadcastChannel is only used for notifications about
|
|
1059
|
+
* changes (extend/logout), not for maintaining shared state
|
|
1060
|
+
* - Consistent behavior: Every tab independently calculates timers based on the same
|
|
1061
|
+
* persisted expiry value, ensuring uniform warning/logout timing
|
|
1062
|
+
*/
|
|
1063
|
+
this.#appCacheService = inject(AppCacheService);
|
|
1064
|
+
this.#snackBarService = inject(SnackBarService);
|
|
1065
|
+
this.#backendService = inject(BackendService);
|
|
1066
|
+
this.#userService = inject(UserService);
|
|
1067
|
+
this.translate = inject(TranslateService);
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region Properties
|
|
1070
|
+
this.#sessionDuration = sessionDefaultDuration;
|
|
1071
|
+
this.#sessionStorageKey = 'session-expires-at';
|
|
1072
|
+
this.#activityWindowBeforeEnd = sessionActivityWindowBeforeEnd;
|
|
1073
|
+
this.#popupBeforeEnd = sessionPopupBeforeEnd;
|
|
1074
|
+
this.#activityDetectedInWindow = false;
|
|
1075
|
+
this.#trackingWindowActivity = false;
|
|
1076
|
+
this.#userActivityTrackingInitialized = false;
|
|
1077
|
+
this.#extendingSessionViaBackend = false;
|
|
1078
|
+
this.#sessionChannel = new BroadcastChannel('session_channel');
|
|
1079
|
+
}
|
|
1080
|
+
//#region Dependencies
|
|
1081
|
+
/**
|
|
1082
|
+
* AppCacheService is used to persist the session expiry timestamp in localStorage.
|
|
1083
|
+
*
|
|
1084
|
+
* Why persist to storage instead of runtime memory:
|
|
1085
|
+
* - Single source of truth: All browser tabs can read the same expiry value directly
|
|
1086
|
+
* from storage without inter-tab communication
|
|
1087
|
+
* - Avoids master/slave tab pattern: No need to elect one "master" tab to hold the
|
|
1088
|
+
* authoritative expiry time and sync it to others
|
|
1089
|
+
* - Survives page refresh: Users don't lose their session when refreshing the page
|
|
1090
|
+
* - Simplifies synchronization: BroadcastChannel is only used for notifications about
|
|
1091
|
+
* changes (extend/logout), not for maintaining shared state
|
|
1092
|
+
* - Consistent behavior: Every tab independently calculates timers based on the same
|
|
1093
|
+
* persisted expiry value, ensuring uniform warning/logout timing
|
|
1094
|
+
*/
|
|
1095
|
+
#appCacheService;
|
|
1096
|
+
#snackBarService;
|
|
1097
|
+
#backendService;
|
|
1098
|
+
#userService;
|
|
1099
|
+
//#endregion
|
|
1100
|
+
//#region Properties
|
|
1101
|
+
#sessionDuration;
|
|
1102
|
+
#sessionStorageKey;
|
|
1103
|
+
#activityWindowBeforeEnd;
|
|
1104
|
+
#popupBeforeEnd;
|
|
1105
|
+
#activityWindowStart$;
|
|
1106
|
+
#activityWindowEnd$;
|
|
1107
|
+
#logoutTimer$;
|
|
1108
|
+
#httpActivitySubscription$;
|
|
1109
|
+
#activityDetectedInWindow;
|
|
1110
|
+
#trackingWindowActivity;
|
|
1111
|
+
#userActivityTrackingInitialized;
|
|
1112
|
+
#extendingSessionViaBackend;
|
|
1113
|
+
#sessionChannel;
|
|
1114
|
+
#snackReference;
|
|
1115
|
+
//#endregion
|
|
1116
|
+
//#region Public methods
|
|
1117
|
+
/**
|
|
1118
|
+
* Initializes cross-tab listeners and activity hooks.
|
|
1119
|
+
*
|
|
1120
|
+
* IMPORTANT: This is automatically called by `provideSession()` via APP_INITIALIZER.
|
|
1121
|
+
* You should NOT call this manually - just add `provideSession()` to your app providers.
|
|
1122
|
+
*
|
|
1123
|
+
* What it does:
|
|
1124
|
+
* - Initializes session with provided or default duration value
|
|
1125
|
+
* - Wires BroadcastChannel subscriptions for cross-tab sync
|
|
1126
|
+
* - Subscribes to BackendService.httpCommunicationOccurred$ for automatic HTTP activity tracking
|
|
1127
|
+
* - Attaches DOM event listeners (mousemove, keydown, click, scroll) for the activity window
|
|
1128
|
+
*/
|
|
1129
|
+
init(sessionDuration) {
|
|
1130
|
+
this.startSession(sessionDuration ?? sessionDefaultDuration);
|
|
1131
|
+
this.listenToChannel();
|
|
1132
|
+
this.setupHttpDebounce();
|
|
1133
|
+
this.setupUserActivityTracking();
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Sets the session duration and starts the session lifecycle.
|
|
1137
|
+
*
|
|
1138
|
+
* Use this method when you need to set or update the session duration dynamically,
|
|
1139
|
+
* typically after receiving authentication/session information from the backend.
|
|
1140
|
+
*
|
|
1141
|
+
* This is the correct approach when:
|
|
1142
|
+
* - Session duration is not known at application startup
|
|
1143
|
+
* - Duration comes from a backend API response after login
|
|
1144
|
+
* - You need to manually override the duration set via provideSession()
|
|
1145
|
+
*
|
|
1146
|
+
* Example:
|
|
1147
|
+
* ```ts
|
|
1148
|
+
* // After login in AuthService
|
|
1149
|
+
* login().subscribe(response => {
|
|
1150
|
+
* this.sessionService.startSession(response.sessionExpiresIn);
|
|
1151
|
+
* });
|
|
1152
|
+
* ```
|
|
1153
|
+
*
|
|
1154
|
+
* @param duration Total session length in milliseconds
|
|
1155
|
+
*/
|
|
1156
|
+
startSession(duration) {
|
|
1157
|
+
this.#sessionDuration = duration;
|
|
1158
|
+
this.extendSession(false, undefined, true);
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Extends the session expiry, resets all timers, and optionally broadcasts to other tabs.
|
|
1162
|
+
*
|
|
1163
|
+
* @param broadcast When true (default), posts a BroadcastChannel message so other tabs sync
|
|
1164
|
+
* @param expiresAt Optional custom expiry timestamp; if not provided, calculates based on session duration
|
|
1165
|
+
* @param skipBackendCall When true, skips the whoami backend call (used when already triggered by HTTP activity)
|
|
1166
|
+
*/
|
|
1167
|
+
extendSession(broadcast = true, expiresAt, skipBackendCall = false) {
|
|
1168
|
+
const newExpiresAt = expiresAt ?? Date.now() + this.#sessionDuration;
|
|
1169
|
+
this.setExpiresAt(newExpiresAt);
|
|
1170
|
+
this.resetAllTimers();
|
|
1171
|
+
if (!skipBackendCall && !this.#extendingSessionViaBackend) {
|
|
1172
|
+
this.#extendingSessionViaBackend = true;
|
|
1173
|
+
this.#backendService
|
|
1174
|
+
.get('/idm/whoami')
|
|
1175
|
+
.pipe(finalize(() => {
|
|
1176
|
+
// Keep flag true longer than debounce time to prevent duplicate broadcasts
|
|
1177
|
+
// from httpCommunicationOccurred$ triggered by this whoami call
|
|
1178
|
+
setTimeout(() => {
|
|
1179
|
+
this.#extendingSessionViaBackend = false;
|
|
1180
|
+
}, 600);
|
|
1181
|
+
if (broadcast) {
|
|
1182
|
+
this.#sessionChannel.postMessage({
|
|
1183
|
+
type: ChannelMessage.SessionExtended,
|
|
1184
|
+
expiresAt: newExpiresAt
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
}))
|
|
1188
|
+
.subscribe();
|
|
1189
|
+
}
|
|
1190
|
+
else if (broadcast) {
|
|
1191
|
+
this.#sessionChannel.postMessage({
|
|
1192
|
+
type: ChannelMessage.SessionExtended,
|
|
1193
|
+
expiresAt: newExpiresAt
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
//#endregion
|
|
1198
|
+
//#region Core Logic
|
|
1199
|
+
listenToChannel() {
|
|
1200
|
+
this.#sessionChannel.onmessage = (event) => {
|
|
1201
|
+
const message = event.data;
|
|
1202
|
+
switch (message.type) {
|
|
1203
|
+
case ChannelMessage.SessionExtended:
|
|
1204
|
+
this.extendSession(false, message.expiresAt, true);
|
|
1205
|
+
break;
|
|
1206
|
+
case ChannelMessage.SessionLogout:
|
|
1207
|
+
this.performLogout(false);
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
scheduleActivityWindow() {
|
|
1213
|
+
this.getExpiresAt().subscribe((expiresAt) => {
|
|
1214
|
+
const activityWindowStartIn = expiresAt - Date.now() - this.#activityWindowBeforeEnd;
|
|
1215
|
+
const activityWindowEndIn = expiresAt - Date.now() - this.#popupBeforeEnd;
|
|
1216
|
+
this.#activityWindowStart$ = timer(Math.max(activityWindowStartIn, 0)).subscribe(() => {
|
|
1217
|
+
this.#trackingWindowActivity = true;
|
|
1218
|
+
this.#activityDetectedInWindow = false;
|
|
1219
|
+
});
|
|
1220
|
+
this.#activityWindowEnd$ = timer(Math.max(activityWindowEndIn, 0)).subscribe(() => {
|
|
1221
|
+
this.#trackingWindowActivity = false;
|
|
1222
|
+
if (this.#activityDetectedInWindow) {
|
|
1223
|
+
this.extendSession();
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
this.showPopup();
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
showPopup() {
|
|
1232
|
+
if (this.#snackReference)
|
|
1233
|
+
return;
|
|
1234
|
+
const message = this.translate.instant('yuv.session.expires.message');
|
|
1235
|
+
const action = this.translate.instant('yuv.session.extend.action');
|
|
1236
|
+
this.#snackReference = this.#snackBarService.warning(message, action);
|
|
1237
|
+
this.scheduleLogout();
|
|
1238
|
+
this.#snackReference.onAction().subscribe(() => {
|
|
1239
|
+
this.#snackReference?.dismiss();
|
|
1240
|
+
this.extendSession();
|
|
1241
|
+
});
|
|
1242
|
+
this.#snackReference.afterDismissed().subscribe(() => {
|
|
1243
|
+
this.#snackReference = undefined;
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
scheduleLogout() {
|
|
1247
|
+
this.#logoutTimer$?.unsubscribe();
|
|
1248
|
+
this.#logoutTimer$ = this.getExpiresAt()
|
|
1249
|
+
.pipe(switchMap((expiresAt) => timer(Math.max(expiresAt - Date.now(), 0)).pipe(map(() => expiresAt))))
|
|
1250
|
+
.subscribe((expiresAt) => {
|
|
1251
|
+
if (Date.now() >= expiresAt) {
|
|
1252
|
+
this.performLogout(true);
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
performLogout(broadcast = true) {
|
|
1257
|
+
this.clearTimers();
|
|
1258
|
+
this.#snackReference?.dismiss();
|
|
1259
|
+
if (broadcast) {
|
|
1260
|
+
this.#sessionChannel.postMessage({
|
|
1261
|
+
type: ChannelMessage.SessionLogout
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
this.#userService.logout();
|
|
1265
|
+
}
|
|
1266
|
+
//#endregion
|
|
1267
|
+
//#region Activity + HTTP
|
|
1268
|
+
setupHttpDebounce() {
|
|
1269
|
+
this.#httpActivitySubscription$?.unsubscribe();
|
|
1270
|
+
this.#httpActivitySubscription$ = this.#backendService.httpCommunicationOccurred$.pipe(debounceTime(500)).subscribe(() => {
|
|
1271
|
+
if (!this.#extendingSessionViaBackend) {
|
|
1272
|
+
this.extendSession(true, undefined, true);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
setupUserActivityTracking() {
|
|
1277
|
+
if (this.#userActivityTrackingInitialized)
|
|
1278
|
+
return;
|
|
1279
|
+
['mousemove', 'keydown', 'click', 'scroll'].forEach((event) => window.addEventListener(event, () => {
|
|
1280
|
+
if (this.#trackingWindowActivity) {
|
|
1281
|
+
this.#activityDetectedInWindow = true;
|
|
1282
|
+
}
|
|
1283
|
+
}));
|
|
1284
|
+
this.#userActivityTrackingInitialized = true;
|
|
1285
|
+
}
|
|
1286
|
+
//#endregion
|
|
1287
|
+
//#region Utilities
|
|
1288
|
+
resetAllTimers() {
|
|
1289
|
+
this.clearTimers();
|
|
1290
|
+
this.scheduleActivityWindow();
|
|
1291
|
+
this.#snackReference?.dismiss();
|
|
1292
|
+
}
|
|
1293
|
+
getExpiresAt() {
|
|
1294
|
+
return this.#appCacheService.getItem(this.#sessionStorageKey);
|
|
1295
|
+
}
|
|
1296
|
+
setExpiresAt(expiresAt) {
|
|
1297
|
+
this.#appCacheService.setItem(this.#sessionStorageKey, expiresAt).subscribe();
|
|
1298
|
+
}
|
|
1299
|
+
clearTimers() {
|
|
1300
|
+
this.#activityWindowStart$?.unsubscribe();
|
|
1301
|
+
this.#activityWindowEnd$?.unsubscribe();
|
|
1302
|
+
this.#logoutTimer$?.unsubscribe();
|
|
1303
|
+
this.#httpActivitySubscription$?.unsubscribe();
|
|
1304
|
+
}
|
|
1305
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SessionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1306
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SessionService, providedIn: 'root' }); }
|
|
1307
|
+
}
|
|
1308
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SessionService, decorators: [{
|
|
1309
|
+
type: Injectable,
|
|
1310
|
+
args: [{ providedIn: 'root' }]
|
|
1311
|
+
}] });
|
|
1312
|
+
|
|
857
1313
|
/**
|
|
858
1314
|
* Provides and initializes the Halo Focus feature for the Angular application.
|
|
859
1315
|
*
|
|
@@ -924,9 +1380,71 @@ function provideHaloFocus(config) {
|
|
|
924
1380
|
]);
|
|
925
1381
|
}
|
|
926
1382
|
|
|
1383
|
+
/**
|
|
1384
|
+
* Provides and initializes the SessionService at application startup.
|
|
1385
|
+
*
|
|
1386
|
+
* What it does
|
|
1387
|
+
* - Registers SessionService as a singleton to manage session expiry across the app
|
|
1388
|
+
* - Runs `session.init()` via APP_INITIALIZER when the app boots
|
|
1389
|
+
* - Initializes cross-tab BroadcastChannel sync, HTTP debounce hooks, and user-activity listeners
|
|
1390
|
+
* - Automatically tracks HTTP activity and user interactions to extend sessions
|
|
1391
|
+
*
|
|
1392
|
+
* Usage scenarios
|
|
1393
|
+
*
|
|
1394
|
+
* Scenario 1: Known session duration at startup
|
|
1395
|
+
* When the session duration is predetermined and constant, provide it here.
|
|
1396
|
+
* ```ts
|
|
1397
|
+
* // app.config.ts
|
|
1398
|
+
* export const appConfig: ApplicationConfig = {
|
|
1399
|
+
* providers: [
|
|
1400
|
+
* provideSession(30 * 60 * 1000), // 30 minutes
|
|
1401
|
+
* ]
|
|
1402
|
+
* };
|
|
1403
|
+
* ```
|
|
1404
|
+
*
|
|
1405
|
+
* Scenario 2: Dynamic session duration from backend
|
|
1406
|
+
* When the session duration is determined after login (e.g., from backend response),
|
|
1407
|
+
* omit the parameter here and call `startSession()` after receiving the duration.
|
|
1408
|
+
* ```ts
|
|
1409
|
+
* // app.config.ts
|
|
1410
|
+
* export const appConfig: ApplicationConfig = {
|
|
1411
|
+
* providers: [
|
|
1412
|
+
* provideSession(), // Defaults to 30 minutes until startSession() is called
|
|
1413
|
+
* ]
|
|
1414
|
+
* };
|
|
1415
|
+
*
|
|
1416
|
+
* // After login in AuthService:
|
|
1417
|
+
* login().subscribe(response => {
|
|
1418
|
+
* sessionService.startSession(response.sessionExpiresIn); // Set actual duration
|
|
1419
|
+
* });
|
|
1420
|
+
* ```
|
|
1421
|
+
*
|
|
1422
|
+
* @param sessionDuration Optional session duration in milliseconds. If omitted, defaults to 30 minutes (1800000ms).
|
|
1423
|
+
*/
|
|
1424
|
+
function provideSession(sessionDuration) {
|
|
1425
|
+
return makeEnvironmentProviders([
|
|
1426
|
+
provideAppInitializer(() => {
|
|
1427
|
+
const session = inject(SessionService);
|
|
1428
|
+
session.init(sessionDuration);
|
|
1429
|
+
})
|
|
1430
|
+
]);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
class YuuvisClientFrameworkModule {
|
|
1434
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1435
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, imports: [CommonModule] }); }
|
|
1436
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, imports: [CommonModule] }); }
|
|
1437
|
+
}
|
|
1438
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: YuuvisClientFrameworkModule, decorators: [{
|
|
1439
|
+
type: NgModule,
|
|
1440
|
+
args: [{
|
|
1441
|
+
imports: [CommonModule],
|
|
1442
|
+
}]
|
|
1443
|
+
}] });
|
|
1444
|
+
|
|
927
1445
|
/**
|
|
928
1446
|
* Generated bundle index. Do not edit.
|
|
929
1447
|
*/
|
|
930
1448
|
|
|
931
|
-
export { HaloFocusService, HaloUtilityService, SnackBarComponent, SnackBarService, YuuvisClientFrameworkModule, provideHaloFocus };
|
|
1449
|
+
export { ChannelMessage, HaloFocusService, HaloUtilityService, SessionService, SnackBarComponent, SnackBarService, YuuvisClientFrameworkModule, defaultHaloFocusOffset, haloExcludedElementsInMatFormField, haloFocusNavigationKeys, haloFocusStyles, provideHaloFocus, provideSession, sessionActivityWindowBeforeEnd, sessionDefaultDuration, sessionPopupBeforeEnd };
|
|
932
1450
|
//# sourceMappingURL=yuuvis-client-framework.mjs.map
|