@open-rlb/ng-app 3.1.28 → 3.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i1$2 from '@angular/common/http';
2
2
  import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
3
3
  import * as i0 from '@angular/core';
4
- import { InjectionToken, Injectable, Inject, Optional, inject, EventEmitter, importProvidersFrom, makeStateKey, makeEnvironmentProviders, Pipe, Input, Component, PLATFORM_ID, NgModule, Directive, isDevMode, provideAppInitializer } from '@angular/core';
4
+ import { InjectionToken, Injectable, Inject, Optional, inject, EventEmitter, importProvidersFrom, makeStateKey, makeEnvironmentProviders, Pipe, Input, Component, PLATFORM_ID, TemplateRef, ViewContainerRef, input, effect, Directive, NgModule, isDevMode, provideAppInitializer } from '@angular/core';
5
5
  import * as i2 from '@angular/router';
6
6
  import { NavigationEnd, RoutesRecognized, RouterModule, Router, provideRouter } from '@angular/router';
7
7
  import * as i1$4 from '@angular/service-worker';
@@ -15,9 +15,8 @@ import { RLB_TRANSLATION_SERVICE, RlbBootstrapModule, ModalDirective, ToastDirec
15
15
  import * as i1$5 from 'angular-auth-oidc-client';
16
16
  import { AbstractLoggerService, AuthModule, provideAuth, AuthInterceptor, AbstractSecurityStorage } from 'angular-auth-oidc-client';
17
17
  import * as i1 from 'ngx-cookie-service-ssr';
18
- import { of, filter, switchMap, map, BehaviorSubject, share, lastValueFrom, from, zip, EMPTY, catchError, Observable, tap, shareReplay, distinctUntilChanged, take } from 'rxjs';
18
+ import { of, tap, switchMap, from, map, catchError, filter, BehaviorSubject, share, lastValueFrom, zip, EMPTY, Observable, shareReplay, distinctUntilChanged, take } from 'rxjs';
19
19
  import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';
20
- import { tapResponse } from '@ngrx/operators';
21
20
  import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
22
21
  import * as i1$3 from '@ngx-translate/core';
23
22
  import { TranslateModule } from '@ngx-translate/core';
@@ -452,31 +451,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
452
451
  args: [RLB_CFG_ACL]
453
452
  }] }] });
454
453
 
454
+ const RLB_INIT_PROVIDER = new InjectionToken(`${RLB_CFG}:init.provider`);
455
+
455
456
  //import { ErrorManagementService } from "../../services/errors/error-management.service";
