@open-rlb/ng-app 3.1.27 → 3.1.29

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,11 +523,10 @@ 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
528
- console.log('Filtering app:', app);
529
530
  const isDomainAllowed = !app.domains || app.domains.includes(this.currentDomain);
530
531
  if (!isDomainAllowed)
531
532
  return false;
@@ -554,11 +555,26 @@ class AppsService {
554
555
  });
555
556
  });
556
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
+ }
557
567
  get currentApp() {
558
568
  const app = this.store.selectSignal(state => state[appContextFeatureKey].currentApp)();
559
569
  this.logger.log('Current app from store:', app);
560
570
  return app;
561
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
+ }
562
578
  selectApp(app, viewMode, url) {
563
579
  const currentApp = this.currentApp;
564
580
  if (!app) {
@@ -926,8 +942,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
926
942
  args: [{ providedIn: 'root' }]
927
943
  }], ctorParameters: () => [{ type: i2.Router }, { type: i2.ActivatedRoute }, { type: AppLoggerService }, { type: LanguageService }, { type: i2$1.UniqueIdService }] });
928
944
 
929
- const RLB_INIT_PROVIDER = new InjectionToken(`${RLB_CFG}:init.provider`);
930
-
931
945
  class LocalCacheService {
932
946
  constructor(options) {
933
947
  this.options = options;
@@ -1527,16 +1541,9 @@ class AuthenticationService {
1527
1541
  this.store.dispatch(AuthActions.setCurrentProvider({
1528
1542
  currentProvider: authenticatedConfig.configId
1529
1543
  }));
1530
- if (this.appconfig.acl) {
1531
- // SignalStore methods can trigger the API call
1532
- return this.aclStore.loadACL().pipe(tap(() => this.handleRedirect()), map(() => responses));
1533
- }
1534
- else {
1535
- this.handleRedirect();
1536
- return of(responses);
1537
- }
1538
1544
  }
1539
- return of(responses);
1545
+ // SignalStore methods can trigger the API call
1546
+ return this.aclStore.loadACL().pipe(tap(() => this.handleRedirect()), map(() => responses));
1540
1547
  }));
1541
1548
  }
1542
1549
  login(targetUrl) {
@@ -1677,7 +1684,6 @@ class CompanyInterceptor {
1677
1684
  }
1678
1685
  const currentApp = this.store.selectSignal(state => state[appContextFeatureKey].currentApp)();
1679
1686
  const data = currentApp?.data;
1680
- console.log('CompanyInterceptor - currentApp data:', data);
1681
1687
  const mapping = this.config.acl?.interceptorMapping || {};
1682
1688
  let params = req.params;
1683
1689
  for (const key of Object.keys(mapping)) {
@@ -2291,6 +2297,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2291
2297
  args: ['toast-container-ids']
2292
2298
  }] } });
2293
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
+
2294
2327
  class RlbAppModule {
2295
2328
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbAppModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2296
2329
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.4", ngImport: i0, type: RlbAppModule, declarations: [
@@ -2314,7 +2347,9 @@ class RlbAppModule {
2314
2347
  CmsComponent,
2315
2348
  ContentComponent,
2316
2349
  AppTemplateComponent,
2317
- AppContainerComponent], imports: [CommonModule,
2350
+ AppContainerComponent,
2351
+ // directives
2352
+ RlbRole], imports: [CommonModule,
2318
2353
  FormsModule,
2319
2354
  ReactiveFormsModule,
2320
2355
  TranslateModule,
@@ -2338,7 +2373,9 @@ class RlbAppModule {
2338
2373
  TranslateModule,
2339
2374
  RlbBootstrapModule,
2340
2375
  RouterModule,
2341
- FormsModule] }); }
2376
+ FormsModule,
2377
+ // directives
2378
+ RlbRole] }); }
2342
2379
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbAppModule, imports: [CommonModule,
2343
2380
  FormsModule,
2344
2381
  ReactiveFormsModule,
@@ -2375,7 +2412,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2375
2412
  CmsComponent,
2376
2413
  ContentComponent,
2377
2414
  AppTemplateComponent,
2378
- AppContainerComponent
2415
+ AppContainerComponent,
2416
+ // directives
2417
+ RlbRole
2379
2418
  ],
2380
2419
  exports: [
2381
2420
  // pipes
@@ -2396,7 +2435,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2396
2435
  TranslateModule,
2397
2436
  RlbBootstrapModule,
2398
2437
  RouterModule,
2399
- FormsModule
2438
+ FormsModule,
2439
+ // directives
2440
+ RlbRole
2400
2441
  ],
2401
2442
  imports: [
2402
2443
  CommonModule,
@@ -2543,55 +2584,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
2543
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" }]
2544
2585
  }], ctorParameters: () => [{ type: i1$6.Location }, { type: AppsService }, { type: AuthenticationService }] });
2545
2586
 
