@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.
- package/esm2022/lib/session/smart-error-catching.interceptor.mjs +3 -3
- package/esm2022/lib/session/smart-session.service.mjs +48 -23
- package/esm2022/lib/smart-component-layout/smart-component-layout.component.mjs +2 -2
- package/fesm2022/smartbit4all-ng-client.mjs +279 -255
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/lib/session/smart-error-catching.interceptor.d.ts +2 -1
- package/lib/session/smart-session.service.d.ts +18 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-6.0.9.tgz +0 -0
- package/smartbit4all-ng-client-6.0.7.tgz +0 -0
|
@@ -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,
|
|
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
|
-
|
|
1050
|
+
const refreshToken = localStorage.getItem('refreshToken');
|
|
1044
1051
|
if (!refreshToken) {
|
|
1045
|
-
|
|
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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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.
|
|
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 }); }
|