456
- const AclStore = signalStore({ providedIn: 'root' }, withState(initialAclState), withMethods((store, adminApi = inject(AdminApiService)) => ({
457
- // REUSABLE LOGIC: Will be used in guard
458
- hasPermission: (resourceName, action) => {
457
+ const AclStore = signalStore({ providedIn: 'root' }, withState(initialAclState), withMethods((store, adminApi = inject(AdminApiService), baseStore = inject((Store)), rlbInitProvider = inject(RLB_INIT_PROVIDER, { optional: true }), aclConfiguration = inject(RLB_CFG_ACL, { optional: true })) => ({
458
+ hasPermission: (busId, resId, action) => {
459
459
  const resources = store.resources();
460
460
  if (!resources)
461
461
  return false;
462
- return resources.some(company => company.resourceBusinessId === resourceName && company.resources.some(res => {
463
- const matchName = res.resourceId === resourceName;
464
- if (!action)
465
- return matchName;
466
- return matchName && res.actions.includes(action);
467
- }));
462
+ return resources.some(company => company.resourceBusinessId === busId &&
463
+ company.resources.some(res => {
464
+ const matchRes = res.resourceId === resId;
465
+ return action ? (matchRes && res.actions.includes(action)) : matchRes;
466
+ }));
468
467
  },
469
- // Replaces legacy ngrx action and the effect
468
+ // Load Logic: Replaces both Effects. Get user res + call via bridge finalizeApps
470
469
  loadACL() {
471
470
  patchState(store, { loading: true, loaded: false });
472
- return adminApi.resourcesByUser$().pipe(
473
- //errorManagement.manageUI('error', 'dialog'),
474
- tapResponse({
475
- next: (resources) => patchState(store, { resources, loaded: true, loading: false }),
476
- error: (error) => {
477
- console.error('ACL Load Failed', error);
478
- patchState(store, { error, loaded: true, loading: false });
479
- },
471
+ return adminApi.resourcesByUser$().pipe(tap(resources => patchState(store, { resources, loaded: true, loading: false })), switchMap((resources) => {
472
+ if (!rlbInitProvider) {
473
+ console.error("RlbInitProvider not found. Define RLB_INIT_PROVIDER");
474
+ return of(resources);
475
+ }
476
+ // Pass resources directly to the finalizer to prevent race condition problem
477
+ return from(rlbInitProvider.finalizeApps(resources, baseStore, aclConfiguration)).pipe(map(() => resources), catchError((err) => {
478
+ console.error('Finalization failed', err);
479
+ return of(resources);
480
+ }));
480
481
  }));
481
482
  },
482
483
  reset: () => patchState(store, initialAclState)
@@ -511,6 +512,7 @@ class AppsService {
511
512
  this.loggerService = loggerService;
512
513
  this.confAuth = confAuth;
513
514
  this.confAcl = confAcl;
515
+ this.aclStore = inject(AclStore); // Inject SignalStore here
514
516
  this.logger = this.loggerService.for(this.constructor.name);
515
517
  this.logger.log('AppsService initialized');
516
518
  this.initAuthProviders(store, confAuth);
@@ -521,7 +523,7 @@ class AppsService {
521
523
  }
522
524
  get apps() {
523
525
  const apps = this.store.selectSignal(state => state[appContextFeatureKey].apps)();
524
- const resources = this.store.selectSignal(state => state[aclFeatureKey].resources)();
526
+ const resources = this.aclStore.resources();
525
527
  const confAcl = this.confAcl;
526
528
  return apps.filter(app => {
527
529
  // Basic domain check
@@ -553,11 +555,26 @@ class AppsService {
553
555
  });
554
556
  });
555
557
  }
558
+ get currentAppAclInfo() {
559
+ const app = this.currentApp;
560
+ if (!app || !app.data || !this.confAcl)
561
+ return null;
562
+ return {
563
+ busId: app.data[this.confAcl.businessIdKey],
564
+ resId: app.data[this.confAcl.resourceIdKey]
565
+ };
566
+ }
556
567
  get currentApp() {
557
568
  const app = this.store.selectSignal(state => state[appContextFeatureKey].currentApp)();
558
569
  this.logger.log('Current app from store:', app);
559
570
  return app;
560
571
  }
572
+ checkPermissionInCurrentApp(action) {
573
+ const info = this.currentAppAclInfo;
574
+ if (!info)
575
+ return false;
576
+ return this.aclStore.hasPermission(info.busId, info.resId, action);
577
+ }
561
578
  selectApp(app, viewMode, url) {
562
579
  const currentApp = this.currentApp;
563
580
  if (!app) {
@@ -925,8 +942,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
925
942
  args: [{ providedIn: 'root' }]
926
943
  }], ctorParameters: () => [{ type: i2.Router }, { type: i2.ActivatedRoute }, { type: AppLoggerService }, { type: LanguageService }, { type: i2$1.UniqueIdService }] });
927
944
 
928
- const RLB_INIT_PROVIDER = new InjectionToken(`${RLB_CFG}:init.provider`);
929
-
930
945
  class LocalCacheService {
931
946
  constructor(options) {
932
947
  this.options = options;
@@ -1526,16 +1541,9 @@ class AuthenticationService {
1526
1541
  this.store.dispatch(AuthActions.setCurrentProvider({
1527
1542
  currentProvider: authenticatedConfig.configId
1528
1543
  }));
1529
- if (this.appconfig.acl) {
1530
- // SignalStore methods can trigger the API call
1531
- return this.aclStore.loadACL().pipe(tap(() => this.handleRedirect()), map(() => responses));
1532
- }
1533
- else {
1534
- this.handleRedirect();
1535
- return of(responses);
1536
- }
1537
1544
  }
1538
- return of(responses);
1545
+ // SignalStore methods can trigger the API call
1546
+ return this.aclStore.loadACL().pipe(tap(() => this.handleRedirect()), map(() => responses));
1539
1547
  }));
1540
1548
  }
1541
1549
  login(targetUrl) {
@@ -2289,6 +2297,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2289
2297
  args: ['toast-container-ids']
2290
2298
  }] } });
2291
2299
 
2300
+ class RlbRole {
2301
+ constructor() {
2302
+ this.appsService = inject(AppsService);
2303
+ this.templateRef = inject((TemplateRef));
2304
+ this.viewContainer = inject(ViewContainerRef);
2305
+ this.action = input(undefined, { ...(ngDevMode ? { debugName: "action" } : {}), alias: 'roles' });
2306
+ // Effect automatically re-runs if store.resources or inputs change
2307
+ effect(() => {
2308
+ const action = this.action() || '';
2309
+ const hasPerm = this.appsService.checkPermissionInCurrentApp(action);
2310
+ this.viewContainer.clear();
2311
+ if (hasPerm) {
2312
+ this.viewContainer.createEmbeddedView(this.templateRef);
2313
+ }
2314
+ });
2315
+ }
2316
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbRole, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2317
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.4", type: RlbRole, isStandalone: false, selector: "[roles]", inputs: { action: { classPropertyName: "action", publicName: "roles", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
2318
+ }
2319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbRole, decorators: [{
2320
+ type: Directive,
2321
+ args: [{
2322
+ selector: '[roles]',
2323
+ standalone: false
2324
+ }]
2325
+ }], ctorParameters: () => [], propDecorators: { action: [{ type: i0.Input, args: [{ isSignal: true, alias: "roles", required: false }] }] } });
2326
+
2292
2327
  class RlbAppModule {
2293
2328
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbAppModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2294
2329
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.4", ngImport: i0, type: RlbAppModule, declarations: [
@@ -2312,7 +2347,9 @@ class RlbAppModule {
2312
2347
  CmsComponent,
2313
2348
  ContentComponent,
2314
2349
  AppTemplateComponent,
2315
- AppContainerComponent], imports: [CommonModule,
2350
+ AppContainerComponent,
2351
+ // directives
2352
+ RlbRole], imports: [CommonModule,
2316
2353
  FormsModule,
2317
2354
  ReactiveFormsModule,
2318
2355
  TranslateModule,
@@ -2336,7 +2373,9 @@ class RlbAppModule {
2336
2373
  TranslateModule,
2337
2374
  RlbBootstrapModule,
2338
2375
  RouterModule,
2339
- FormsModule] }); }
2376
+ FormsModule,
2377
+ // directives
2378
+ RlbRole] }); }
2340
2379
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbAppModule, imports: [CommonModule,
2341
2380
  FormsModule,
2342
2381
  ReactiveFormsModule,
@@ -2373,7 +2412,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2373
2412
  CmsComponent,
2374
2413
  ContentComponent,
2375
2414
  AppTemplateComponent,
2376
- AppContainerComponent
2415
+ AppContainerComponent,
2416
+ // directives
2417
+ RlbRole
2377
2418
  ],
