@bnsights/bbsf-utilities 1.2.5 → 1.2.8-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/fesm2022/bnsights-bbsf-utilities.mjs +361 -101
- package/fesm2022/bnsights-bbsf-utilities.mjs.map +1 -1
- package/lib/shared/authentication/auth.service.d.ts +10 -0
- package/lib/shared/services/master-layout.service.d.ts +9 -5
- package/lib/shared/services/preload.service.d.ts +5 -0
- package/lib/shared/services/service-worker-helper.service.d.ts +6 -0
- package/package.json +1 -1
|
@@ -12,11 +12,11 @@ import * as i4$1 from 'ngx-cookie-service';
|
|
|
12
12
|
import { CookieService } from 'ngx-cookie-service';
|
|
13
13
|
import * as i1 from '@angular/common/http';
|
|
14
14
|
import { HttpParams, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
|
15
|
-
import { Subject, throwError, of, Observable, lastValueFrom, BehaviorSubject } from 'rxjs';
|
|
15
|
+
import { Subject, switchMap, tap, take, takeUntil, throwError, of, Observable, lastValueFrom, BehaviorSubject } from 'rxjs';
|
|
16
16
|
import { JwtHelperService } from '@auth0/angular-jwt';
|
|
17
17
|
import { __decorate } from 'tslib';
|
|
18
18
|
import { plainToClass } from 'class-transformer';
|
|
19
|
-
import { takeUntil, tap, map, shareReplay, take, filter, switchMap } from 'rxjs/operators';
|
|
19
|
+
import { takeUntil as takeUntil$1, tap as tap$1, map, shareReplay, take as take$1, filter, switchMap as switchMap$1 } from 'rxjs/operators';
|
|
20
20
|
import * as i1$2 from '@angular/service-worker';
|
|
21
21
|
|
|
22
22
|
class User {
|
|
@@ -67,10 +67,6 @@ class ConfigurationService {
|
|
|
67
67
|
constructor(httpClient, injector) {
|
|
68
68
|
this.httpClient = httpClient;
|
|
69
69
|
this.injector = injector;
|
|
70
|
-
this.httpClient.get("./assets/config/configurations.json")
|
|
71
|
-
.subscribe(data => {
|
|
72
|
-
ConfigurationService.JsonData = data;
|
|
73
|
-
});
|
|
74
70
|
}
|
|
75
71
|
get authService() {
|
|
76
72
|
if (!this._authService) {
|
|
@@ -386,39 +382,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImpo
|
|
|
386
382
|
}] });
|
|
387
383
|
|
|
388
384
|
class MasterLayoutService {
|
|
389
|
-
constructor(router, http, authService, stylesBundleService, translate,
|
|
385
|
+
constructor(router, http, authService, stylesBundleService, translate, fileLoaderService) {
|
|
390
386
|
this.router = router;
|
|
391
387
|
this.http = http;
|
|
392
388
|
this.authService = authService;
|
|
393
389
|
this.stylesBundleService = stylesBundleService;
|
|
394
390
|
this.translate = translate;
|
|
395
|
-
this.
|
|
391
|
+
this.fileLoaderService = fileLoaderService;
|
|
396
392
|
this.apiUrl = '/api/Home/';
|
|
393
|
+
this.destroy$ = new Subject();
|
|
397
394
|
}
|
|
398
395
|
switchLang(lang, bundleEnglishName, bundleArabicName) {
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
this.updateUserInfo().subscribe((Value) => {
|
|
402
|
-
let UserInfoObject = Value;
|
|
403
|
-
this.authService.handleAccessToken(UserInfoObject.token);
|
|
404
|
-
this.stylesBundleService.loadThemes(lang, bundleEnglishName, bundleArabicName);
|
|
405
|
-
localStorage.setItem('language', lang);
|
|
406
|
-
this.translate.use(lang);
|
|
407
|
-
});
|
|
408
|
-
});
|
|
396
|
+
if (!lang?.trim()) {
|
|
397
|
+
return;
|
|
409
398
|
}
|
|
410
|
-
|
|
399
|
+
if (!this.authService.isAuthenticated()) {
|
|
411
400
|
this.router.navigate(['/Admin/account/login']);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.changeLanguage(lang).pipe(switchMap(() => this.updateUserInfo()), tap((userInfo) => {
|
|
404
|
+
if (!userInfo?.token) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
this.authService.handleAccessToken(userInfo.token).then(async () => {
|
|
408
|
+
this.stylesBundleService.loadThemes(lang, bundleEnglishName, bundleArabicName);
|
|
409
|
+
localStorage.setItem('language', lang);
|
|
410
|
+
try {
|
|
411
|
+
await this.fileLoaderService.loadLanguage(lang);
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
this.logError(e?.message || `loadLanguage failed for ${lang}`).pipe(take(1)).subscribe();
|
|
415
|
+
}
|
|
416
|
+
this.translate.use(lang);
|
|
417
|
+
});
|
|
418
|
+
}), takeUntil(this.destroy$)).subscribe({
|
|
419
|
+
error: (error) => {
|
|
420
|
+
this.logError(error?.message || 'switchLang failed').pipe(take(1)).subscribe();
|
|
421
|
+
}
|
|
422
|
+
});
|
|
412
423
|
}
|
|
413
424
|
reloadComponent() {
|
|
414
|
-
|
|
425
|
+
const currentUrl = this.router.url;
|
|
426
|
+
const previousShouldReuseRoute = this.router.routeReuseStrategy.shouldReuseRoute;
|
|
427
|
+
const previousOnSameUrlNavigation = this.router.onSameUrlNavigation;
|
|
415
428
|
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
|
416
429
|
this.router.onSameUrlNavigation = 'reload';
|
|
417
|
-
this.router.navigate([currentUrl])
|
|
430
|
+
this.router.navigate([currentUrl]).finally(() => {
|
|
431
|
+
this.router.routeReuseStrategy.shouldReuseRoute = previousShouldReuseRoute;
|
|
432
|
+
this.router.onSameUrlNavigation = previousOnSameUrlNavigation;
|
|
433
|
+
});
|
|
418
434
|
}
|
|
419
435
|
changeLanguage(key) {
|
|
436
|
+
const profile = this.authService.getCurrentUserProfile();
|
|
437
|
+
if (!profile?.id) {
|
|
438
|
+
return this.logError('changeLanguage failed: missing user profile id');
|
|
439
|
+
}
|
|
420
440
|
let params = new HttpParams();
|
|
421
|
-
params = params.append('UserId',
|
|
441
|
+
params = params.append('UserId', profile.id);
|
|
422
442
|
params = params.append('LanguageKey', key);
|
|
423
443
|
return this.http.post(this.apiUrl + 'UpdateLanguage', null, null, params);
|
|
424
444
|
}
|
|
@@ -431,20 +451,34 @@ class MasterLayoutService {
|
|
|
431
451
|
return this.http.get(this.apiUrl + 'UpdateUserInfo', null, null);
|
|
432
452
|
}
|
|
433
453
|
switchRole(permissionSetID) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
454
|
+
if (!permissionSetID?.trim()) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
this.updateRole(permissionSetID).pipe(switchMap(() => this.updateUserInfo()), tap((userInfo) => {
|
|
458
|
+
if (userInfo?.token) {
|
|
459
|
+
this.authService.handleAccessToken(userInfo.token);
|
|
460
|
+
}
|
|
461
|
+
}), takeUntil(this.destroy$)).subscribe({
|
|
462
|
+
error: (error) => {
|
|
463
|
+
this.logError(error?.message || 'switchRole failed').pipe(take(1)).subscribe();
|
|
464
|
+
}
|
|
439
465
|
});
|
|
440
466
|
}
|
|
441
467
|
updateRole(permissionSetID) {
|
|
468
|
+
const profile = this.authService.getCurrentUserProfile();
|
|
469
|
+
if (!profile?.id) {
|
|
470
|
+
return this.logError('updateRole failed: missing user profile id');
|
|
471
|
+
}
|
|
442
472
|
let params = new HttpParams();
|
|
443
|
-
params = params.append('UserId',
|
|
473
|
+
params = params.append('UserId', profile.id);
|
|
444
474
|
params = params.append('RoleID', permissionSetID);
|
|
445
475
|
return this.http.post(this.apiUrl + 'SwitchRole', null, null, params);
|
|
446
476
|
}
|
|
447
|
-
|
|
477
|
+
ngOnDestroy() {
|
|
478
|
+
this.destroy$.next();
|
|
479
|
+
this.destroy$.complete();
|
|
480
|
+
}
|
|
481
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: MasterLayoutService, deps: [{ token: i1$1.Router }, { token: RequestHandlerService }, { token: AuthService }, { token: StylesBundleService }, { token: i4.TranslateService }, { token: FileLoaderService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
448
482
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: MasterLayoutService, providedIn: 'root' }); }
|
|
449
483
|
}
|
|
450
484
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: MasterLayoutService, decorators: [{
|
|
@@ -452,7 +486,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImpo
|
|
|
452
486
|
args: [{
|
|
453
487
|
providedIn: 'root',
|
|
454
488
|
}]
|
|
455
|
-
}], ctorParameters: () => [{ type: i1$1.Router }, { type: RequestHandlerService }, { type: AuthService }, { type: StylesBundleService }, { type: i4.TranslateService }, { type:
|
|
489
|
+
}], ctorParameters: () => [{ type: i1$1.Router }, { type: RequestHandlerService }, { type: AuthService }, { type: StylesBundleService }, { type: i4.TranslateService }, { type: FileLoaderService }] });
|
|
456
490
|
|
|
457
491
|
class RequestHandlerService {
|
|
458
492
|
constructor(http, authService, environmentService, utilityService, bbsfTranslateService) {
|
|
@@ -488,7 +522,7 @@ class RequestHandlerService {
|
|
|
488
522
|
let headers = this.getHeaders();
|
|
489
523
|
if (!currentRequestOptions.disableBlockUI)
|
|
490
524
|
this.utilityService.startBlockUI();
|
|
491
|
-
return this.http.get(this.environmentService.getApiUrl() + Url, { headers: headers, params: params }).pipe(takeUntil(this.onDestroy$), tap((result) => {
|
|
525
|
+
return this.http.get(this.environmentService.getApiUrl() + Url, { headers: headers, params: params }).pipe(takeUntil$1(this.onDestroy$), tap$1((result) => {
|
|
492
526
|
if (!currentRequestOptions.disableBlockUI)
|
|
493
527
|
this.utilityService.stopBlockUI();
|
|
494
528
|
}, error => {
|
|
@@ -513,7 +547,7 @@ class RequestHandlerService {
|
|
|
513
547
|
let headers = this.getHeaders();
|
|
514
548
|
if (!currentRequestOptions.disableBlockUI)
|
|
515
549
|
this.utilityService.startBlockUI();
|
|
516
|
-
return this.http.post(this.environmentService.getApiUrl() + Url, model, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil(this.onDestroy$), tap((result) => {
|
|
550
|
+
return this.http.post(this.environmentService.getApiUrl() + Url, model, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil$1(this.onDestroy$), tap$1((result) => {
|
|
517
551
|
if (!currentRequestOptions.disableBlockUI)
|
|
518
552
|
this.utilityService.stopBlockUI();
|
|
519
553
|
}, error => {
|
|
@@ -538,7 +572,7 @@ class RequestHandlerService {
|
|
|
538
572
|
let headers = this.getHeaders();
|
|
539
573
|
if (!currentRequestOptions.disableBlockUI)
|
|
540
574
|
this.utilityService.startBlockUI();
|
|
541
|
-
return this.http.delete(this.environmentService.getApiUrl() + Url + `/${deletedId}`, { headers: headers, params: params }).pipe(takeUntil(this.onDestroy$), tap((result) => {
|
|
575
|
+
return this.http.delete(this.environmentService.getApiUrl() + Url + `/${deletedId}`, { headers: headers, params: params }).pipe(takeUntil$1(this.onDestroy$), tap$1((result) => {
|
|
542
576
|
if (!currentRequestOptions.disableBlockUI)
|
|
543
577
|
this.utilityService.stopBlockUI();
|
|
544
578
|
}, error => {
|
|
@@ -563,7 +597,7 @@ class RequestHandlerService {
|
|
|
563
597
|
let headers = this.getHeaders();
|
|
564
598
|
if (!currentRequestOptions.disableBlockUI)
|
|
565
599
|
this.utilityService.startBlockUI();
|
|
566
|
-
return this.http.put(this.environmentService.getApiUrl() + Url, model, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil(this.onDestroy$), tap((result) => {
|
|
600
|
+
return this.http.put(this.environmentService.getApiUrl() + Url, model, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil$1(this.onDestroy$), tap$1((result) => {
|
|
567
601
|
if (!currentRequestOptions.disableBlockUI)
|
|
568
602
|
this.utilityService.stopBlockUI();
|
|
569
603
|
}, error => {
|
|
@@ -594,7 +628,7 @@ class RequestHandlerService {
|
|
|
594
628
|
headers = headers.set('ignore-cookies', 'true');
|
|
595
629
|
if (!currentRequestOptions.disableBlockUI)
|
|
596
630
|
this.utilityService.startBlockUI();
|
|
597
|
-
return this.http.patch(this.environmentService.getApiUrl() + Url, model, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil(this.onDestroy$), tap((result) => {
|
|
631
|
+
return this.http.patch(this.environmentService.getApiUrl() + Url, model, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil$1(this.onDestroy$), tap$1((result) => {
|
|
598
632
|
if (!currentRequestOptions.disableBlockUI)
|
|
599
633
|
this.utilityService.stopBlockUI();
|
|
600
634
|
}, error => {
|
|
@@ -619,7 +653,7 @@ class RequestHandlerService {
|
|
|
619
653
|
let headers = this.getHeaders();
|
|
620
654
|
if (!currentRequestOptions.disableBlockUI)
|
|
621
655
|
this.utilityService.startBlockUI();
|
|
622
|
-
return this.http.get(this.environmentService.getApiUrl() + Url, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil(this.onDestroy$), tap((result) => {
|
|
656
|
+
return this.http.get(this.environmentService.getApiUrl() + Url, { headers: headers, params: params, responseType: currentRequestOptions.responseType }).pipe(takeUntil$1(this.onDestroy$), tap$1((result) => {
|
|
623
657
|
if (!currentRequestOptions.disableBlockUI)
|
|
624
658
|
this.utilityService.stopBlockUI();
|
|
625
659
|
}, error => {
|
|
@@ -637,7 +671,7 @@ class RequestHandlerService {
|
|
|
637
671
|
}
|
|
638
672
|
const currentRequestOptions = requestOptions || new RequestOptionsModel();
|
|
639
673
|
let headers = this.getHeadersUpdated();
|
|
640
|
-
return this.http.post(this.environmentService.getApiUrl() + Url, model, { headers: headers, reportProgress: true, observe: 'events' }).pipe(takeUntil(this.onDestroy$), tap((result) => {
|
|
674
|
+
return this.http.post(this.environmentService.getApiUrl() + Url, model, { headers: headers, reportProgress: true, observe: 'events' }).pipe(takeUntil$1(this.onDestroy$), tap$1((result) => {
|
|
641
675
|
if (!currentRequestOptions.disableBlockUI)
|
|
642
676
|
this.utilityService.stopBlockUI();
|
|
643
677
|
}, error => {
|
|
@@ -896,13 +930,13 @@ class LanguageService {
|
|
|
896
930
|
if (this.isLoaded || this.isLoading)
|
|
897
931
|
return; // Sync guard prevents race condition
|
|
898
932
|
this.isLoading = true; // Set immediately to block concurrent calls
|
|
899
|
-
this.loadRequest$ = this.http.get(this.apiUrl + 'GetAll', null, null).pipe(tap((languages) => {
|
|
933
|
+
this.loadRequest$ = this.http.get(this.apiUrl + 'GetAll', null, null).pipe(tap$1((languages) => {
|
|
900
934
|
this._languages = languages;
|
|
901
935
|
this.isLoaded = true;
|
|
902
936
|
this.isLoading = false;
|
|
903
937
|
}), shareReplay(1));
|
|
904
938
|
// take(1) ensures auto-unsubscribe after completion
|
|
905
|
-
this.loadRequest$.pipe(take(1)).subscribe({
|
|
939
|
+
this.loadRequest$.pipe(take$1(1)).subscribe({
|
|
906
940
|
error: (err) => {
|
|
907
941
|
this.isLoading = false;
|
|
908
942
|
this.loadRequest$ = null;
|
|
@@ -1007,8 +1041,23 @@ class FileLoaderService {
|
|
|
1007
1041
|
this.translate = translate;
|
|
1008
1042
|
this.http = http;
|
|
1009
1043
|
this.availableLanguages = ['en', 'ar']; // Add more languages as needed
|
|
1044
|
+
this.loadedLanguages = new Set();
|
|
1010
1045
|
this.translate.addLangs(this.availableLanguages);
|
|
1011
1046
|
}
|
|
1047
|
+
async preloadAll() {
|
|
1048
|
+
const defaultLang = this.getDefaultLanguage();
|
|
1049
|
+
try {
|
|
1050
|
+
await Promise.all([
|
|
1051
|
+
this.loadEnvironment(),
|
|
1052
|
+
this.loadConfigurations(),
|
|
1053
|
+
this.loadLanguage(defaultLang),
|
|
1054
|
+
]);
|
|
1055
|
+
this.translate.use(defaultLang);
|
|
1056
|
+
}
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
console.error('Error during preloadAll:', error);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1012
1061
|
loadEnvironment() {
|
|
1013
1062
|
return new Promise((resolve, reject) => {
|
|
1014
1063
|
const script = document.createElement('script');
|
|
@@ -1026,6 +1075,32 @@ class FileLoaderService {
|
|
|
1026
1075
|
document.head.insertBefore(script, document.head.firstChild);
|
|
1027
1076
|
});
|
|
1028
1077
|
}
|
|
1078
|
+
async loadConfigurations() {
|
|
1079
|
+
const data = await lastValueFrom(this.http.get('./assets/config/configurations.json'));
|
|
1080
|
+
ConfigurationService.JsonData = data;
|
|
1081
|
+
}
|
|
1082
|
+
getDefaultLanguage() {
|
|
1083
|
+
const lang = localStorage.getItem('language');
|
|
1084
|
+
if (lang && this.availableLanguages.includes(lang)) {
|
|
1085
|
+
return lang;
|
|
1086
|
+
}
|
|
1087
|
+
return 'en';
|
|
1088
|
+
}
|
|
1089
|
+
async loadLanguage(lang) {
|
|
1090
|
+
const normalizedLang = (lang || '').trim();
|
|
1091
|
+
if (!normalizedLang) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (!this.availableLanguages.includes(normalizedLang)) {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (this.loadedLanguages.has(normalizedLang)) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const translations = await lastValueFrom(this.http.get(`/assets/i18n/${normalizedLang}.json`));
|
|
1101
|
+
this.translate.setTranslation(normalizedLang, translations, true);
|
|
1102
|
+
this.loadedLanguages.add(normalizedLang);
|
|
1103
|
+
}
|
|
1029
1104
|
async preloadTranslations() {
|
|
1030
1105
|
try {
|
|
1031
1106
|
const requests = this.availableLanguages.map(async (lang) => {
|
|
@@ -1054,7 +1129,12 @@ class ServiceWorkerHelperService {
|
|
|
1054
1129
|
this.updates = updates;
|
|
1055
1130
|
this.swRegistration = null;
|
|
1056
1131
|
this.updateCheckInProgress = false;
|
|
1132
|
+
this.chunkHandlersRegistered = false;
|
|
1133
|
+
this.chunkRecoveryInFlight = false;
|
|
1134
|
+
this.sessionRecoveryAttemptsKey = 'bbsf_chunk_recovery_count';
|
|
1135
|
+
this.maxChunkRecoveryAttempts = 3;
|
|
1057
1136
|
this.initializeServiceWorker();
|
|
1137
|
+
this.setupChunkErrorHandler();
|
|
1058
1138
|
}
|
|
1059
1139
|
checkForUpdates() {
|
|
1060
1140
|
if (!this.updates.isEnabled) {
|
|
@@ -1065,8 +1145,6 @@ class ServiceWorkerHelperService {
|
|
|
1065
1145
|
this.setupUpdateListeners();
|
|
1066
1146
|
// Start smart checking for updates
|
|
1067
1147
|
this.startSmartUpdateChecking();
|
|
1068
|
-
// CRITICAL: Handle chunk load errors
|
|
1069
|
-
this.setupChunkErrorHandler();
|
|
1070
1148
|
}
|
|
1071
1149
|
setupUpdateListeners() {
|
|
1072
1150
|
this.updates.versionUpdates
|
|
@@ -1076,7 +1154,7 @@ class ServiceWorkerHelperService {
|
|
|
1076
1154
|
this.skipWaiting();
|
|
1077
1155
|
});
|
|
1078
1156
|
this.updates.versionUpdates
|
|
1079
|
-
.pipe(filter((event) => event.type === 'VERSION_READY'), switchMap(() => this.updates.activateUpdate()))
|
|
1157
|
+
.pipe(filter((event) => event.type === 'VERSION_READY'), switchMap$1(() => this.updates.activateUpdate()))
|
|
1080
1158
|
.subscribe(() => {
|
|
1081
1159
|
console.log('New version activated, reloading page...');
|
|
1082
1160
|
// CRITICAL FIX: Reload the page after activation
|
|
@@ -1142,38 +1220,92 @@ class ServiceWorkerHelperService {
|
|
|
1142
1220
|
this.swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
|
1143
1221
|
}
|
|
1144
1222
|
}
|
|
1145
|
-
// CRITICAL: Handle chunk load errors
|
|
1146
1223
|
setupChunkErrorHandler() {
|
|
1224
|
+
if (this.chunkHandlersRegistered) {
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
this.chunkHandlersRegistered = true;
|
|
1147
1228
|
window.addEventListener('error', (event) => {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
if (isChunkError) {
|
|
1151
|
-
console.warn('Chunk load error detected, attempting recovery...');
|
|
1152
|
-
event.preventDefault(); // Prevent default error handling
|
|
1153
|
-
this.handleChunkLoadError();
|
|
1229
|
+
if (!this.isChunkErrorEvent(event)) {
|
|
1230
|
+
return;
|
|
1154
1231
|
}
|
|
1232
|
+
console.warn('Chunk load error detected, attempting recovery...');
|
|
1233
|
+
event.preventDefault();
|
|
1234
|
+
this.handleChunkLoadError();
|
|
1155
1235
|
});
|
|
1156
|
-
// Also handle unhandled promise rejections (some chunk errors appear here)
|
|
1157
1236
|
window.addEventListener('unhandledrejection', (event) => {
|
|
1158
|
-
const reason = event.reason
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1237
|
+
const reason = event.reason;
|
|
1238
|
+
const text = (reason && typeof reason === 'object' && 'message' in reason
|
|
1239
|
+
? String(reason.message)
|
|
1240
|
+
: '') || (reason instanceof Error ? reason.message : String(reason ?? ''));
|
|
1241
|
+
const name = reason instanceof Error ? reason.name : '';
|
|
1242
|
+
if (!this.isChunkLoadFailure(reason, text) && name !== 'ChunkLoadError') {
|
|
1243
|
+
return;
|
|
1164
1244
|
}
|
|
1245
|
+
console.warn('Chunk load error in promise, attempting recovery...');
|
|
1246
|
+
event.preventDefault();
|
|
1247
|
+
this.handleChunkLoadError();
|
|
1165
1248
|
});
|
|
1166
1249
|
}
|
|
1250
|
+
isChunkErrorEvent(event) {
|
|
1251
|
+
const ev = event;
|
|
1252
|
+
const message = ev.message || '';
|
|
1253
|
+
const scriptSrc = event.target instanceof HTMLScriptElement ? event.target.src || undefined : undefined;
|
|
1254
|
+
return this.isChunkLoadFailure(ev.error, message, scriptSrc);
|
|
1255
|
+
}
|
|
1256
|
+
isChunkLoadFailure(error, message, scriptSrc) {
|
|
1257
|
+
const segments = [message];
|
|
1258
|
+
if (error instanceof Error) {
|
|
1259
|
+
segments.push(error.message, error.name);
|
|
1260
|
+
}
|
|
1261
|
+
else if (error && typeof error === 'object' && 'message' in error) {
|
|
1262
|
+
segments.push(String(error.message));
|
|
1263
|
+
}
|
|
1264
|
+
else if (error != null) {
|
|
1265
|
+
segments.push(String(error));
|
|
1266
|
+
}
|
|
1267
|
+
const text = segments.join(' ');
|
|
1268
|
+
if (/Loading chunk .+ failed/i.test(text)) {
|
|
1269
|
+
return true;
|
|
1270
|
+
}
|
|
1271
|
+
if (/ChunkLoadError/i.test(text)) {
|
|
1272
|
+
return true;
|
|
1273
|
+
}
|
|
1274
|
+
if (/Failed to fetch dynamically imported module/i.test(text)) {
|
|
1275
|
+
return true;
|
|
1276
|
+
}
|
|
1277
|
+
if (scriptSrc && /\.js(\?|#|$)/i.test(scriptSrc)) {
|
|
1278
|
+
const looksLikeBundle = /chunk|main|runtime|polyfills|vendor/i.test(scriptSrc) ||
|
|
1279
|
+
/\/\d+\.[\w.-]*\.js(\?|#|$)/i.test(scriptSrc);
|
|
1280
|
+
const opaqueOrEmpty = !message || message === 'Script error.';
|
|
1281
|
+
if (looksLikeBundle && opaqueOrEmpty) {
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1167
1287
|
handleChunkLoadError() {
|
|
1168
|
-
|
|
1169
|
-
const lastReloadTime = localStorage.getItem('lastChunkErrorReload');
|
|
1170
|
-
const now = Date.now();
|
|
1171
|
-
if (lastReloadTime && (now - parseInt(lastReloadTime, 10)) < 5000) {
|
|
1172
|
-
console.error('Multiple chunk errors in short time, skipping auto-reload');
|
|
1288
|
+
if (this.chunkRecoveryInFlight) {
|
|
1173
1289
|
return;
|
|
1174
1290
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1291
|
+
let attempts = 0;
|
|
1292
|
+
try {
|
|
1293
|
+
attempts = parseInt(sessionStorage.getItem(this.sessionRecoveryAttemptsKey) || '0', 10);
|
|
1294
|
+
}
|
|
1295
|
+
catch {
|
|
1296
|
+
/* private / blocked storage */
|
|
1297
|
+
}
|
|
1298
|
+
if (attempts >= this.maxChunkRecoveryAttempts) {
|
|
1299
|
+
console.error('Chunk error recovery limit reached for this session; not reloading again.');
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
try {
|
|
1303
|
+
sessionStorage.setItem(this.sessionRecoveryAttemptsKey, String(attempts + 1));
|
|
1304
|
+
}
|
|
1305
|
+
catch {
|
|
1306
|
+
/* ignore */
|
|
1307
|
+
}
|
|
1308
|
+
this.chunkRecoveryInFlight = true;
|
|
1177
1309
|
this.clearCacheAndReload();
|
|
1178
1310
|
}
|
|
1179
1311
|
clearCacheAndReload() {
|
|
@@ -1222,15 +1354,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImpo
|
|
|
1222
1354
|
class AuthService {
|
|
1223
1355
|
static { this.user = null; }
|
|
1224
1356
|
get user() {
|
|
1225
|
-
|
|
1226
|
-
|
|
1357
|
+
// Always use static as source of truth
|
|
1358
|
+
// Only hydrate if static is null
|
|
1359
|
+
if (!AuthService.user) {
|
|
1360
|
+
this.getUserManager(); // This will set AuthService.user
|
|
1227
1361
|
}
|
|
1228
|
-
return
|
|
1362
|
+
return AuthService.user; // Always return fresh static value
|
|
1229
1363
|
}
|
|
1230
1364
|
static { this.UserClaims = null; }
|
|
1231
1365
|
//refresh
|
|
1232
1366
|
static { this.timers = []; }
|
|
1233
1367
|
static { this.seconds = 0; }
|
|
1368
|
+
static { this.isLoginInProgress = false; }
|
|
1234
1369
|
constructor(injector, http, environmentService, translateService, router, cookieService, utilityService) {
|
|
1235
1370
|
this.injector = injector;
|
|
1236
1371
|
this.http = http;
|
|
@@ -1243,6 +1378,9 @@ class AuthService {
|
|
|
1243
1378
|
this.jwtHelper = new JwtHelperService();
|
|
1244
1379
|
this.isAuthenticatedSubject = new BehaviorSubject(this.hasToken());
|
|
1245
1380
|
this.isAuthenticate$ = this.isAuthenticatedSubject.asObservable();
|
|
1381
|
+
// User cache to prevent inconsistent multi-call results
|
|
1382
|
+
this._userCache = null;
|
|
1383
|
+
this.USER_CACHE_TTL = 100; // ms
|
|
1246
1384
|
// Listen for logout events from other tabs
|
|
1247
1385
|
window.addEventListener('storage', (event) => {
|
|
1248
1386
|
if (event.key === 'auth_logout_broadcast' && event.newValue) {
|
|
@@ -1262,6 +1400,11 @@ class AuthService {
|
|
|
1262
1400
|
return token && !this.jwtHelper.isTokenExpired(token);
|
|
1263
1401
|
}
|
|
1264
1402
|
getUserManager() {
|
|
1403
|
+
const now = Date.now();
|
|
1404
|
+
// Return cached user if fresh (within 100ms TTL)
|
|
1405
|
+
if (this._userCache && now - this._userCache.timestamp < this.USER_CACHE_TTL) {
|
|
1406
|
+
return this._userCache.user;
|
|
1407
|
+
}
|
|
1265
1408
|
const token = this.cookieService.get(this.TOKEN_KEY);
|
|
1266
1409
|
if (token) {
|
|
1267
1410
|
try {
|
|
@@ -1270,10 +1413,7 @@ class AuthService {
|
|
|
1270
1413
|
const expiryDate = this.jwtHelper.getTokenExpirationDate(token);
|
|
1271
1414
|
const hasTimeLeft = expiryDate && expiryDate.getTime() > Date.now();
|
|
1272
1415
|
if (!isExpired && hasTimeLeft) {
|
|
1273
|
-
|
|
1274
|
-
if (!AuthService.user || AuthService.user.access_token !== token) {
|
|
1275
|
-
this.handleAccessTokenWithoutLanguage(token);
|
|
1276
|
-
}
|
|
1416
|
+
this.handleAccessTokenWithoutLanguage(token);
|
|
1277
1417
|
}
|
|
1278
1418
|
else {
|
|
1279
1419
|
this.clearLocalAuthState({ keepRedirectUrl: true });
|
|
@@ -1287,13 +1427,17 @@ class AuthService {
|
|
|
1287
1427
|
else {
|
|
1288
1428
|
this.clearLocalAuthState({ keepRedirectUrl: true });
|
|
1289
1429
|
}
|
|
1430
|
+
// Update cache with current user state
|
|
1431
|
+
this._userCache = { user: AuthService.user, timestamp: now };
|
|
1290
1432
|
return AuthService.user;
|
|
1291
1433
|
}
|
|
1292
1434
|
getUser() {
|
|
1293
|
-
|
|
1435
|
+
// No-op: getter now always reads from static
|
|
1436
|
+
// Kept for backward compatibility
|
|
1294
1437
|
}
|
|
1295
1438
|
storUser(User) {
|
|
1296
|
-
|
|
1439
|
+
// Getter now always reads from static, so we don't need to sync
|
|
1440
|
+
// This method appears unused but kept for backward compatibility
|
|
1297
1441
|
}
|
|
1298
1442
|
getCurrentUser() {
|
|
1299
1443
|
return AuthService.user;
|
|
@@ -1308,13 +1452,17 @@ class AuthService {
|
|
|
1308
1452
|
return AuthService.user?.profile ?? null;
|
|
1309
1453
|
}
|
|
1310
1454
|
isAuthenticated() {
|
|
1311
|
-
|
|
1312
|
-
if (!
|
|
1455
|
+
const user = this.user; // Call getter to ensure hydrated
|
|
1456
|
+
if (!user) {
|
|
1313
1457
|
return false;
|
|
1458
|
+
}
|
|
1314
1459
|
// Add 10-second grace period to prevent race conditions
|
|
1315
|
-
const expiryDate = new Date(
|
|
1460
|
+
const expiryDate = new Date(user.expires_at);
|
|
1316
1461
|
const nowWithBuffer = new Date(Date.now() + 10000);
|
|
1317
|
-
|
|
1462
|
+
const now = new Date();
|
|
1463
|
+
const secondsUntilExpiry = (expiryDate.getTime() - now.getTime()) / 1000;
|
|
1464
|
+
const result = expiryDate.getTime() > nowWithBuffer.getTime();
|
|
1465
|
+
return result;
|
|
1318
1466
|
}
|
|
1319
1467
|
isUserInRole(allowedPermission) {
|
|
1320
1468
|
const profile = this.getCurrentUserProfile();
|
|
@@ -1362,8 +1510,12 @@ class AuthService {
|
|
|
1362
1510
|
*/
|
|
1363
1511
|
clearLocalAuthState(options) {
|
|
1364
1512
|
// Clear timers
|
|
1365
|
-
AuthService.timers.
|
|
1513
|
+
AuthService.timers.forEach((t) => {
|
|
1514
|
+
clearInterval(t);
|
|
1515
|
+
});
|
|
1366
1516
|
AuthService.timers = [];
|
|
1517
|
+
// Reset seconds counter to prevent stale timers
|
|
1518
|
+
AuthService.seconds = 0;
|
|
1367
1519
|
// Clear memory
|
|
1368
1520
|
AuthService.user = null;
|
|
1369
1521
|
this._user = null;
|
|
@@ -1374,6 +1526,8 @@ class AuthService {
|
|
|
1374
1526
|
if (options?.keepRedirectUrl !== true) {
|
|
1375
1527
|
localStorage.removeItem('redirectUrl');
|
|
1376
1528
|
}
|
|
1529
|
+
// Invalidate user cache since auth state is cleared
|
|
1530
|
+
this._userCache = null;
|
|
1377
1531
|
this.isAuthenticatedSubject.next(false);
|
|
1378
1532
|
}
|
|
1379
1533
|
signOut() {
|
|
@@ -1412,24 +1566,37 @@ class AuthService {
|
|
|
1412
1566
|
return this.http.get(this.environmentService.getBaseUrl() + ApiUrl + 'ClearCurrentUserSession', httpOptions);
|
|
1413
1567
|
}
|
|
1414
1568
|
async handleAccessToken(response) {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1569
|
+
AuthService.isLoginInProgress = true;
|
|
1570
|
+
try {
|
|
1571
|
+
const token = response;
|
|
1572
|
+
// AGGRESSIVE timer cleanup before starting new session
|
|
1573
|
+
AuthService.timers.map(t => clearInterval(t));
|
|
1574
|
+
AuthService.timers = [];
|
|
1575
|
+
AuthService.seconds = 0; // Reset counter
|
|
1576
|
+
// Brief pause to ensure intervals are cleared
|
|
1577
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1578
|
+
// Update user atomically
|
|
1579
|
+
AuthService.user = new User();
|
|
1580
|
+
AuthService.user.token_type = "Bearer";
|
|
1581
|
+
AuthService.user.access_token = token;
|
|
1582
|
+
AuthService.user.profile = this.jwtHelper.decodeToken(token);
|
|
1583
|
+
AuthService.user.expires_at = this.jwtHelper.getTokenExpirationDate(token);
|
|
1584
|
+
// Add 5-minute buffer to cookie expiry to prevent browser deletion before refresh
|
|
1585
|
+
// JWT expiry in isAuthenticated() remains authoritative
|
|
1586
|
+
const cookieExpiry = new Date(AuthService.user.expires_at.getTime() + (5 * 60 * 1000));
|
|
1587
|
+
// Update cookie atomically (no deletion window)
|
|
1588
|
+
this.cookieService.set(this.TOKEN_KEY, token, cookieExpiry, "/", null, true, 'Lax');
|
|
1589
|
+
localStorage.setItem("language", AuthService.user.profile.locale);
|
|
1590
|
+
this.isAuthenticatedSubject.next(true);
|
|
1591
|
+
// Invalidate user cache since we have a new token
|
|
1592
|
+
this._userCache = null;
|
|
1593
|
+
this.setTokenSeconds();
|
|
1594
|
+
AuthService.timers.push(this.checkRefreshToken());
|
|
1595
|
+
await this.updateLanguage();
|
|
1596
|
+
}
|
|
1597
|
+
finally {
|
|
1598
|
+
AuthService.isLoginInProgress = false;
|
|
1599
|
+
}
|
|
1433
1600
|
}
|
|
1434
1601
|
handleAccessTokenWithoutLanguage(response) {
|
|
1435
1602
|
const token = response;
|
|
@@ -1442,11 +1609,15 @@ class AuthService {
|
|
|
1442
1609
|
AuthService.user.access_token = token;
|
|
1443
1610
|
AuthService.user.profile = this.jwtHelper.decodeToken(token);
|
|
1444
1611
|
AuthService.user.expires_at = this.jwtHelper.getTokenExpirationDate(token);
|
|
1445
|
-
|
|
1612
|
+
// Add 5-minute buffer to cookie expiry to prevent browser deletion before refresh
|
|
1613
|
+
// JWT expiry in isAuthenticated() remains authoritative
|
|
1614
|
+
const cookieExpiry = new Date(AuthService.user.expires_at.getTime() + (5 * 60 * 1000));
|
|
1446
1615
|
// Update cookie atomically (no deletion window)
|
|
1447
|
-
this.cookieService.set(this.TOKEN_KEY, token,
|
|
1616
|
+
this.cookieService.set(this.TOKEN_KEY, token, cookieExpiry, "/", null, true, 'Lax');
|
|
1448
1617
|
localStorage.setItem("language", AuthService.user.profile.locale);
|
|
1449
1618
|
this.isAuthenticatedSubject.next(true);
|
|
1619
|
+
// Invalidate user cache since we have a new token
|
|
1620
|
+
this._userCache = null;
|
|
1450
1621
|
this.setTokenSeconds();
|
|
1451
1622
|
AuthService.timers.push(this.checkRefreshToken());
|
|
1452
1623
|
}
|
|
@@ -1464,23 +1635,68 @@ class AuthService {
|
|
|
1464
1635
|
}
|
|
1465
1636
|
checkRefreshToken() {
|
|
1466
1637
|
let date = new Date();
|
|
1467
|
-
|
|
1468
|
-
if
|
|
1638
|
+
const timerId = setInterval(() => {
|
|
1639
|
+
// Early exit if user logged out or no user exists
|
|
1640
|
+
if (!AuthService.user) {
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
const secondsRemaining = Math.floor(AuthService.seconds);
|
|
1644
|
+
// Early exit if seconds is invalid (negative or zero means already expired/cleared)
|
|
1645
|
+
if (secondsRemaining <= 0) {
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
// Refresh when 30 seconds remain (instead of 120)
|
|
1649
|
+
// This prevents continuous refresh loops with short-lived tokens
|
|
1650
|
+
// Timer is cleared after triggering to ensure only ONE refresh per token cycle
|
|
1651
|
+
if (secondsRemaining <= 30 && secondsRemaining > 0 && this.isAuthenticated()) {
|
|
1652
|
+
// Double-check user still exists before refreshing
|
|
1653
|
+
if (!AuthService.user) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1469
1656
|
AuthService.timers.map(t => clearInterval(t));
|
|
1470
1657
|
AuthService.timers = [];
|
|
1471
1658
|
const token = this.cookieService.get(this.TOKEN_KEY);
|
|
1472
1659
|
if (token) {
|
|
1473
1660
|
this.refresh();
|
|
1474
1661
|
}
|
|
1662
|
+
else {
|
|
1663
|
+
// Defensive: Token disappeared between isAuthenticated() check and now
|
|
1664
|
+
console.error('[REFRESH-TIMER] Token disappeared! Attempting recovery...', {
|
|
1665
|
+
timestamp: new Date().toISOString(),
|
|
1666
|
+
secondsRemaining,
|
|
1667
|
+
wasAuthenticated: true
|
|
1668
|
+
});
|
|
1669
|
+
// Try one more time after short delay
|
|
1670
|
+
setTimeout(() => {
|
|
1671
|
+
const retryToken = this.cookieService.get(this.TOKEN_KEY);
|
|
1672
|
+
if (retryToken) {
|
|
1673
|
+
this.refresh();
|
|
1674
|
+
}
|
|
1675
|
+
else {
|
|
1676
|
+
console.error('[REFRESH-TIMER] Token still missing after retry');
|
|
1677
|
+
// Don't clear auth state here - let natural expiry handle it
|
|
1678
|
+
// This prevents premature logout if user is still active
|
|
1679
|
+
}
|
|
1680
|
+
}, 100);
|
|
1681
|
+
}
|
|
1475
1682
|
}
|
|
1476
1683
|
AuthService.seconds--;
|
|
1477
1684
|
}, 1000);
|
|
1685
|
+
return timerId;
|
|
1478
1686
|
}
|
|
1479
1687
|
setTokenSeconds() {
|
|
1480
1688
|
let date = new Date();
|
|
1481
1689
|
AuthService.seconds = (AuthService.user.expires_at - date) / 1000;
|
|
1482
1690
|
}
|
|
1483
1691
|
refresh() {
|
|
1692
|
+
// Don't refresh if login is in progress
|
|
1693
|
+
if (AuthService.isLoginInProgress) {
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
// Don't refresh if user logged out
|
|
1697
|
+
if (!AuthService.user) {
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1484
1700
|
const refreshLockKey = 'auth_refreshing_lock';
|
|
1485
1701
|
const lockValue = localStorage.getItem(refreshLockKey);
|
|
1486
1702
|
// Check if another tab is refreshing (within last 5 seconds)
|
|
@@ -1489,6 +1705,10 @@ class AuthService {
|
|
|
1489
1705
|
}
|
|
1490
1706
|
// Acquire lock
|
|
1491
1707
|
localStorage.setItem(refreshLockKey, Date.now().toString());
|
|
1708
|
+
// IMPORTANT: Don't delete cookie before refresh to prevent:
|
|
1709
|
+
// 1. Race condition window where no token exists
|
|
1710
|
+
// 2. Repeated cookie deletion/recreation cycles with short-lived tokens
|
|
1711
|
+
// The cookie will be atomically replaced by handleAccessTokenWithoutLanguage()
|
|
1492
1712
|
const httpOptions = {
|
|
1493
1713
|
headers: new HttpHeaders({
|
|
1494
1714
|
'Content-Type': 'application/json',
|
|
@@ -1498,16 +1718,56 @@ class AuthService {
|
|
|
1498
1718
|
let ApiUrl = '/api/Home/';
|
|
1499
1719
|
this.http.get(this.environmentService.getApiUrl() + ApiUrl + 'RefreshAccessToken', httpOptions).subscribe({
|
|
1500
1720
|
next: (res) => {
|
|
1721
|
+
// Check if user logged out during HTTP call
|
|
1722
|
+
if (!AuthService.user) {
|
|
1723
|
+
localStorage.removeItem(refreshLockKey);
|
|
1724
|
+
return; // Abort - don't set new token
|
|
1725
|
+
}
|
|
1726
|
+
// Atomically replaces the old cookie with new token
|
|
1501
1727
|
this.handleAccessTokenWithoutLanguage(res.val);
|
|
1502
1728
|
localStorage.removeItem(refreshLockKey);
|
|
1729
|
+
// Auto-navigate if user was redirected to login during token expiry
|
|
1730
|
+
// this.handlePostRefreshNavigation();
|
|
1503
1731
|
},
|
|
1504
1732
|
error: (err) => {
|
|
1505
|
-
// Only clear auth state if refresh actually failed
|
|
1506
|
-
this.clearLocalAuthState({ keepRedirectUrl: true });
|
|
1507
1733
|
localStorage.removeItem(refreshLockKey);
|
|
1734
|
+
// On error, old cookie remains valid until natural expiry
|
|
1508
1735
|
}
|
|
1509
1736
|
});
|
|
1510
1737
|
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Handles navigation after successful token refresh.
|
|
1740
|
+
* If user is on login page and has a stored redirect URL, navigates to that URL.
|
|
1741
|
+
* This resolves the issue where users remain on login page after token refresh.
|
|
1742
|
+
* NOTE: This is not used anymore because we are using the async guard to wait for the refresh to complete before navigating to the protected route.
|
|
1743
|
+
*/
|
|
1744
|
+
handlePostRefreshNavigation() {
|
|
1745
|
+
// Get the stored redirect URL
|
|
1746
|
+
const redirectUrl = this.getUrl(); // Returns localStorage 'redirectUrl' or "/"
|
|
1747
|
+
// Get current URL
|
|
1748
|
+
const currentUrl = this.router.url;
|
|
1749
|
+
// Check if we're currently on the login page
|
|
1750
|
+
const isOnLoginPage = currentUrl.toLowerCase().includes('login');
|
|
1751
|
+
const isAuth = this.isAuthenticated();
|
|
1752
|
+
// More lenient conditions:
|
|
1753
|
+
// 1. User is authenticated
|
|
1754
|
+
// 2. User is on login page
|
|
1755
|
+
// 3. There's ANY redirect URL (even "/")
|
|
1756
|
+
if (isAuth && isOnLoginPage) {
|
|
1757
|
+
// Determine target URL
|
|
1758
|
+
const targetUrl = redirectUrl && !redirectUrl.toLowerCase().includes('login')
|
|
1759
|
+
? redirectUrl
|
|
1760
|
+
: '/Admin/Home'; // Default fallback instead of rejecting "/"
|
|
1761
|
+
// Clear the stored redirect URL first
|
|
1762
|
+
localStorage.removeItem('redirectUrl');
|
|
1763
|
+
// Use setTimeout to ensure router has finished current navigation
|
|
1764
|
+
// This prevents competing navigation attempts
|
|
1765
|
+
setTimeout(() => {
|
|
1766
|
+
this.router.navigate([targetUrl]);
|
|
1767
|
+
}, 100);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
//#region uaepass methods
|
|
1511
1771
|
loginWithUAEPass() {
|
|
1512
1772
|
const authEndpoint = `${this.environmentService.getUAEPassBaseUrl()}${this.environmentService.getUAEPassAuthorizationEndPoint()}`;
|
|
1513
1773
|
const queryParams = {
|