2546
- class RlbRole {
2547
- constructor(templateRef, viewContainer, authenticationService, parseJwtService) {
2548
- this.templateRef = templateRef;
2549
- this.viewContainer = viewContainer;
2550
- this.authenticationService = authenticationService;
2551
- this.parseJwtService = parseJwtService;
2552
- }
2553
- set roles(roles) {
2554
- if (typeof roles === 'string') {
2555
- if (roles.includes(','))
2556
- roles = roles.split(',').map(role => role.trim());
2557
- else
2558
- roles = [roles];
2559
- }
2560
- }
2561
- updateView() {
2562
- return this.authenticationService.
2563
- accessToken$.pipe(map(token => this.parseJwtService.parseJwt(token)),
2564
- //map(payload => this.authenticationService.currentProvider.roleClaim?.(payload) || []),
2565
- tap(roles => {
2566
- let valid = true;
2567
- for (const role of this.roles) {
2568
- if (roles.includes(role))
2569
- valid &&= true;
2570
- }
2571
- if (valid) {
2572
- this.viewContainer.createEmbeddedView(this.templateRef);
2573
- }
2574
- else {
2575
- this.viewContainer.clear();
2576
- }
2577
- }));
2578
- }
2579
- ngOnInit() {
2580
- this.updateView().subscribe();
2581
- }
2582
- 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 }); }
2583
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.4", type: RlbRole, isStandalone: false, selector: "[roles]", inputs: { roles: "roles" }, ngImport: i0 }); }
2584
- }
2585
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RlbRole, decorators: [{
2586
- type: Directive,
2587
- args: [{
2588
- selector: '[roles]',
2589
- standalone: false
2590
- }]
2591
- }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: AuthenticationService }, { type: ParseJwtService }], propDecorators: { roles: [{
2592
- type: Input
2593
- }] } });
2594
-
2595
2587
  const oauthGuard = (route, state) => {
2596
2588
  const authService = inject(AuthenticationService);
2597
2589
  return authService.isAuthenticated$.pipe(take(1), map((isAuthenticated) => {
@@ -2604,14 +2596,17 @@ const oauthGuard = (route, state) => {
2604
2596
  };
2605
2597
 
2606
2598
  const permissionGuard = (route) => {
2607
- const store = inject(AclStore);
2599
+ const aclStore = inject(AclStore);
2600
+ const appsService = inject(AppsService);
2608
2601
  const router = inject(Router);
2609
- const resource = route.data['resource'];
2610
- const action = route.data['action'];
2611
- // toObservable helps wait for 'loaded' to be true
2612
- return toObservable(store.loaded).pipe(filter(Boolean), take(1), map(() => store.hasPermission(resource, action)
2613
- ? true
2614
- : 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
+ }));
2615
2610
  };
2616
2611
 
2617
2612
  const SESSION_RT = 'RT';
@@ -3252,6 +3247,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
3252
3247
  }] });
3253
3248
 
3254
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
+ // });
3255
3268
  constructor(actions$, adminApi, rlbInitProvider, aclConfiguration, store) {
3256
3269
  this.actions$ = actions$;
3257
3270
  this.adminApi = adminApi;
@@ -3266,15 +3279,6 @@ class AclEffects {
3266
3279
  this.fetchAcl$ = createEffect(() => {
3267
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 }))))));
3268
3281
  });
3269
- this.finalizeAppsOnAclLoad$ = createEffect(() => {
3270
- return this.actions$.pipe(ofType(AclActions.loadACLSuccess), switchMap$1(() => {
3271
- if (!this.rlbInitProvider) {
3272
- console.error("RlbInitProvider not found. Define RLB_INIT_PROVIDER");
3273
- return of(AclActions.finalizedAppsFailure());
3274
- }
3275
- return from(this.rlbInitProvider.finalizeApps(this.store, this.aclConfiguration)).pipe(map$1(() => AclActions.finalizedAppsSuccess()), catchError(() => of(AclActions.finalizedAppsFailure())));
3276
- }));
3277
- });
3278
3282
  }
3279
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 }); }
3280
3284
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: AclEffects }); }
@@ -3342,14 +3346,10 @@ function provideRlbConfig(env) {
3342
3346
  inject(AppsService);
3343
3347
  }),
3344
3348
  provideAppInitializer(() => {
3349
+ // From here starts all pipeline: checkAuthMultiple -> resourcesByUser -> finalizeApp -> orchestration in appsService
3345
3350
  const authService = inject(AuthenticationService);
3346
3351
  return authService.checkAuthMultiple();
3347
3352
  }),
3348
- provideAppInitializer(() => {
3349
- const aclStore = inject(AclStore);
3350
- // We call the rxMethod, AppInitializer can guarantee that this logic will be executed before routing
3351
- return aclStore.loadACL();
3352
- }),
3353
3353
  { provide: RLB_CFG, useValue: env },
3354
3354
  { provide: RLB_CFG_ENV, useValue: env.environment },
3355
3355
  { provide: RLB_CFG_CMS, useValue: env.cms },