@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.
- 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-form/api/model/models.mjs +1 -1
- package/esm2022/lib/view-context/api/api/view.service.mjs +46 -1
- package/esm2022/lib/view-context/api/model/models.mjs +2 -1
- package/esm2022/lib/view-context/api/model/publishedViewData.mjs +13 -0
- package/esm2022/lib/view-context/api/model/view.mjs +1 -1
- package/esm2022/lib/view-context/api/model/viewData.mjs +1 -1
- package/fesm2022/smartbit4all-ng-client.mjs +426 -345
- 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/lib/view-context/api/api/view.service.d.ts +20 -0
- package/lib/view-context/api/model/models.d.ts +1 -0
- package/lib/view-context/api/model/publishedViewData.d.ts +14 -0
- package/lib/view-context/api/model/view.d.ts +1 -0
- package/lib/view-context/api/model/viewData.d.ts +1 -0
- package/package.json +1 -1
- package/smartbit4all-ng-client-4.5.34.tgz +0 -0
- package/smartbit4all-ng-client-4.5.32.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';
|
|
@@ -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
|
-
|
|
1051
|
+
const refreshToken = localStorage.getItem('refreshToken');
|
|
1045
1052
|
if (!refreshToken) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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
|
-
|
|
1063
|
-
|
|
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
|
|
13469
|
-
constructor(
|
|
13470
|
-
this.
|
|
13491
|
+
class SmartErrorCatchingInterceptor {
|
|
13492
|
+
constructor(session, bootstrap) {
|
|
13493
|
+
this.session = session;
|
|
13471
13494
|
this.bootstrap = bootstrap;
|
|
13472
|
-
this.
|
|
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
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
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
|
-
|
|
13484
|
-
|
|
13485
|
-
this._destroy$.complete();
|
|
13560
|
+
isSessionError(error) {
|
|
13561
|
+
return error.status === 400 && error.error.code != null;
|
|
13486
13562
|
}
|
|
13487
|
-
|
|
13488
|
-
|
|
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
|
-
|
|
13493
|
-
|
|
13494
|
-
}
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
}
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
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 }); }
|