2378
2419
  exports: [
2379
2420
  // pipes
@@ -2394,7 +2435,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2394
2435
  TranslateModule,
2395
2436
  RlbBootstrapModule,
2396
2437
  RouterModule,
2397
- FormsModule
2438
+ FormsModule,
2439
+ // directives
2440
+ RlbRole
2398
2441
  ],
2399
2442
  imports: [
2400
2443
  CommonModule,
@@ -2541,55 +2584,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2541
2584
  args: [{ selector: 'rlb-app-selector', imports: [RlbAppModule, CommonModule], template: "<div class=\"container mt-2\">\n <div class=\"container text-center position-relative my-3\">\n <h3>{{ 'core.apps.title' | translate}}</h3>\n <i role=\"button\" class=\"bi bi-chevron-left position-absolute start-0 top-50 translate-middle-y cur\"\n style=\"font-size: 25px;font-weight: 600;\" (click)=\"backClicked()\"></i>\n </div>\n <rlb-list>\n @for (app of apps; track app.id) {\n <ng-container ngProjectAs=\"rlb-list-item-image\">\n @if (app.core) {\n <ng-container ngProjectAs=\"rlb-list-item-image\">\n <rlb-list-item-image [icon]=\"app.core.icon\" [avatar-size]=\"35\"\n [line-1]=\"(app.core.title | translate) + (app.data?.appName ? ' - ' + app.data?.appName : '')\"\n [disabled]=\"!app.enabled && (app.core.auth && !(auth$ | async))\" [line-2]=\"app.core.description | translate\"\n (click)=\"selectApp(app)\">\n </rlb-list-item-image>\n </ng-container>\n }\n </ng-container>\n }\n </rlb-list>\n</div>\n" }]
2542
2585
  }], ctorParameters: () => [{ type: i1$6.Location }, { type: AppsService }, { type: AuthenticationService }] });
2543
2586
 
2544
- class RlbRole {
2545
- constructor(templateRef, viewContainer, authenticationService, parseJwtService) {
2546
- this.templateRef = templateRef;
2547
- this.viewContainer = viewContainer;
2548
- this.authenticationService = authenticationService;
2549
- this.parseJwtService = parseJwtService;
2550
- }
2551
- set roles(roles) {
2552
- if (typeof roles === 'string') {
2553
- if (roles.includes(','))
2554
- roles = roles.split(',').map(role => role.trim());
2555
- else
2556
- roles = [roles];
2557
- }
2558
- }
2559
- updateView() {
2560
- return this.authenticationService.
2561
- accessToken$.pipe(map(token => this.parseJwtService.parseJwt(token)),
2562
- //map(payload => this.authenticationService.currentProvider.roleClaim?.(payload) || []),
2563
- tap(roles => {
2564
- let valid = true;
2565
- for (const role of this.roles) {
2566
- if (roles.includes(role))
2567
- valid &&= true;
2568
- }
2569
- if (valid) {
2570
- this.viewContainer.createEmbeddedView(this.templateRef);
2571
- }
2572
- else {
2573
- this.viewContainer.clear();
2574
- }
2575
- }));
2576
- }
2577
- ngOnInit() {
2578
- this.updateView().subscribe();
2579
- }
2580
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbRole, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: AuthenticationService }, { token: ParseJwtService }], target: i0.ɵɵFactoryTarget.Directive }); }
2581
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.4", type: RlbRole, isStandalone: false, selector: "[roles]", inputs: { roles: "roles" }, ngImport: i0 }); }
2582
- }
2583
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbRole, decorators: [{
2584
- type: Directive,
2585
- args: [{
2586
- selector: '[roles]',
2587
- standalone: false
2588
- }]
2589
- }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: AuthenticationService }, { type: ParseJwtService }], propDecorators: { roles: [{
2590
- type: Input
2591
- }] } });
2592
-
2593
2587
  const oauthGuard = (route, state) => {
2594
2588
  const authService = inject(AuthenticationService);
2595
2589
  return authService.isAuthenticated$.pipe(take(1), map((isAuthenticated) => {
@@ -2602,14 +2596,17 @@ const oauthGuard = (route, state) => {
2602
2596
  };
2603
2597
 
2604
2598
  const permissionGuard = (route) => {
2605
- const store = inject(AclStore);
2599
+ const aclStore = inject(AclStore);
2600
+ const appsService = inject(AppsService);
2606
2601
  const router = inject(Router);
2607
- const resource = route.data['resource'];
2608
- const action = route.data['action'];
2609
- // toObservable helps wait for 'loaded' to be true
2610
- return toObservable(store.loaded).pipe(filter(Boolean), take(1), map(() => store.hasPermission(resource, action)
2611
- ? true
2612
- : router.createUrlTree(['/notFound'])));
2602
+ // We wait for aclStore to load data
2603
+ return toObservable(aclStore.loaded).pipe(filter(Boolean), take(1), map(() => {
2604
+ const action = route.data['action'];
2605
+ if (appsService.checkPermissionInCurrentApp(action)) {
2606
+ return true;
2607
+ }
2608
+ return router.createUrlTree(['/notFound']);
2609
+ }));
2613
2610
  };
2614
2611
 
2615
2612
  const SESSION_RT = 'RT';
@@ -3250,6 +3247,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
3250
3247
  }] });
3251
3248
 
3252
3249
  class AclEffects {
3250
+ // TODO: It's legacy approach. We're moving forward to signalStore. All legacy boilerplate (effects.ts, reducers.ts, actions.ts) will be removed soon
3251
+ // I've moved this logic into acl.store.ts -> loadACL() method
3252
+ // finalizeAppsOnAclLoad$ = createEffect(() => {
3253
+ // return this.actions$.pipe(
3254
+ // ofType(AclActions.loadACLSuccess),
3255
+ // switchMap(() => {
3256
+ // if (!this.rlbInitProvider) {
3257
+ // console.error("RlbInitProvider not found. Define RLB_INIT_PROVIDER");
3258
+ // return of(AclActions.finalizedAppsFailure());
3259
+ // }
3260
+ // return from(
3261
+ // this.rlbInitProvider.finalizeApps(this.store, this.aclConfiguration)
3262
+ // ).pipe(
3263
+ // map(() => AclActions.finalizedAppsSuccess()),
3264
+ // catchError(() => of(AclActions.finalizedAppsFailure()))
3265
+ // );
3266
+ // }));
3267
+ // });
3253
3268
  constructor(actions$, adminApi, rlbInitProvider, aclConfiguration, store) {
3254
3269
  this.actions$ = actions$;
3255
3270
  this.adminApi = adminApi;
@@ -3264,15 +3279,6 @@ class AclEffects {
3264
3279
  this.fetchAcl$ = createEffect(() => {
3265
3280
  return this.actions$.pipe(ofType(AclActions.loadACL), switchMap$1(() => this.adminApi.resourcesByUser$().pipe(map$1(resources => AclActions.loadACLSuccess({ resources })), catchError(error => of(AclActions.loadACLFailure({ error }))))));
3266
3281
  });
3267
- this.finalizeAppsOnAclLoad$ = createEffect(() => {
3268
- return this.actions$.pipe(ofType(AclActions.loadACLSuccess), switchMap$1(() => {
3269
- if (!this.rlbInitProvider) {
3270
- console.error("RlbInitProvider not found. Define RLB_INIT_PROVIDER");
3271
- return of(AclActions.finalizedAppsFailure());
3272
- }
3273
- return from(this.rlbInitProvider.finalizeApps(this.store, this.aclConfiguration)).pipe(map$1(() => AclActions.finalizedAppsSuccess()), catchError(() => of(AclActions.finalizedAppsFailure())));
3274
- }));
3275
- });
3276
3282
  }
3277
3283
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: AclEffects, deps: [{ token: i1$8.Actions }, { token: AdminApiService }, { token: RLB_INIT_PROVIDER, optional: true }, { token: RLB_CFG_ACL, optional: true }, { token: i1$1.Store }], target: i0.ɵɵFactoryTarget.Injectable }); }
3278
3284
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: AclEffects }); }
@@ -3340,14 +3346,10 @@ function provideRlbConfig(env) {
3340
3346
  inject(AppsService);
3341
3347
  }),
3342
3348
  provideAppInitializer(() => {
3349
+ // From here starts all pipeline: checkAuthMultiple -> resourcesByUser -> finalizeApp -> orchestration in appsService
3343
3350
  const authService = inject(AuthenticationService);
3344
3351
  return authService.checkAuthMultiple();
3345
3352
  }),
3346
- provideAppInitializer(() => {
3347
- const aclStore = inject(AclStore);
3348
- // We call the rxMethod, AppInitializer can guarantee that this logic will be executed before routing
3349
- return aclStore.loadACL();
3350
- }),
3351
3353
  { provide: RLB_CFG, useValue: env },
3352
3354
  { provide: RLB_CFG_ENV, useValue: env.environment },
3353
3355
  { provide: RLB_CFG_CMS, useValue: env.cms },