Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR-13900 - Rule Based Entitlements #477

Merged
merged 3 commits into from
Nov 1, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'];
doregg marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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);
doregg marked this conversation as resolved.
Show resolved Hide resolved

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);
doregg marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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
Loading