@smartbit4all/ng-client 4.5.32 → 4.5.34

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Optional, Inject, PLATFORM_ID, NgModule, SkipSelf, RendererStyleFlags2, Directive, Input, HostBinding, HostListener, Component, EventEmitter, Output, ViewChildren, ViewChild, ElementRef, forwardRef, Pipe, ViewContainerRef, ViewEncapsulation, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, inject, input, computed, effect, signal, ApplicationRef, viewChild, ChangeDetectorRef } from '@angular/core';
2
+ import { InjectionToken, Injectable, Optional, Inject, PLATFORM_ID, NgModule, SkipSelf, RendererStyleFlags2, Directive, Input, HostBinding, HostListener, Component, EventEmitter, Output, ViewChildren, ViewChild, ElementRef, forwardRef, Pipe, ViewContainerRef, ViewEncapsulation, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, input, computed, effect, signal, inject, ApplicationRef, viewChild, ChangeDetectorRef } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
4
  import { HttpHeaders, HttpContext, HttpErrorResponse, HttpParams, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
5
5
  import { Subject, lastValueFrom, take, takeUntil, interval, startWith, map, distinctUntilChanged, catchError, throwError, from } from 'rxjs';
@@ -140,6 +140,13 @@ import { MatBadgeModule } from '@angular/material/badge';
140
140
  import * as i4$5 from '@angular/material/tabs';
141
141
  import { MatTabsModule } from '@angular/material/tabs';
142
142
 
143
+ var SessionErrorBehaviour;
144
+ (function (SessionErrorBehaviour) {
145
+ SessionErrorBehaviour[SessionErrorBehaviour["REFRESH"] = 0] = "REFRESH";
146
+ SessionErrorBehaviour[SessionErrorBehaviour["RESTART"] = 1] = "RESTART";
147
+ SessionErrorBehaviour[SessionErrorBehaviour["RESTART_VIEWCONTEXT"] = 2] = "RESTART_VIEWCONTEXT";
148
+ })(SessionErrorBehaviour || (SessionErrorBehaviour = {}));
149
+
143
150
  class SmartSubject extends Subject {
144
151
  constructor(unsubscribe$) {
145
152
  super();
@@ -1041,31 +1048,60 @@ class SmartSessionService {
1041
1048
  }
1042
1049
  async doRefreshSession() {
1043
1050
  try {
1044
- let refreshToken = localStorage.getItem('refreshToken');
1051
+ const refreshToken = localStorage.getItem('refreshToken');
1045
1052
  if (!refreshToken) {
1046
- return await this.startSession();
1047
- }
1048
- try {
1049
- const sessionInfo = await lastValueFrom(this.apiService.refreshSession({ refreshToken }));
1050
- if (sessionInfo) {
1051
- this.updateSessionInfo(sessionInfo);
1052
- return sessionInfo;
1053
- }
1053
+ // No refresh token at all: there is no HTTP refresh round-trip that the
1054
+ // interceptor could classify as an error, so run the session-restart
1055
+ // handling here instead of silently starting a new session.
1056
+ return await this.failRefresh();
1054
1057
  }
1055
- catch (error) {
1056
- if (this.isInvalidRefreshTokenError(error)) {
1057
- await this.clearAndStartNewSession();
1058
- // still throw the error, so the error can be handled in the caller
1059
- }
1060
- throw error;
1058
+ const sessionInfo = await lastValueFrom(this.apiService.refreshSession({ refreshToken }));
1059
+ if (!sessionInfo) {
1060
+ // The refresh round-trip completed (HTTP 200) but yielded no session, so
1061
+ // the interceptor never saw an error response handle it here as well.
1062
+ return await this.failRefresh();
1061
1063
  }
1062
- // no sessionInfo returned
1063
- throw new Error('Unable to refresh session');
1064
+ this.updateSessionInfo(sessionInfo);
1065
+ return sessionInfo;
1066
+ // If the refresh call itself FAILS with a session error, the
1067
+ // SmartErrorCatchingInterceptor already runs handleSessionError for that
1068
+ // (nested) HTTP error response. We let that error propagate unchanged and
1069
+ // must NOT handle it again here — that would invoke handleSessionError twice.
1064
1070
  }
1065
1071
  finally {
1066
1072
  this.runningRefresh = undefined;
1067
1073
  }
1068
1074
  }
1075
+ /**
1076
+ * Runs the standard session-restart handling for a refresh that finished
1077
+ * without an error HTTP response (no refresh token, or an empty refresh
1078
+ * result), then throws a failure shaped like the backend's rejected refresh.
1079
+ *
1080
+ * Only the paths that do NOT produce an error HTTP response come here: a
1081
+ * refresh call that fails with a session error is already handled by the
1082
+ * SmartErrorCatchingInterceptor, so routing it through here too would invoke
1083
+ * handleSessionError a second time.
1084
+ */
1085
+ async failRefresh() {
1086
+ await this.handleSessionError({
1087
+ code: 'session.refreshtoken.invalid',
1088
+ behaviour: SessionErrorBehaviour.RESTART,
1089
+ });
1090
+ throw this.createRefreshFailedError();
1091
+ }
1092
+ /**
1093
+ * Builds a failure shaped exactly like the backend's rejected-refresh
1094
+ * response (HTTP 400 + ApiError.code === 'session.refreshtoken.invalid'), so a
1095
+ * failed/impossible refresh surfaces like any other invalid refresh token
1096
+ * instead of producing a fresh session as a refresh side effect.
1097
+ */
1098
+ createRefreshFailedError() {
1099
+ const error = {
1100
+ code: 'session.refreshtoken.invalid',
1101
+ message: 'Unable to refresh session',
1102
+ };
1103
+ return new HttpErrorResponse({ status: 400, error });
1104
+ }
1069
1105
  updateSessionInfo(sessionInfo) {
1070
1106
  this.sessionInfoData = sessionInfo;
1071
1107
  this.token = sessionInfo.sid;
@@ -1079,11 +1115,6 @@ class SmartSessionService {
1079
1115
  this.setUpTokenForSessionService();
1080
1116
  this.setAuthenticationStatus();
1081
1117
  }
1082
- isInvalidRefreshTokenError(error) {
1083
- return (error instanceof HttpErrorResponse &&
1084
- error.error &&
1085
- error.error.code === 'session.refreshtoken.invalid');
1086
- }
1087
1118
  async getAuthenticationProviders() {
1088
1119
  let providers = await this.apiService
1089
1120
  .getAuthenticationProviders()
@@ -1584,6 +1615,51 @@ class ViewService {
1584
1615
  reportProgress: reportProgress
1585
1616
  });
1586
1617
  }
1618
+ getPublishedViewData(channel, uuid, observe = 'body', reportProgress = false, options) {
1619
+ if (channel === null || channel === undefined) {
1620
+ throw new Error('Required parameter channel was null or undefined when calling getPublishedViewData.');
1621
+ }
1622
+ if (uuid === null || uuid === undefined) {
1623
+ throw new Error('Required parameter uuid was null or undefined when calling getPublishedViewData.');
1624
+ }
1625
+ let localVarHeaders = this.defaultHeaders;
1626
+ let localVarHttpHeaderAcceptSelected = options && options.httpHeaderAccept;
1627
+ if (localVarHttpHeaderAcceptSelected === undefined) {
1628
+ // to determine the Accept header
1629
+ const httpHeaderAccepts = [
1630
+ 'application/json'
1631
+ ];
1632
+ localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
1633
+ }
1634
+ if (localVarHttpHeaderAcceptSelected !== undefined) {
1635
+ localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
1636
+ }
1637
+ let localVarHttpContext = options && options.context;
1638
+ if (localVarHttpContext === undefined) {
1639
+ localVarHttpContext = new HttpContext();
1640
+ }
1641
+ let responseType_ = 'json';
1642
+ if (localVarHttpHeaderAcceptSelected) {
1643
+ if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
1644
+ responseType_ = 'text';
1645
+ }
1646
+ else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
1647
+ responseType_ = 'json';
1648
+ }
1649
+ else {
1650
+ responseType_ = 'blob';
1651
+ }
1652
+ }
1653
+ let localVarPath = `/smartlink/${this.configuration.encodeParam({ name: "channel", value: channel, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined })}/${this.configuration.encodeParam({ name: "uuid", value: uuid, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined })}`;
1654
+ return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, {
1655
+ context: localVarHttpContext,
1656
+ responseType: responseType_,
1657
+ withCredentials: this.configuration.withCredentials,
1658
+ headers: localVarHeaders,
1659
+ observe: observe,
1660
+ reportProgress: reportProgress
1661
+ });
1662
+ }
1587
1663
  getViewConstraint(uuid, observe = 'body', reportProgress = false, options) {
1588
1664
  if (uuid === null || uuid === undefined) {
1589
1665
  throw new Error('Required parameter uuid was null or undefined when calling getViewConstraint.');
@@ -2387,6 +2463,18 @@ var NamedValidatorOperationEnum;
2387
2463
  })(NamedValidatorOperationEnum || (NamedValidatorOperationEnum = {}));
2388
2464
  ;
2389
2465
 
2466
+ /**
2467
+ * View API
2468
+ * View API
2469
+ *
2470
+ * The version of the OpenAPI document: 1.0.0
2471
+ * Contact: info@it4all.hu
2472
+ *
2473
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
2474
+ * https://openapi-generator.tech
2475
+ * Do not edit the class manually.
2476
+ */
2477
+
2390
2478
  /**
2391
2479
  * View API
2392
2480
  * View API
@@ -13190,71 +13278,6 @@ var SmartActionType;
13190
13278
  * Public API Surface of smartdialog
13191
13279
  */
13192
13280
 
13193
- var SessionErrorBehaviour;
13194
- (function (SessionErrorBehaviour) {
13195
- SessionErrorBehaviour[SessionErrorBehaviour["REFRESH"] = 0] = "REFRESH";
13196
- SessionErrorBehaviour[SessionErrorBehaviour["RESTART"] = 1] = "RESTART";
13197
- SessionErrorBehaviour[SessionErrorBehaviour["RESTART_VIEWCONTEXT"] = 2] = "RESTART_VIEWCONTEXT";
13198
- })(SessionErrorBehaviour || (SessionErrorBehaviour = {}));
13199
-
13200
- class SmartSessionTimerService {
13201
- constructor(session) {
13202
- this.session = session;
13203
- this._destroy$ = new Subject();
13204
- this.totalTimeInMinutes = 30;
13205
- this.sessionExpiration = 'sessionExpiracy';
13206
- this.timeChanged = new SmartSubject(this._destroy$);
13207
- this.onTimerExpired = new SmartSubject(this._destroy$);
13208
- this.session.sessionExpiracyChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13209
- this.restart();
13210
- });
13211
- }
13212
- ngOnDestroy() {
13213
- this._destroy$.next();
13214
- this._destroy$.complete();
13215
- }
13216
- startTimer() {
13217
- if (this.timer) {
13218
- clearInterval(this.timer);
13219
- }
13220
- const sessionInfoData = this.session.sessionInfoData;
13221
- const lifetimeSeconds = sessionInfoData?.duration;
13222
- if (lifetimeSeconds && lifetimeSeconds !== 0) {
13223
- this.timeLeftInSeconds = lifetimeSeconds;
13224
- }
13225
- else {
13226
- const expiration = new Date(sessionInfoData.expiration);
13227
- const now = new Date();
13228
- this.timeLeftInSeconds = Math.floor((expiration.getTime() - now.getTime()) / 1000);
13229
- }
13230
- this.timer = setInterval(() => {
13231
- this.timeLeftInSeconds--;
13232
- if (this.timeLeftInSeconds === 0) {
13233
- this.timeout();
13234
- }
13235
- this.timeChanged.next();
13236
- }, 1000);
13237
- }
13238
- restart() {
13239
- clearInterval(this.timer);
13240
- this.timer = undefined;
13241
- this.startTimer();
13242
- }
13243
- timeout() {
13244
- clearInterval(this.timer);
13245
- this.timer = undefined;
13246
- this.onTimerExpired.next();
13247
- }
13248
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, deps: [{ token: SmartSessionService }], target: i0.ɵɵFactoryTarget.Injectable }); }
13249
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, providedIn: 'root' }); }
13250
- }
13251
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, decorators: [{
13252
- type: Injectable,
13253
- args: [{
13254
- providedIn: 'root',
13255
- }]
13256
- }], ctorParameters: () => [{ type: SmartSessionService }] });
13257
-
13258
13281
  /**
13259
13282
  * Thrown by `whenReady()` when the viewcontext was rebuilt between the
13260
13283
  * caller's entry into `whenReady()` and the gate releasing. Any uuid the
@@ -13465,269 +13488,103 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
13465
13488
  args: [{ providedIn: 'root' }]
13466
13489
  }], ctorParameters: () => [{ type: SmartSessionService }, { type: SmartViewContextService }] });
13467
13490
 
13468
- class SmartSessionTimerComponent {
13469
- constructor(service, bootstrap) {
13470
- this.service = service;
13491
+ class SmartErrorCatchingInterceptor {
13492
+ constructor(session, bootstrap) {
13493
+ this.session = session;
13471
13494
  this.bootstrap = bootstrap;
13472
- this._destroy$ = new Subject();
13495
+ this.sessionErrorCodes = [
13496
+ {
13497
+ code: 'session.expired',
13498
+ behaviour: SessionErrorBehaviour.REFRESH,
13499
+ },
13500
+ {
13501
+ code: 'session.refreshtoken.invalid',
13502
+ behaviour: SessionErrorBehaviour.RESTART,
13503
+ },
13504
+ {
13505
+ code: 'session.badrequest.missingtoken',
13506
+ behaviour: SessionErrorBehaviour.RESTART,
13507
+ },
13508
+ {
13509
+ code: 'session.security.nocontext',
13510
+ behaviour: SessionErrorBehaviour.RESTART,
13511
+ },
13512
+ {
13513
+ code: 'session.invalidsessionuri',
13514
+ behaviour: SessionErrorBehaviour.RESTART,
13515
+ },
13516
+ {
13517
+ code: 'session.notinitialized',
13518
+ behaviour: SessionErrorBehaviour.RESTART,
13519
+ },
13520
+ {
13521
+ code: 'session.viewcontext.missing',
13522
+ behaviour: SessionErrorBehaviour.RESTART_VIEWCONTEXT,
13523
+ },
13524
+ ];
13473
13525
  }
13474
- ngOnInit() {
13475
- this.bootstrap.whenReady().then(() => {
13476
- this.service.startTimer();
13477
- this.timeInSeconds = this.service.timeLeftInSeconds;
13478
- this.service.timeChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13479
- this.timeInSeconds = this.service.timeLeftInSeconds;
13526
+ intercept(request, next) {
13527
+ return next
13528
+ .handle(request)
13529
+ .pipe(catchError$1((error) => this.handleError(error, request, next)));
13530
+ }
13531
+ handleError(error, request, next) {
13532
+ if (this.isSessionError(error)) {
13533
+ const sessionError = this.getSessionError(error);
13534
+ if (sessionError) {
13535
+ return this.resolveError(sessionError).pipe(switchMap((resolved) => {
13536
+ if (resolved && sessionError.behaviour === SessionErrorBehaviour.REFRESH) {
13537
+ return next.handle(this.updateRequestHeaders(request));
13538
+ }
13539
+ return throwError(() => error);
13540
+ }), catchError$1((resolveError) => {
13541
+ // when the resolveError throws a restart error (in the refreshSession), rethrow the original error
13542
+ if (this.isSessionError(resolveError)) {
13543
+ const resolveSessionError = this.getSessionError(resolveError);
13544
+ if (resolveSessionError?.behaviour === SessionErrorBehaviour.RESTART) {
13545
+ return throwError(() => error);
13546
+ }
13547
+ }
13548
+ return throwError(() => resolveError);
13549
+ }));
13550
+ }
13551
+ }
13552
+ if (this.isTransportError(error)) {
13553
+ // Fire-and-forget; the request still rejects so callers can react per-call.
13554
+ this.bootstrap.handleTransportError(error).catch((err) => {
13555
+ console.error('handleTransportError hook threw', err);
13480
13556
  });
13481
- });
13557
+ }
13558
+ return throwError(() => error);
13482
13559
  }
13483
- ngOnDestroy() {
13484
- this._destroy$.next();
13485
- this._destroy$.complete();
13560
+ isSessionError(error) {
13561
+ return error.status === 400 && error.error.code != null;
13486
13562
  }
13487
- formatTime(seconds) {
13488
- let minutes = Math.floor(seconds / 60);
13489
- let remainingSeconds = seconds % 60;
13490
- return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
13563
+ isTransportError(error) {
13564
+ return error.status === 0 || error.status >= 500;
13491
13565
  }
13492
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerComponent, deps: [{ token: SmartSessionTimerService }, { token: SmartBackendBootstrapService }], target: i0.ɵɵFactoryTarget.Component }); }
13493
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.11", type: SmartSessionTimerComponent, selector: "smart-session-timer", providers: [SmartSessionTimerService], ngImport: i0, template: "<div class=\"smart-session-timer-container\">\r\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\r\n {{ formatTime(timeInSeconds!) }}\r\n </span>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
13494
- }
13495
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerComponent, decorators: [{
13496
- type: Component,
13497
- args: [{ selector: 'smart-session-timer', providers: [SmartSessionTimerService], template: "<div class=\"smart-session-timer-container\">\r\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\r\n {{ formatTime(timeInSeconds!) }}\r\n </span>\r\n</div>\r\n" }]
13498
- }], ctorParameters: () => [{ type: SmartSessionTimerService }, { type: SmartBackendBootstrapService }] });
13499
-
13500
- const STRINGS_HU = {
13501
- startErrorMessage: 'A szerver nem elérhető.',
13502
- startErrorRetry: 'Újrapróbálás',
13503
- transportErrorMessage: 'A szerver nem elérhető.',
13504
- transportErrorReload: 'Újratöltés',
13505
- sessionErrorMessage: 'A munkamenet lejárt, kérjük jelentkezzen be újra.',
13506
- sessionErrorAction: 'Bejelentkezés',
13507
- };
13508
- const STRINGS_EN = {
13509
- startErrorMessage: 'The server is unavailable.',
13510
- startErrorRetry: 'Retry',
13511
- transportErrorMessage: 'The server is unavailable.',
13512
- transportErrorReload: 'Reload',
13513
- sessionErrorMessage: 'Your session has expired. Please log in again.',
13514
- sessionErrorAction: 'Log in',
13515
- };
13516
- /**
13517
- * Opt-in default UX for `SmartBackendBootstrapService` error hooks.
13518
- *
13519
- * Spread the result of `hooks(options)` into `bootstrap.configure()` to get a
13520
- * MatSnackBar-based "error + retry" UI without writing the host glue:
13521
- *
13522
- * ```ts
13523
- * bootstrap.configure({
13524
- * ...minimalConfig,
13525
- * ...defaultErrorUi.hooks({ language: 'hu' }),
13526
- * });
13527
- * ```
13528
- *
13529
- * Hosts that want custom logic can omit this service and write their own
13530
- * `onStartError` / `onTransportError` callbacks directly.
13531
- */
13532
- class SmartDefaultErrorUiService {
13533
- constructor() {
13534
- this.snackBar = inject(MatSnackBar);
13535
- this.session = inject(SmartSessionService);
13536
- }
13537
- /** Returns a hooks bundle ready to spread into `bootstrap.configure()`. */
13538
- hooks(options = {}) {
13539
- return {
13540
- onStartError: (err) => this.showStartError(err, options),
13541
- onTransportError: (err) => this.showTransportError(err, options),
13542
- onSessionError: (err) => this.showSessionError(err, options),
13543
- };
13544
- }
13545
- showStartError(_err, options = {}) {
13546
- const s = this.resolveStrings(options);
13547
- const ref = this.snackBar.open(s.startErrorMessage, s.startErrorRetry, {
13548
- duration: 0, // sticky — app is not usable until retry succeeds
13549
- });
13550
- // Full reload, not bootstrap.start(). A bootstrap.start() retry only re-runs
13551
- // the init sequence; any components that already mounted during the failed
13552
- // attempt have rejected `whenReady()` Promises and won't re-trigger their
13553
- // run() when bootstrap eventually succeeds. Reload guarantees a clean mount.
13554
- ref.onAction().subscribe(() => this.verifyServerAndReload());
13555
- }
13556
- showTransportError(_err, options = {}) {
13557
- const s = this.resolveStrings(options);
13558
- const ref = this.snackBar.open(s.transportErrorMessage, s.transportErrorReload, {
13559
- duration: options.transportErrorDuration ?? 0,
13560
- });
13561
- ref.onAction().subscribe(() => this.verifyServerAndReload());
13562
- }
13563
- showSessionError(_err, options = {}) {
13564
- const s = this.resolveStrings(options);
13565
- const ref = this.snackBar.open(s.sessionErrorMessage, s.sessionErrorAction, {
13566
- duration: 0, // sticky — session is invalid until the user takes action
13567
- });
13568
- ref.onAction().subscribe(() => this.recoverSession());
13569
- }
13570
- /**
13571
- * Clears the dead session and reloads. Unlike `verifyServerAndReload`, this
13572
- * does NOT pre-check via `getSession()` — the SID is known to be invalid,
13573
- * so the check would fail. We best-effort `clearAndStartNewSession()`
13574
- * (creates a fresh anonymous session); reload happens regardless so the
13575
- * user is not stuck if the clear fails (e.g., server flapping).
13576
- *
13577
- * Overridable in tests.
13578
- */
13579
- async recoverSession() {
13580
- try {
13581
- await this.session.clearAndStartNewSession();
13582
- }
13583
- catch {
13584
- // Server may also be down; reload anyway and let bootstrap.start() retry.
13585
- }
13586
- this.reloadPage();
13587
- }
13588
- /**
13589
- * Pings `getSession()` before reloading: if the server is still down, the
13590
- * reload would just re-trigger the broken-screen state. The failed check
13591
- * goes through `SmartErrorCatchingInterceptor` and re-fires `onTransportError`,
13592
- * so the user sees a fresh snackbar — they get visible feedback that the
13593
- * server is still unreachable, without paying the cost of a full bundle
13594
- * reload that lands on the same broken state.
13595
- *
13596
- * Overridable in tests.
13597
- */
13598
- async verifyServerAndReload() {
13599
- try {
13600
- await this.session.getSession();
13601
- }
13602
- catch {
13603
- return; // server still unreachable — interceptor re-opens the snackbar
13604
- }
13605
- this.reloadPage();
13606
- }
13607
- /** Overridable in tests. */
13608
- reloadPage() {
13609
- window.location.reload();
13610
- }
13611
- resolveStrings(options) {
13612
- const lang = this.resolveLanguage(options.language);
13613
- const base = lang === 'hu' ? STRINGS_HU : STRINGS_EN;
13614
- return { ...base, ...(options.strings ?? {}) };
13615
- }
13616
- resolveLanguage(lang) {
13617
- if (lang === 'hu' || lang === 'en') {
13618
- return lang;
13619
- }
13620
- return navigator.language?.toLowerCase().startsWith('hu') ? 'hu' : 'en';
13621
- }
13622
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
13623
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, providedIn: 'root' }); }
13624
- }
13625
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, decorators: [{
13626
- type: Injectable,
13627
- args: [{ providedIn: 'root' }]
13628
- }] });
13629
-
13630
- /*
13631
- * Public API Surface of smart-session
13632
- */
13633
-
13634
- class SmartErrorCatchingInterceptor {
13635
- constructor(session, bootstrap) {
13636
- this.session = session;
13637
- this.bootstrap = bootstrap;
13638
- this.sessionErrorCodes = [
13639
- {
13640
- code: 'session.expired',
13641
- behaviour: SessionErrorBehaviour.REFRESH,
13642
- },
13643
- {
13644
- code: 'session.refreshtoken.invalid',
13645
- behaviour: SessionErrorBehaviour.RESTART,
13646
- },
13647
- {
13648
- code: 'session.badrequest.missingtoken',
13649
- behaviour: SessionErrorBehaviour.RESTART,
13650
- },
13651
- {
13652
- code: 'session.security.nocontext',
13653
- behaviour: SessionErrorBehaviour.RESTART,
13654
- },
13655
- {
13656
- code: 'session.invalidsessionuri',
13657
- behaviour: SessionErrorBehaviour.RESTART,
13658
- },
13659
- {
13660
- code: 'session.notinitialized',
13661
- behaviour: SessionErrorBehaviour.RESTART,
13662
- },
13663
- {
13664
- code: 'session.viewcontext.missing',
13665
- behaviour: SessionErrorBehaviour.RESTART_VIEWCONTEXT,
13666
- },
13667
- ];
13668
- }
13669
- intercept(request, next) {
13670
- return next
13671
- .handle(request)
13672
- .pipe(catchError$1((error) => this.handleError(error, request, next)));
13673
- }
13674
- handleError(error, request, next) {
13675
- if (this.isSessionError(error)) {
13676
- const sessionError = this.getSessionError(error);
13677
- if (sessionError) {
13678
- return this.resolveError(sessionError).pipe(switchMap((resolved) => {
13679
- if (resolved && sessionError.behaviour === SessionErrorBehaviour.REFRESH) {
13680
- return next.handle(this.updateRequestHeaders(request));
13681
- }
13682
- return throwError(() => error);
13683
- }), catchError$1((resolveError) => {
13684
- // when the resolveError throws a restart error (in the refreshSession), rethrow the original error
13685
- if (this.isSessionError(resolveError)) {
13686
- const resolveSessionError = this.getSessionError(resolveError);
13687
- if (resolveSessionError?.behaviour === SessionErrorBehaviour.RESTART) {
13688
- return throwError(() => error);
13689
- }
13690
- }
13691
- return throwError(() => resolveError);
13692
- }));
13693
- }
13694
- }
13695
- if (this.isTransportError(error)) {
13696
- // Fire-and-forget; the request still rejects so callers can react per-call.
13697
- this.bootstrap.handleTransportError(error).catch((err) => {
13698
- console.error('handleTransportError hook threw', err);
13699
- });
13700
- }
13701
- return throwError(() => error);
13702
- }
13703
- isSessionError(error) {
13704
- return error.status === 400 && error.error.code != null;
13705
- }
13706
- isTransportError(error) {
13707
- return error.status === 0 || error.status >= 500;
13708
- }
13709
- getSessionError(error) {
13710
- return this.sessionErrorCodes.find((sessionError) => sessionError.code === error.error.code);
13711
- }
13712
- resolveError(sessionError) {
13713
- if (sessionError.behaviour === SessionErrorBehaviour.RESTART) {
13714
- return from(this.session.handleSessionError(sessionError));
13715
- }
13716
- if (sessionError.behaviour === SessionErrorBehaviour.RESTART_VIEWCONTEXT) {
13717
- return from(this.bootstrap.handleViewContextLost(sessionError));
13718
- }
13719
- return from(this.session.refreshSession());
13720
- }
13721
- updateRequestHeaders(request) {
13722
- this.session.headerParams.forEach((headerParam) => {
13723
- request = request.clone({
13724
- headers: request.headers.set(headerParam.headerName, headerParam.value),
13725
- });
13726
- });
13727
- return request;
13728
- }
13729
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartErrorCatchingInterceptor, deps: [{ token: SmartSessionService }, { token: SmartBackendBootstrapService }], target: i0.ɵɵFactoryTarget.Injectable }); }
13730
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartErrorCatchingInterceptor }); }
13566
+ getSessionError(error) {
13567
+ return this.sessionErrorCodes.find((sessionError) => sessionError.code === error.error.code);
13568
+ }
13569
+ resolveError(sessionError) {
13570
+ if (sessionError.behaviour === SessionErrorBehaviour.RESTART) {
13571
+ return from(this.session.handleSessionError(sessionError));
13572
+ }
13573
+ if (sessionError.behaviour === SessionErrorBehaviour.RESTART_VIEWCONTEXT) {
13574
+ return from(this.bootstrap.handleViewContextLost(sessionError));
13575
+ }
13576
+ return from(this.session.refreshSession());
13577
+ }
13578
+ updateRequestHeaders(request) {
13579
+ this.session.headerParams.forEach((headerParam) => {
13580
+ request = request.clone({
13581
+ headers: request.headers.set(headerParam.headerName, headerParam.value),
13582
+ });
13583
+ });
13584
+ return request;
13585
+ }
13586
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartErrorCatchingInterceptor, deps: [{ token: SmartSessionService }, { token: SmartBackendBootstrapService }], target: i0.ɵɵFactoryTarget.Injectable }); }
13587
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartErrorCatchingInterceptor }); }
13731
13588
  }
13732
13589
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartErrorCatchingInterceptor, decorators: [{
13733
13590
  type: Injectable
@@ -13774,6 +13631,96 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
13774
13631
  type: Injectable
13775
13632
  }], ctorParameters: () => [{ type: SmartSessionService }] });
13776
13633
 
13634
+ class SmartSessionTimerService {
13635
+ constructor(session) {
13636
+ this.session = session;
13637
+ this._destroy$ = new Subject();
13638
+ this.totalTimeInMinutes = 30;
13639
+ this.sessionExpiration = 'sessionExpiracy';
13640
+ this.timeChanged = new SmartSubject(this._destroy$);
13641
+ this.onTimerExpired = new SmartSubject(this._destroy$);
13642
+ this.session.sessionExpiracyChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13643
+ this.restart();
13644
+ });
13645
+ }
13646
+ ngOnDestroy() {
13647
+ this._destroy$.next();
13648
+ this._destroy$.complete();
13649
+ }
13650
+ startTimer() {
13651
+ if (this.timer) {
13652
+ clearInterval(this.timer);
13653
+ }
13654
+ const sessionInfoData = this.session.sessionInfoData;
13655
+ const lifetimeSeconds = sessionInfoData?.duration;
13656
+ if (lifetimeSeconds && lifetimeSeconds !== 0) {
13657
+ this.timeLeftInSeconds = lifetimeSeconds;
13658
+ }
13659
+ else {
13660
+ const expiration = new Date(sessionInfoData.expiration);
13661
+ const now = new Date();
13662
+ this.timeLeftInSeconds = Math.floor((expiration.getTime() - now.getTime()) / 1000);
13663
+ }
13664
+ this.timer = setInterval(() => {
13665
+ this.timeLeftInSeconds--;
13666
+ if (this.timeLeftInSeconds === 0) {
13667
+ this.timeout();
13668
+ }
13669
+ this.timeChanged.next();
13670
+ }, 1000);
13671
+ }
13672
+ restart() {
13673
+ clearInterval(this.timer);
13674
+ this.timer = undefined;
13675
+ this.startTimer();
13676
+ }
13677
+ timeout() {
13678
+ clearInterval(this.timer);
13679
+ this.timer = undefined;
13680
+ this.onTimerExpired.next();
13681
+ }
13682
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, deps: [{ token: SmartSessionService }], target: i0.ɵɵFactoryTarget.Injectable }); }
13683
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, providedIn: 'root' }); }
13684
+ }
13685
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, decorators: [{
13686
+ type: Injectable,
13687
+ args: [{
13688
+ providedIn: 'root',
13689
+ }]
13690
+ }], ctorParameters: () => [{ type: SmartSessionService }] });
13691
+
13692
+ class SmartSessionTimerComponent {
13693
+ constructor(service, bootstrap) {
13694
+ this.service = service;
13695
+ this.bootstrap = bootstrap;
13696
+ this._destroy$ = new Subject();
13697
+ }
13698
+ ngOnInit() {
13699
+ this.bootstrap.whenReady().then(() => {
13700
+ this.service.startTimer();
13701
+ this.timeInSeconds = this.service.timeLeftInSeconds;
13702
+ this.service.timeChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13703
+ this.timeInSeconds = this.service.timeLeftInSeconds;
13704
+ });
13705
+ });
13706
+ }
13707
+ ngOnDestroy() {
13708
+ this._destroy$.next();
13709
+ this._destroy$.complete();
13710
+ }
13711
+ formatTime(seconds) {
13712
+ let minutes = Math.floor(seconds / 60);
13713
+ let remainingSeconds = seconds % 60;
13714
+ return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
13715
+ }
13716
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerComponent, deps: [{ token: SmartSessionTimerService }, { token: SmartBackendBootstrapService }], target: i0.ɵɵFactoryTarget.Component }); }
13717
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.11", type: SmartSessionTimerComponent, selector: "smart-session-timer", providers: [SmartSessionTimerService], ngImport: i0, template: "<div class=\"smart-session-timer-container\">\r\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\r\n {{ formatTime(timeInSeconds!) }}\r\n </span>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
13718
+ }
13719
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerComponent, decorators: [{
13720
+ type: Component,
13721
+ args: [{ selector: 'smart-session-timer', providers: [SmartSessionTimerService], template: "<div class=\"smart-session-timer-container\">\r\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\r\n {{ formatTime(timeInSeconds!) }}\r\n </span>\r\n</div>\r\n" }]
13722
+ }], ctorParameters: () => [{ type: SmartSessionTimerService }, { type: SmartBackendBootstrapService }] });
13723
+
13777
13724
  class SmartSessionModule {
13778
13725
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
13779
13726
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionModule, declarations: [SmartSessionTimerComponent], imports: [BrowserModule, MatCommonModule, MatExpansionModule, MatButtonModule, SmartIconModule], exports: [SmartSessionTimerComponent] }); }
@@ -24128,6 +24075,140 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
24128
24075
  args: [UiActionToolbarComponent]
24129
24076
  }] } });
24130
24077
 
24078
+ const STRINGS_HU = {
24079
+ startErrorMessage: 'A szerver nem elérhető.',
24080
+ startErrorRetry: 'Újrapróbálás',
24081
+ transportErrorMessage: 'A szerver nem elérhető.',
24082
+ transportErrorReload: 'Újratöltés',
24083
+ sessionErrorMessage: 'A munkamenet lejárt, kérjük jelentkezzen be újra.',
24084
+ sessionErrorAction: 'Bejelentkezés',
24085
+ };
24086
+ const STRINGS_EN = {
24087
+ startErrorMessage: 'The server is unavailable.',
24088
+ startErrorRetry: 'Retry',
24089
+ transportErrorMessage: 'The server is unavailable.',
24090
+ transportErrorReload: 'Reload',
24091
+ sessionErrorMessage: 'Your session has expired. Please log in again.',
24092
+ sessionErrorAction: 'Log in',
24093
+ };
24094
+ /**
24095
+ * Opt-in default UX for `SmartBackendBootstrapService` error hooks.
24096
+ *
24097
+ * Spread the result of `hooks(options)` into `bootstrap.configure()` to get a
24098
+ * MatSnackBar-based "error + retry" UI without writing the host glue:
24099
+ *
24100
+ * ```ts
24101
+ * bootstrap.configure({
24102
+ * ...minimalConfig,
24103
+ * ...defaultErrorUi.hooks({ language: 'hu' }),
24104
+ * });
24105
+ * ```
24106
+ *
24107
+ * Hosts that want custom logic can omit this service and write their own
24108
+ * `onStartError` / `onTransportError` callbacks directly.
24109
+ */
24110
+ class SmartDefaultErrorUiService {
24111
+ constructor() {
24112
+ this.snackBar = inject(MatSnackBar);
24113
+ this.session = inject(SmartSessionService);
24114
+ }
24115
+ /** Returns a hooks bundle ready to spread into `bootstrap.configure()`. */
24116
+ hooks(options = {}) {
24117
+ return {
24118
+ onStartError: (err) => this.showStartError(err, options),
24119
+ onTransportError: (err) => this.showTransportError(err, options),
24120
+ onSessionError: (err) => this.showSessionError(err, options),
24121
+ };
24122
+ }
24123
+ showStartError(_err, options = {}) {
24124
+ const s = this.resolveStrings(options);
24125
+ const ref = this.snackBar.open(s.startErrorMessage, s.startErrorRetry, {
24126
+ duration: 0, // sticky — app is not usable until retry succeeds
24127
+ });
24128
+ // Full reload, not bootstrap.start(). A bootstrap.start() retry only re-runs
24129
+ // the init sequence; any components that already mounted during the failed
24130
+ // attempt have rejected `whenReady()` Promises and won't re-trigger their
24131
+ // run() when bootstrap eventually succeeds. Reload guarantees a clean mount.
24132
+ ref.onAction().subscribe(() => this.verifyServerAndReload());
24133
+ }
24134
+ showTransportError(_err, options = {}) {
24135
+ const s = this.resolveStrings(options);
24136
+ const ref = this.snackBar.open(s.transportErrorMessage, s.transportErrorReload, {
24137
+ duration: options.transportErrorDuration ?? 0,
24138
+ });
24139
+ ref.onAction().subscribe(() => this.verifyServerAndReload());
24140
+ }
24141
+ showSessionError(_err, options = {}) {
24142
+ const s = this.resolveStrings(options);
24143
+ const ref = this.snackBar.open(s.sessionErrorMessage, s.sessionErrorAction, {
24144
+ duration: 0, // sticky — session is invalid until the user takes action
24145
+ });
24146
+ ref.onAction().subscribe(() => this.recoverSession());
24147
+ }
24148
+ /**
24149
+ * Clears the dead session and reloads. Unlike `verifyServerAndReload`, this
24150
+ * does NOT pre-check via `getSession()` — the SID is known to be invalid,
24151
+ * so the check would fail. We best-effort `clearAndStartNewSession()`
24152
+ * (creates a fresh anonymous session); reload happens regardless so the
24153
+ * user is not stuck if the clear fails (e.g., server flapping).
24154
+ *
24155
+ * Overridable in tests.
24156
+ */
24157
+ async recoverSession() {
24158
+ try {
24159
+ await this.session.clearAndStartNewSession();
24160
+ }
24161
+ catch {
24162
+ // Server may also be down; reload anyway and let bootstrap.start() retry.
24163
+ }
24164
+ this.reloadPage();
24165
+ }
24166
+ /**
24167
+ * Pings `getSession()` before reloading: if the server is still down, the
24168
+ * reload would just re-trigger the broken-screen state. The failed check
24169
+ * goes through `SmartErrorCatchingInterceptor` and re-fires `onTransportError`,
24170
+ * so the user sees a fresh snackbar — they get visible feedback that the
24171
+ * server is still unreachable, without paying the cost of a full bundle
24172
+ * reload that lands on the same broken state.
24173
+ *
24174
+ * Overridable in tests.
24175
+ */
24176
+ async verifyServerAndReload() {
24177
+ try {
24178
+ await this.session.getSession();
24179
+ }
24180
+ catch {
24181
+ return; // server still unreachable — interceptor re-opens the snackbar
24182
+ }
24183
+ this.reloadPage();
24184
+ }
24185
+ /** Overridable in tests. */
24186
+ reloadPage() {
24187
+ window.location.reload();
24188
+ }
24189
+ resolveStrings(options) {
24190
+ const lang = this.resolveLanguage(options.language);
24191
+ const base = lang === 'hu' ? STRINGS_HU : STRINGS_EN;
24192
+ return { ...base, ...(options.strings ?? {}) };
24193
+ }
24194
+ resolveLanguage(lang) {
24195
+ if (lang === 'hu' || lang === 'en') {
24196
+ return lang;
24197
+ }
24198
+ return navigator.language?.toLowerCase().startsWith('hu') ? 'hu' : 'en';
24199
+ }
24200
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
24201
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, providedIn: 'root' }); }
24202
+ }
24203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, decorators: [{
24204
+ type: Injectable,
24205
+ args: [{ providedIn: 'root' }]
24206
+ }] });
24207
+
24208
+ /*
24209
+ * Public API Surface of smart-session
24210
+ */
24211
+
24131
24212
  class SmartNavbarService {
24132
24213
  constructor() { }
24133
24214
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartNavbarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }