@smartbit4all/ng-client 6.0.7 → 6.0.9

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';
@@ -139,6 +139,13 @@ import { MatToolbarModule } from '@angular/material/toolbar';
139
139
  import { MatBadgeModule } from '@angular/material/badge';
140
140
  import { MatTabsModule } from '@angular/material/tabs';
141
141
 
142
+ var SessionErrorBehaviour;
143
+ (function (SessionErrorBehaviour) {
144
+ SessionErrorBehaviour[SessionErrorBehaviour["REFRESH"] = 0] = "REFRESH";
145
+ SessionErrorBehaviour[SessionErrorBehaviour["RESTART"] = 1] = "RESTART";
146
+ SessionErrorBehaviour[SessionErrorBehaviour["RESTART_VIEWCONTEXT"] = 2] = "RESTART_VIEWCONTEXT";
147
+ })(SessionErrorBehaviour || (SessionErrorBehaviour = {}));
148
+
142
149
  class SmartSubject extends Subject {
143
150
  constructor(unsubscribe$) {
144
151
  super();
@@ -1040,31 +1047,60 @@ class SmartSessionService {
1040
1047
  }
1041
1048
  async doRefreshSession() {
1042
1049
  try {
1043
- let refreshToken = localStorage.getItem('refreshToken');
1050
+ const refreshToken = localStorage.getItem('refreshToken');
1044
1051
  if (!refreshToken) {
1045
- return await this.startSession();
1052
+ // No refresh token at all: there is no HTTP refresh round-trip that the
1053
+ // interceptor could classify as an error, so run the session-restart
1054
+ // handling here instead of silently starting a new session.
1055
+ return await this.failRefresh();
1046
1056
  }
1047
- try {
1048
- const sessionInfo = await lastValueFrom(this.apiService.refreshSession({ refreshToken }));
1049
- if (sessionInfo) {
1050
- this.updateSessionInfo(sessionInfo);
1051
- return sessionInfo;
1052
- }
1057
+ const sessionInfo = await lastValueFrom(this.apiService.refreshSession({ refreshToken }));
1058
+ if (!sessionInfo) {
1059
+ // The refresh round-trip completed (HTTP 200) but yielded no session, so
1060
+ // the interceptor never saw an error response — handle it here as well.
1061
+ return await this.failRefresh();
1053
1062
  }
1054
- catch (error) {
1055
- if (this.isInvalidRefreshTokenError(error)) {
1056
- await this.clearAndStartNewSession();
1057
- // still throw the error, so the error can be handled in the caller
1058
- }
1059
- throw error;
1060
- }
1061
- // no sessionInfo returned
1062
- throw new Error('Unable to refresh session');
1063
+ this.updateSessionInfo(sessionInfo);
1064
+ return sessionInfo;
1065
+ // If the refresh call itself FAILS with a session error, the
1066
+ // SmartErrorCatchingInterceptor already runs handleSessionError for that
1067
+ // (nested) HTTP error response. We let that error propagate unchanged and
1068
+ // must NOT handle it again here — that would invoke handleSessionError twice.
1063
1069
  }
1064
1070
  finally {
1065
1071
  this.runningRefresh = undefined;
1066
1072
  }
1067
1073
  }
1074
+ /**
1075
+ * Runs the standard session-restart handling for a refresh that finished
1076
+ * without an error HTTP response (no refresh token, or an empty refresh
1077
+ * result), then throws a failure shaped like the backend's rejected refresh.
1078
+ *
1079
+ * Only the paths that do NOT produce an error HTTP response come here: a
1080
+ * refresh call that fails with a session error is already handled by the
1081
+ * SmartErrorCatchingInterceptor, so routing it through here too would invoke
1082
+ * handleSessionError a second time.
1083
+ */
1084
+ async failRefresh() {
1085
+ await this.handleSessionError({
1086
+ code: 'session.refreshtoken.invalid',
1087
+ behaviour: SessionErrorBehaviour.RESTART,
1088
+ });
1089
+ throw this.createRefreshFailedError();
1090
+ }
1091
+ /**
1092
+ * Builds a failure shaped exactly like the backend's rejected-refresh
1093
+ * response (HTTP 400 + ApiError.code === 'session.refreshtoken.invalid'), so a
1094
+ * failed/impossible refresh surfaces like any other invalid refresh token
1095
+ * instead of producing a fresh session as a refresh side effect.
1096
+ */
1097
+ createRefreshFailedError() {
1098
+ const error = {
1099
+ code: 'session.refreshtoken.invalid',
1100
+ message: 'Unable to refresh session',
1101
+ };
1102
+ return new HttpErrorResponse({ status: 400, error });
1103
+ }
1068
1104
  updateSessionInfo(sessionInfo) {
1069
1105
  this.sessionInfoData = sessionInfo;
1070
1106
  this.token = sessionInfo.sid;
@@ -1078,11 +1114,6 @@ class SmartSessionService {
1078
1114
  this.setUpTokenForSessionService();
1079
1115
  this.setAuthenticationStatus();
1080
1116
  }
1081
- isInvalidRefreshTokenError(error) {
1082
- return (error instanceof HttpErrorResponse &&
1083
- error.error &&
1084
- error.error.code === 'session.refreshtoken.invalid');
1085
- }
1086
1117
  async getAuthenticationProviders() {
1087
1118
  let providers = await this.apiService
1088
1119
  .getAuthenticationProviders()
@@ -13400,71 +13431,6 @@ var SmartActionType;
13400
13431
  * Public API Surface of smartdialog
13401
13432
  */
13402
13433
 
13403
- var SessionErrorBehaviour;
13404
- (function (SessionErrorBehaviour) {
13405
- SessionErrorBehaviour[SessionErrorBehaviour["REFRESH"] = 0] = "REFRESH";
13406
- SessionErrorBehaviour[SessionErrorBehaviour["RESTART"] = 1] = "RESTART";
13407
- SessionErrorBehaviour[SessionErrorBehaviour["RESTART_VIEWCONTEXT"] = 2] = "RESTART_VIEWCONTEXT";
13408
- })(SessionErrorBehaviour || (SessionErrorBehaviour = {}));
13409
-
13410
- class SmartSessionTimerService {
13411
- constructor(session) {
13412
- this.session = session;
13413
- this._destroy$ = new Subject();
13414
- this.totalTimeInMinutes = 30;
13415
- this.sessionExpiration = 'sessionExpiracy';
13416
- this.timeChanged = new SmartSubject(this._destroy$);
13417
- this.onTimerExpired = new SmartSubject(this._destroy$);
13418
- this.session.sessionExpiracyChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13419
- this.restart();
13420
- });
13421
- }
13422
- ngOnDestroy() {
13423
- this._destroy$.next();
13424
- this._destroy$.complete();
13425
- }
13426
- startTimer() {
13427
- if (this.timer) {
13428
- clearInterval(this.timer);
13429
- }
13430
- const sessionInfoData = this.session.sessionInfoData;
13431
- const lifetimeSeconds = sessionInfoData?.duration;
13432
- if (lifetimeSeconds && lifetimeSeconds !== 0) {
13433
- this.timeLeftInSeconds = lifetimeSeconds;
13434
- }
13435
- else {
13436
- const expiration = new Date(sessionInfoData.expiration);
13437
- const now = new Date();
13438
- this.timeLeftInSeconds = Math.floor((expiration.getTime() - now.getTime()) / 1000);
13439
- }
13440
- this.timer = setInterval(() => {
13441
- this.timeLeftInSeconds--;
13442
- if (this.timeLeftInSeconds === 0) {
13443
- this.timeout();
13444
- }
13445
- this.timeChanged.next();
13446
- }, 1000);
13447
- }
13448
- restart() {
13449
- clearInterval(this.timer);
13450
- this.timer = undefined;
13451
- this.startTimer();
13452
- }
13453
- timeout() {
13454
- clearInterval(this.timer);
13455
- this.timer = undefined;
13456
- this.onTimerExpired.next();
13457
- }
13458
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, deps: [{ token: SmartSessionService }], target: i0.ɵɵFactoryTarget.Injectable }); }
13459
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, providedIn: 'root' }); }
13460
- }
13461
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, decorators: [{
13462
- type: Injectable,
13463
- args: [{
13464
- providedIn: 'root',
13465
- }]
13466
- }], ctorParameters: () => [{ type: SmartSessionService }] });
13467
-
13468
13434
  /**
13469
13435
  * Thrown by `whenReady()` when the viewcontext was rebuilt between the
13470
13436
  * caller's entry into `whenReady()` and the gate releasing. Any uuid the
@@ -13675,172 +13641,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
13675
13641
  args: [{ providedIn: 'root' }]
13676
13642
  }], ctorParameters: () => [{ type: SmartSessionService }, { type: SmartViewContextService }] });
13677
13643
 
13678
- class SmartSessionTimerComponent {
13679
- constructor(service, bootstrap) {
13680
- this.service = service;
13681
- this.bootstrap = bootstrap;
13682
- this._destroy$ = new Subject();
13683
- }
13684
- ngOnInit() {
13685
- this.bootstrap.whenReady().then(() => {
13686
- this.service.startTimer();
13687
- this.timeInSeconds = this.service.timeLeftInSeconds;
13688
- this.service.timeChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13689
- this.timeInSeconds = this.service.timeLeftInSeconds;
13690
- });
13691
- });
13692
- }
13693
- ngOnDestroy() {
13694
- this._destroy$.next();
13695
- this._destroy$.complete();
13696
- }
13697
- formatTime(seconds) {
13698
- let minutes = Math.floor(seconds / 60);
13699
- let remainingSeconds = seconds % 60;
13700
- return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
13701
- }
13702
- 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 }); }
13703
- 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\">\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\n {{ formatTime(timeInSeconds!) }}\n </span>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
13704
- }
13705
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerComponent, decorators: [{
13706
- type: Component,
13707
- args: [{ selector: 'smart-session-timer', providers: [SmartSessionTimerService], template: "<div class=\"smart-session-timer-container\">\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\n {{ formatTime(timeInSeconds!) }}\n </span>\n</div>\n" }]
13708
- }], ctorParameters: () => [{ type: SmartSessionTimerService }, { type: SmartBackendBootstrapService }] });
13709
-
13710
- const STRINGS_HU = {
13711
- startErrorMessage: 'A szerver nem elérhető.',
13712
- startErrorRetry: 'Újrapróbálás',
13713
- transportErrorMessage: 'A szerver nem elérhető.',
13714
- transportErrorReload: 'Újratöltés',
13715
- sessionErrorMessage: 'A munkamenet lejárt, kérjük jelentkezzen be újra.',
13716
- sessionErrorAction: 'Bejelentkezés',
13717
- };
13718
- const STRINGS_EN = {
13719
- startErrorMessage: 'The server is unavailable.',
13720
- startErrorRetry: 'Retry',
13721
- transportErrorMessage: 'The server is unavailable.',
13722
- transportErrorReload: 'Reload',
13723
- sessionErrorMessage: 'Your session has expired. Please log in again.',
13724
- sessionErrorAction: 'Log in',
13725
- };
13726
- /**
13727
- * Opt-in default UX for `SmartBackendBootstrapService` error hooks.
13728
- *
13729
- * Spread the result of `hooks(options)` into `bootstrap.configure()` to get a
13730
- * MatSnackBar-based "error + retry" UI without writing the host glue:
13731
- *
13732
- * ```ts
13733
- * bootstrap.configure({
13734
- * ...minimalConfig,
13735
- * ...defaultErrorUi.hooks({ language: 'hu' }),
13736
- * });
13737
- * ```
13738
- *
13739
- * Hosts that want custom logic can omit this service and write their own
13740
- * `onStartError` / `onTransportError` callbacks directly.
13741
- */
13742
- class SmartDefaultErrorUiService {
13743
- constructor() {
13744
- this.snackBar = inject(MatSnackBar);
13745
- this.session = inject(SmartSessionService);
13746
- }
13747
- /** Returns a hooks bundle ready to spread into `bootstrap.configure()`. */
13748
- hooks(options = {}) {
13749
- return {
13750
- onStartError: (err) => this.showStartError(err, options),
13751
- onTransportError: (err) => this.showTransportError(err, options),
13752
- onSessionError: (err) => this.showSessionError(err, options),
13753
- };
13754
- }
13755
- showStartError(_err, options = {}) {
13756
- const s = this.resolveStrings(options);
13757
- const ref = this.snackBar.open(s.startErrorMessage, s.startErrorRetry, {
13758
- duration: 0, // sticky — app is not usable until retry succeeds
13759
- });
13760
- // Full reload, not bootstrap.start(). A bootstrap.start() retry only re-runs
13761
- // the init sequence; any components that already mounted during the failed
13762
- // attempt have rejected `whenReady()` Promises and won't re-trigger their
13763
- // run() when bootstrap eventually succeeds. Reload guarantees a clean mount.
13764
- ref.onAction().subscribe(() => this.verifyServerAndReload());
13765
- }
13766
- showTransportError(_err, options = {}) {
13767
- const s = this.resolveStrings(options);
13768
- const ref = this.snackBar.open(s.transportErrorMessage, s.transportErrorReload, {
13769
- duration: options.transportErrorDuration ?? 0,
13770
- });
13771
- ref.onAction().subscribe(() => this.verifyServerAndReload());
13772
- }
13773
- showSessionError(_err, options = {}) {
13774
- const s = this.resolveStrings(options);
13775
- const ref = this.snackBar.open(s.sessionErrorMessage, s.sessionErrorAction, {
13776
- duration: 0, // sticky — session is invalid until the user takes action
13777
- });
13778
- ref.onAction().subscribe(() => this.recoverSession());
13779
- }
13780
- /**
13781
- * Clears the dead session and reloads. Unlike `verifyServerAndReload`, this
13782
- * does NOT pre-check via `getSession()` — the SID is known to be invalid,
13783
- * so the check would fail. We best-effort `clearAndStartNewSession()`
13784
- * (creates a fresh anonymous session); reload happens regardless so the
13785
- * user is not stuck if the clear fails (e.g., server flapping).
13786
- *
13787
- * Overridable in tests.
13788
- */
13789
- async recoverSession() {
13790
- try {
13791
- await this.session.clearAndStartNewSession();
13792
- }
13793
- catch {
13794
- // Server may also be down; reload anyway and let bootstrap.start() retry.
13795
- }
13796
- this.reloadPage();
13797
- }
13798
- /**
13799
- * Pings `getSession()` before reloading: if the server is still down, the
13800
- * reload would just re-trigger the broken-screen state. The failed check
13801
- * goes through `SmartErrorCatchingInterceptor` and re-fires `onTransportError`,
13802
- * so the user sees a fresh snackbar — they get visible feedback that the
13803
- * server is still unreachable, without paying the cost of a full bundle
13804
- * reload that lands on the same broken state.
13805
- *
13806
- * Overridable in tests.
13807
- */
13808
- async verifyServerAndReload() {
13809
- try {
13810
- await this.session.getSession();
13811
- }
13812
- catch {
13813
- return; // server still unreachable — interceptor re-opens the snackbar
13814
- }
13815
- this.reloadPage();
13816
- }
13817
- /** Overridable in tests. */
13818
- reloadPage() {
13819
- window.location.reload();
13820
- }
13821
- resolveStrings(options) {
13822
- const lang = this.resolveLanguage(options.language);
13823
- const base = lang === 'hu' ? STRINGS_HU : STRINGS_EN;
13824
- return { ...base, ...(options.strings ?? {}) };
13825
- }
13826
- resolveLanguage(lang) {
13827
- if (lang === 'hu' || lang === 'en') {
13828
- return lang;
13829
- }
13830
- return navigator.language?.toLowerCase().startsWith('hu') ? 'hu' : 'en';
13831
- }
13832
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
13833
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, providedIn: 'root' }); }
13834
- }
13835
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, decorators: [{
13836
- type: Injectable,
13837
- args: [{ providedIn: 'root' }]
13838
- }] });
13839
-
13840
- /*
13841
- * Public API Surface of smart-session
13842
- */
13843
-
13844
13644
  class SmartErrorCatchingInterceptor {
13845
13645
  constructor(session, bootstrap) {
13846
13646
  this.session = session;
@@ -13984,6 +13784,96 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
13984
13784
  type: Injectable
13985
13785
  }], ctorParameters: () => [{ type: SmartSessionService }] });
13986
13786
 
13787
+ class SmartSessionTimerService {
13788
+ constructor(session) {
13789
+ this.session = session;
13790
+ this._destroy$ = new Subject();
13791
+ this.totalTimeInMinutes = 30;
13792
+ this.sessionExpiration = 'sessionExpiracy';
13793
+ this.timeChanged = new SmartSubject(this._destroy$);
13794
+ this.onTimerExpired = new SmartSubject(this._destroy$);
13795
+ this.session.sessionExpiracyChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13796
+ this.restart();
13797
+ });
13798
+ }
13799
+ ngOnDestroy() {
13800
+ this._destroy$.next();
13801
+ this._destroy$.complete();
13802
+ }
13803
+ startTimer() {
13804
+ if (this.timer) {
13805
+ clearInterval(this.timer);
13806
+ }
13807
+ const sessionInfoData = this.session.sessionInfoData;
13808
+ const lifetimeSeconds = sessionInfoData?.duration;
13809
+ if (lifetimeSeconds && lifetimeSeconds !== 0) {
13810
+ this.timeLeftInSeconds = lifetimeSeconds;
13811
+ }
13812
+ else {
13813
+ const expiration = new Date(sessionInfoData.expiration);
13814
+ const now = new Date();
13815
+ this.timeLeftInSeconds = Math.floor((expiration.getTime() - now.getTime()) / 1000);
13816
+ }
13817
+ this.timer = setInterval(() => {
13818
+ this.timeLeftInSeconds--;
13819
+ if (this.timeLeftInSeconds === 0) {
13820
+ this.timeout();
13821
+ }
13822
+ this.timeChanged.next();
13823
+ }, 1000);
13824
+ }
13825
+ restart() {
13826
+ clearInterval(this.timer);
13827
+ this.timer = undefined;
13828
+ this.startTimer();
13829
+ }
13830
+ timeout() {
13831
+ clearInterval(this.timer);
13832
+ this.timer = undefined;
13833
+ this.onTimerExpired.next();
13834
+ }
13835
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, deps: [{ token: SmartSessionService }], target: i0.ɵɵFactoryTarget.Injectable }); }
13836
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, providedIn: 'root' }); }
13837
+ }
13838
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerService, decorators: [{
13839
+ type: Injectable,
13840
+ args: [{
13841
+ providedIn: 'root',
13842
+ }]
13843
+ }], ctorParameters: () => [{ type: SmartSessionService }] });
13844
+
13845
+ class SmartSessionTimerComponent {
13846
+ constructor(service, bootstrap) {
13847
+ this.service = service;
13848
+ this.bootstrap = bootstrap;
13849
+ this._destroy$ = new Subject();
13850
+ }
13851
+ ngOnInit() {
13852
+ this.bootstrap.whenReady().then(() => {
13853
+ this.service.startTimer();
13854
+ this.timeInSeconds = this.service.timeLeftInSeconds;
13855
+ this.service.timeChanged.pipe(takeUntil(this._destroy$)).subscribe(() => {
13856
+ this.timeInSeconds = this.service.timeLeftInSeconds;
13857
+ });
13858
+ });
13859
+ }
13860
+ ngOnDestroy() {
13861
+ this._destroy$.next();
13862
+ this._destroy$.complete();
13863
+ }
13864
+ formatTime(seconds) {
13865
+ let minutes = Math.floor(seconds / 60);
13866
+ let remainingSeconds = seconds % 60;
13867
+ return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
13868
+ }
13869
+ 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 }); }
13870
+ 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\">\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\n {{ formatTime(timeInSeconds!) }}\n </span>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
13871
+ }
13872
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionTimerComponent, decorators: [{
13873
+ type: Component,
13874
+ args: [{ selector: 'smart-session-timer', providers: [SmartSessionTimerService], template: "<div class=\"smart-session-timer-container\">\n <span class=\"smart-session-timer\" *ngIf=\"timeInSeconds\">\n {{ formatTime(timeInSeconds!) }}\n </span>\n</div>\n" }]
13875
+ }], ctorParameters: () => [{ type: SmartSessionTimerService }, { type: SmartBackendBootstrapService }] });
13876
+
13987
13877
  class SmartSessionModule {
13988
13878
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartSessionModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
13989
13879
  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] }); }
@@ -21571,7 +21461,7 @@ class SmartComponentLayoutComponent {
21571
21461
  const form = this.formRef();
21572
21462
  if (this.smartForm && form && this.parentSmartComponent) {
21573
21463
  this.parentSmartComponent.handleDataChangeSubscriptions(form);
21574
- this.parentSmartComponent.handleLayoutUploadCallback(form);
21464
+ this.parentSmartComponent.handleUploadCallback(form);
21575
21465
  form.markAllWidgetsForChangeDetection();
21576
21466
  }
21577
21467
  });
@@ -24237,6 +24127,140 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
24237
24127
  args: [UiActionToolbarComponent]
24238
24128
  }] } });
24239
24129
 
24130
+ const STRINGS_HU = {
24131
+ startErrorMessage: 'A szerver nem elérhető.',
24132
+ startErrorRetry: 'Újrapróbálás',
24133
+ transportErrorMessage: 'A szerver nem elérhető.',
24134
+ transportErrorReload: 'Újratöltés',
24135
+ sessionErrorMessage: 'A munkamenet lejárt, kérjük jelentkezzen be újra.',
24136
+ sessionErrorAction: 'Bejelentkezés',
24137
+ };
24138
+ const STRINGS_EN = {
24139
+ startErrorMessage: 'The server is unavailable.',
24140
+ startErrorRetry: 'Retry',
24141
+ transportErrorMessage: 'The server is unavailable.',
24142
+ transportErrorReload: 'Reload',
24143
+ sessionErrorMessage: 'Your session has expired. Please log in again.',
24144
+ sessionErrorAction: 'Log in',
24145
+ };
24146
+ /**
24147
+ * Opt-in default UX for `SmartBackendBootstrapService` error hooks.
24148
+ *
24149
+ * Spread the result of `hooks(options)` into `bootstrap.configure()` to get a
24150
+ * MatSnackBar-based "error + retry" UI without writing the host glue:
24151
+ *
24152
+ * ```ts
24153
+ * bootstrap.configure({
24154
+ * ...minimalConfig,
24155
+ * ...defaultErrorUi.hooks({ language: 'hu' }),
24156
+ * });
24157
+ * ```
24158
+ *
24159
+ * Hosts that want custom logic can omit this service and write their own
24160
+ * `onStartError` / `onTransportError` callbacks directly.
24161
+ */
24162
+ class SmartDefaultErrorUiService {
24163
+ constructor() {
24164
+ this.snackBar = inject(MatSnackBar);
24165
+ this.session = inject(SmartSessionService);
24166
+ }
24167
+ /** Returns a hooks bundle ready to spread into `bootstrap.configure()`. */
24168
+ hooks(options = {}) {
24169
+ return {
24170
+ onStartError: (err) => this.showStartError(err, options),
24171
+ onTransportError: (err) => this.showTransportError(err, options),
24172
+ onSessionError: (err) => this.showSessionError(err, options),
24173
+ };
24174
+ }
24175
+ showStartError(_err, options = {}) {
24176
+ const s = this.resolveStrings(options);
24177
+ const ref = this.snackBar.open(s.startErrorMessage, s.startErrorRetry, {
24178
+ duration: 0, // sticky — app is not usable until retry succeeds
24179
+ });
24180
+ // Full reload, not bootstrap.start(). A bootstrap.start() retry only re-runs
24181
+ // the init sequence; any components that already mounted during the failed
24182
+ // attempt have rejected `whenReady()` Promises and won't re-trigger their
24183
+ // run() when bootstrap eventually succeeds. Reload guarantees a clean mount.
24184
+ ref.onAction().subscribe(() => this.verifyServerAndReload());
24185
+ }
24186
+ showTransportError(_err, options = {}) {
24187
+ const s = this.resolveStrings(options);
24188
+ const ref = this.snackBar.open(s.transportErrorMessage, s.transportErrorReload, {
24189
+ duration: options.transportErrorDuration ?? 0,
24190
+ });
24191
+ ref.onAction().subscribe(() => this.verifyServerAndReload());
24192
+ }
24193
+ showSessionError(_err, options = {}) {
24194
+ const s = this.resolveStrings(options);
24195
+ const ref = this.snackBar.open(s.sessionErrorMessage, s.sessionErrorAction, {
24196
+ duration: 0, // sticky — session is invalid until the user takes action
24197
+ });
24198
+ ref.onAction().subscribe(() => this.recoverSession());
24199
+ }
24200
+ /**
24201
+ * Clears the dead session and reloads. Unlike `verifyServerAndReload`, this
24202
+ * does NOT pre-check via `getSession()` — the SID is known to be invalid,
24203
+ * so the check would fail. We best-effort `clearAndStartNewSession()`
24204
+ * (creates a fresh anonymous session); reload happens regardless so the
24205
+ * user is not stuck if the clear fails (e.g., server flapping).
24206
+ *
24207
+ * Overridable in tests.
24208
+ */
24209
+ async recoverSession() {
24210
+ try {
24211
+ await this.session.clearAndStartNewSession();
24212
+ }
24213
+ catch {
24214
+ // Server may also be down; reload anyway and let bootstrap.start() retry.
24215
+ }
24216
+ this.reloadPage();
24217
+ }
24218
+ /**
24219
+ * Pings `getSession()` before reloading: if the server is still down, the
24220
+ * reload would just re-trigger the broken-screen state. The failed check
24221
+ * goes through `SmartErrorCatchingInterceptor` and re-fires `onTransportError`,
24222
+ * so the user sees a fresh snackbar — they get visible feedback that the
24223
+ * server is still unreachable, without paying the cost of a full bundle
24224
+ * reload that lands on the same broken state.
24225
+ *
24226
+ * Overridable in tests.
24227
+ */
24228
+ async verifyServerAndReload() {
24229
+ try {
24230
+ await this.session.getSession();
24231
+ }
24232
+ catch {
24233
+ return; // server still unreachable — interceptor re-opens the snackbar
24234
+ }
24235
+ this.reloadPage();
24236
+ }
24237
+ /** Overridable in tests. */
24238
+ reloadPage() {
24239
+ window.location.reload();
24240
+ }
24241
+ resolveStrings(options) {
24242
+ const lang = this.resolveLanguage(options.language);
24243
+ const base = lang === 'hu' ? STRINGS_HU : STRINGS_EN;
24244
+ return { ...base, ...(options.strings ?? {}) };
24245
+ }
24246
+ resolveLanguage(lang) {
24247
+ if (lang === 'hu' || lang === 'en') {
24248
+ return lang;
24249
+ }
24250
+ return navigator.language?.toLowerCase().startsWith('hu') ? 'hu' : 'en';
24251
+ }
24252
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
24253
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, providedIn: 'root' }); }
24254
+ }
24255
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartDefaultErrorUiService, decorators: [{
24256
+ type: Injectable,
24257
+ args: [{ providedIn: 'root' }]
24258
+ }] });
24259
+
24260
+ /*
24261
+ * Public API Surface of smart-session
24262
+ */
24263
+
24240
24264
  class SmartNavbarService {
24241
24265
  constructor() { }
24242
24266
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartNavbarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }