generated from layer5io/layer5-repo-template
-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #737 from codeSafari10/eventBus
Implement permissions flow
- Loading branch information
Showing
5 changed files
with
162 additions
and
8 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { Observable, Subject, filter } from 'rxjs'; | ||
|
||
// Generic Event interface | ||
export interface EventBusEvent<Type extends string, Data = unknown> { | ||
type: Type; | ||
data: Data; | ||
} | ||
|
||
// Helper type to extract event types from a union | ||
export type EventType<T> = T extends EventBusEvent<infer Type, any> ? Type : never; | ||
|
||
// Helper type to extract payload type for a given event type | ||
export type DataType<T, Type extends string> = T extends EventBusEvent<Type, infer Data> | ||
? Data | ||
: never; | ||
|
||
// Generic EventBus class | ||
export class EventBus<T extends EventBusEvent<string, any>> { | ||
private eventSubject: Subject<T>; | ||
private eventObservable: Observable<T>; | ||
|
||
constructor() { | ||
this.eventSubject = new Subject<T>(); | ||
this.eventObservable = this.eventSubject.asObservable(); | ||
} | ||
|
||
// Method to publish an event | ||
publish<E extends T>(event: E): void { | ||
this.eventSubject.next(event); | ||
} | ||
|
||
// Method to subscribe to a specific event type | ||
on(eventType: EventType<T>): Observable<T> { | ||
return this.eventObservable.pipe(filter((event): event is T => event.type === eventType)); | ||
} | ||
|
||
// Method to subscribe to all events | ||
onAny(): Observable<T> { | ||
return this.eventObservable; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,3 +37,5 @@ export { | |
sendToActor, | ||
sendToActors | ||
} from './utils'; | ||
|
||
export * from './eventBus'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -122,3 +122,4 @@ export type { | |
}; | ||
|
||
export * from './Dialog'; | ||
export * from './permissions'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// import { CAN, getCapabilitiesRegistry, getMesheryEventBus } from '@/globals/mesherySdk'; | ||
import React from 'react'; | ||
import { EventBus } from '../actors/eventBus'; | ||
|
||
export interface Key { | ||
subject: string; | ||
action: string; | ||
} | ||
|
||
export type InvertAction = 'disable' | 'hide'; | ||
|
||
export type MissingPermissionReason = { | ||
type: 'MISSING_PERMISSION'; | ||
data: { | ||
keyId: string; | ||
}; | ||
}; | ||
|
||
export type MissingCapabilityReason = { | ||
type: 'MISSING_CAPABILITY'; | ||
data: { | ||
capabilityId: string; | ||
}; | ||
}; | ||
|
||
export type ReasonEvent = MissingPermissionReason | MissingCapabilityReason; | ||
|
||
export interface HasKeyProps<ReasonEvent> { | ||
Key?: Key; | ||
predicate?: (capabilitiesRegistry: unknown) => [boolean, ReasonEvent]; // returns a boolean and an event if the user does not have the permission | ||
children: React.ReactNode; | ||
notifyOnclick?: boolean; | ||
invert_action?: InvertAction[]; | ||
} | ||
|
||
// returns the children if the user has the permission to view the component or if a key is not provided | ||
// if the user does not have the permission to view the component, it will return null or a disabled version of the component specified by the invert_action prop | ||
export const createCanShow = ( | ||
getCapabilitiesRegistry = () => {}, | ||
CAN: (action: string, subject: string) => boolean, | ||
eventBus: () => EventBus<ReasonEvent> | ||
) => { | ||
return ({ | ||
Key, | ||
children, | ||
notifyOnclick = true, | ||
predicate, | ||
invert_action = ['disable'] | ||
}: HasKeyProps<ReasonEvent>) => { | ||
if (!children) { | ||
return null; | ||
} | ||
|
||
const hasKey = Key?.subject ? CAN(Key?.action, Key?.subject) : true; | ||
const predicateRes = predicate && predicate(getCapabilitiesRegistry()); | ||
|
||
const can = predicateRes ? predicateRes[0] && hasKey : hasKey; | ||
|
||
const reason = predicateRes?.[1] || { | ||
type: 'MISSING_PERMISSION', | ||
data: { | ||
keyId: Key?.action as string | ||
} | ||
}; | ||
|
||
if (can) { | ||
return children; | ||
} | ||
|
||
if (invert_action.includes('hide')) { | ||
return null; | ||
} | ||
|
||
const pointerEvents = notifyOnclick ? 'auto' : 'none'; | ||
|
||
const onClick = notifyOnclick | ||
? (e: React.MouseEvent<HTMLDivElement | HTMLElement>) => { | ||
e.stopPropagation(); | ||
console.log('cant perform action : reason', reason, eventBus); | ||
const mesheryEventBus = eventBus(); | ||
mesheryEventBus.publish(reason); | ||
} | ||
: () => {}; | ||
|
||
const opacity = invert_action.includes('disable') ? 0.5 : 1; | ||
|
||
return ( | ||
<div | ||
style={{ | ||
cursor: 'pointer', | ||
pointerEvents, | ||
opacity: opacity | ||
}} | ||
onClick={onClick} | ||
> | ||
{React.cloneElement(children as React.ReactElement, { | ||
style: { | ||
...((children as React.ReactElement).props.style as React.CSSProperties), | ||
cursor: 'pointer', | ||
pointerEvents, | ||
opacity: opacity | ||
}, | ||
onClick: onClick | ||
})} | ||
</div> | ||
); | ||
|
||
return null; | ||
}; | ||
}; |