Skip to content

Commit

Permalink
Merge pull request #477 from frontegg/FR-13900-rule-based-entitlements
Browse files Browse the repository at this point in the history
  • Loading branch information
doregg authored Nov 1, 2023
2 parents 2a82828 + 65ff125 commit e938df7
Showing 1 changed file with 35 additions and 35 deletions.
70 changes: 35 additions & 35 deletions projects/frontegg-app/src/lib/frontegg-entitlements.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { Subscription, PartialObserver, BehaviorSubject } from 'rxjs';
import { AuthState } from '@frontegg/redux-store';
import { Injectable } from '@angular/core';
import { Entitlement, LoadEntitlementsCallback, EntitledToOptions } from '@frontegg/types';
import { Entitlement, LoadEntitlementsCallback, EntitledToOptions, CustomAttributes } from '@frontegg/types';

import {
FronteggState,
getPermissionEntitlements,
getFeatureEntitlements,
getEntitlements,
} from '@frontegg/redux-store';
import { FronteggState } from '@frontegg/redux-store';
import { FronteggAppService } from './frontegg-app.service';

type User = FronteggState['auth']['user'];

/**
* An entitlements service for:
* 1. Managing state subscription.
Expand All @@ -21,16 +18,16 @@ import { FronteggAppService } from './frontegg-app.service';
providedIn: 'root',
})
export class FronteggEntitlementsService {
private entitlementsStateSubject = new BehaviorSubject<any>(undefined);
private userStateSubject = new BehaviorSubject<any>(undefined);

constructor(private fronteggAppService: FronteggAppService) {
const state = this.fronteggAppService.fronteggApp.store.getState() as FronteggState;
this.updateEntitlementsStateIfNeeded(state.auth);
this.updateUserStateIfNeeded(state.auth);

// Memoized entitlements State
this.fronteggAppService.fronteggApp.store.subscribe(() => {
const newState = this.fronteggAppService.fronteggApp.store.getState() as FronteggState;
this.updateEntitlementsStateIfNeeded(newState.auth);
this.updateUserStateIfNeeded(newState.auth);
});
}

Expand All @@ -39,57 +36,58 @@ export class FronteggEntitlementsService {
* No need for deep equal because we already check it internally
* @param authState
*/
private updateEntitlementsStateIfNeeded(authState: AuthState): void {
const entitlementsState = authState.user?.entitlements as any;
if (this.entitlementsStateSubject.value === entitlementsState) {
private updateUserStateIfNeeded(authState: AuthState): void {
const entitlementsState = authState.user as User;
if (this.userStateSubject.value === entitlementsState) {
return;
}

this.entitlementsStateSubject.next(entitlementsState);
this.userStateSubject.next(entitlementsState);
}

/**
* The function gives the ability to return a manipulated data of the entitlements state as a subscription.
* The function gives the ability to return a manipulated data of the user state as a subscription.
*
* @param dataManipulator Manipulator function that receives entitlements state and returns a manipulated data
* @param dataManipulator Manipulator function that receives user state and returns a manipulated data
* @param observer For receiving manipulated data result
* @returns a subscription to be able to unsubscribe
*/
private getEntitlementsManipulatorSubscription<Result>(
dataManipulator: (entitlements: any) => Result,
private getUserManipulatorSubscription<Result>(
dataManipulator: (user: User) => Result,
observer: PartialObserver<Result>
): Subscription {
// used for computing the entitlements result because we don't return the state itself, but a calculated one
const entitlementsSubject = new BehaviorSubject<Result>(undefined as unknown as Result);
const userSubject = new BehaviorSubject<Result>(undefined as unknown as Result);

const stateSubscription = this.entitlementsStateSubject.subscribe(entitlements => {
entitlementsSubject.next(dataManipulator(entitlements));
const stateSubscription = this.userStateSubject.subscribe(user => {
userSubject.next(dataManipulator(user));
});

// subscribing the consumer observer
const entitlementsResultSubscription = entitlementsSubject.asObservable().subscribe(observer)
const userResultSubscription = userSubject.asObservable().subscribe(observer)

// monkey patched to manage both un-subscriptions: state and entitlements manipulated result
const originalUnsubscribe = entitlementsResultSubscription.unsubscribe.bind(entitlementsResultSubscription);
// monkey patched to manage both un-subscriptions: state and user manipulated result
const originalUnsubscribe = userResultSubscription.unsubscribe.bind(userResultSubscription);

entitlementsResultSubscription.unsubscribe = ()=>{
userResultSubscription.unsubscribe = ()=>{
originalUnsubscribe();
stateSubscription.unsubscribe();
};

return entitlementsResultSubscription;
return userResultSubscription;
}

/**
* @param feature
* @param observer For receiving the feature entitlements result including if the user is entitled to the given feature.
* Attaching the justification if not entitled
* @param customAttributes consumer attributes
* @returns a subscription to be able to unsubscribe
* @throws when entitlement is not enabled via frontegg options
*/
public featureEntitlements$(feature: string, observer: PartialObserver<Entitlement>): Subscription {
return this.getEntitlementsManipulatorSubscription<Entitlement>(
(entitlements) => getFeatureEntitlements(entitlements, feature),
public featureEntitlements$(feature: string, observer: PartialObserver<Entitlement>, customAttributes?: CustomAttributes): Subscription {
return this.getUserManipulatorSubscription<Entitlement>(
() => { return this.fronteggAppService.fronteggApp.getFeatureEntitlements(feature, customAttributes)},
observer
);
}
Expand All @@ -98,11 +96,12 @@ export class FronteggEntitlementsService {
* @param permission
* @param observer For receiving the permission entitlements result including if the user is entitled to the given permission.
* Attaching the justification if not entitled
* @param customAttributes consumer attributes
* @returns a subscription to be able to unsubscribe
*/
public permissionEntitlements$(permission: string, observer: PartialObserver<Entitlement>): Subscription {
return this.getEntitlementsManipulatorSubscription<Entitlement>(
(entitlements) => getPermissionEntitlements(entitlements, permission),
public permissionEntitlements$(permission: string, observer: PartialObserver<Entitlement>, customAttributes?: CustomAttributes): Subscription {
return this.getUserManipulatorSubscription<Entitlement>(
() => this.fronteggAppService.fronteggApp.getPermissionEntitlements(permission, customAttributes),
observer
);
}
Expand All @@ -111,11 +110,12 @@ export class FronteggEntitlementsService {
* @param options permissionKey or featureKey in an options object
* @param observer For receiving the permission entitlements result including if the user is entitled to the given permission.
* Attaching the justification if not entitled
* @param customAttributes consumer attributes
* @returns a subscription to be able to unsubscribe
*/
public entitlements$(options: EntitledToOptions, observer: PartialObserver<Entitlement>): Subscription {
return this.getEntitlementsManipulatorSubscription<Entitlement>(
(entitlements) => getEntitlements(entitlements, options),
public entitlements$(options: EntitledToOptions, observer: PartialObserver<Entitlement>, customAttributes?: CustomAttributes): Subscription {
return this.getUserManipulatorSubscription<Entitlement>(
() => this.fronteggAppService.fronteggApp.getEntitlements(options, customAttributes),
observer
);
}
Expand Down

0 comments on commit e938df7

Please sign in to comment.