UNPKG

sparrow-controllers

Version:

Collection of platform-agnostic modules for creating secure data models for cryptocurrency wallets

363 lines 15.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ControllerMessenger = exports.RestrictedControllerMessenger = void 0; /** * A restricted controller messenger. * * This acts as a wrapper around the controller messenger instance that restricts access to actions * and events. * * @template N - The namespace for this messenger. Typically this is the name of the controller or * module that this messenger has been created for. The authority to publish events and register * actions under this namespace is granted to this restricted messenger instance. * @template Action - A type union of all Action types. * @template Event - A type union of all Event types. * @template AllowedAction - A type union of the 'type' string for any allowed actions. * @template AllowedEvent - A type union of the 'type' string for any allowed events. */ class RestrictedControllerMessenger { /** * Constructs a restricted controller messenger * * The provided allowlists grant the ability to call the listed actions and subscribe to the * listed events. The "name" provided grants ownership of any actions and events under that * namespace. Ownership allows registering actions and publishing events, as well as * unregistering actions and clearing event subscriptions. * * @param options - The controller options. * @param options.controllerMessenger - The controller messenger instance that is being wrapped. * @param options.name - The name of the thing this messenger will be handed to (e.g. the * controller name). This grants "ownership" of actions and events under this namespace to the * restricted controller messenger returned. * @param options.allowedActions - The list of actions that this restricted controller messenger * should be alowed to call. * @param options.allowedEvents - The list of events that this restricted controller messenger * should be allowed to subscribe to. */ constructor({ controllerMessenger, name, allowedActions, allowedEvents, }) { this.controllerMessenger = controllerMessenger; this.controllerName = name; this.allowedActions = allowedActions || null; this.allowedEvents = allowedEvents || null; } /** * Register an action handler. * * This will make the registered function available to call via the `call` method. * * The action type this handler is registered under *must* be in the current namespace. * * @param action - The action type. This is a unqiue identifier for this action. * @param handler - The action handler. This function gets called when the `call` method is * invoked with the given action type. * @throws Will throw when a handler has been registered for this action type already. * @template T - A type union of Action type strings that are namespaced by N. */ registerActionHandler(action, handler) { /* istanbul ignore if */ // Branch unreachable with valid types if (!action.startsWith(`${this.controllerName}:`)) { throw new Error(`Only allowed registering action handlers prefixed by '${this.controllerName}:'`); } this.controllerMessenger.registerActionHandler(action, handler); } /** * Unregister an action handler. * * This will prevent this action from being called. * * The action type being unregistered *must* be in the current namespace. * * @param action - The action type. This is a unqiue identifier for this action. * @template T - A type union of Action type strings that are namespaced by N. */ unregisterActionHandler(action) { /* istanbul ignore if */ // Branch unreachable with valid types if (!action.startsWith(`${this.controllerName}:`)) { throw new Error(`Only allowed unregistering action handlers prefixed by '${this.controllerName}:'`); } this.controllerMessenger.unregisterActionHandler(action); } /** * Call an action. * * This function will call the action handler corresponding to the given action type, passing * along any parameters given. * * The action type being called must be on the action allowlist. * * @param action - The action type. This is a unqiue identifier for this action. * @param params - The action parameters. These must match the type of the parameters of the * registered action handler. * @throws Will throw when no handler has been registered for the given type. * @template T - A type union of allowed Action type strings. * @returns The action return value. */ call(action, ...params) { /* istanbul ignore next */ // Branches unreachable with valid types if (this.allowedActions === null) { throw new Error('No actions allowed'); } else if (!this.allowedActions.includes(action)) { throw new Error(`Action missing from allow list: ${action}`); } return this.controllerMessenger.call(action, ...params); } /** * Publish an event. * * Publishes the given payload to all subscribers of the given event type. * * The event type being published *must* be in the current namespace. * * @param event - The event type. This is a unique identifier for this event. * @param payload - The event payload. The type of the parameters for each event handler must * match the type of this payload. * @template E - A type union of Event type strings that are namespaced by N. */ publish(event, ...payload) { /* istanbul ignore if */ // Branch unreachable with valid types if (!event.startsWith(`${this.controllerName}:`)) { throw new Error(`Only allowed publishing events prefixed by '${this.controllerName}:'`); } this.controllerMessenger.publish(event, ...payload); } subscribe(event, handler, selector) { /* istanbul ignore next */ // Branches unreachable with valid types if (this.allowedEvents === null) { throw new Error('No events allowed'); } else if (!this.allowedEvents.includes(event)) { throw new Error(`Event missing from allow list: ${event}`); } if (selector) { return this.controllerMessenger.subscribe(event, handler, selector); } return this.controllerMessenger.subscribe(event, handler); } /** * Unsubscribe from an event. * * Unregisters the given function as an event handler for the given event. * * The event type being unsubscribed to must be on the event allowlist. * * @param event - The event type. This is a unique identifier for this event. * @param handler - The event handler to unregister. * @throws Will throw when the given event handler is not registered for this event. * @template T - A type union of allowed Event type strings. */ unsubscribe(event, handler) { /* istanbul ignore next */ // Branches unreachable with valid types if (this.allowedEvents === null) { throw new Error('No events allowed'); } else if (!this.allowedEvents.includes(event)) { throw new Error(`Event missing from allow list: ${event}`); } this.controllerMessenger.unsubscribe(event, handler); } /** * Clear subscriptions for a specific event. * * This will remove all subscribed handlers for this event. * * The event type being cleared *must* be in the current namespace. * * @param event - The event type. This is a unique identifier for this event. * @template E - A type union of Event type strings that are namespaced by N. */ clearEventSubscriptions(event) { /* istanbul ignore if */ // Branch unreachable with valid types if (!event.startsWith(`${this.controllerName}:`)) { throw new Error(`Only allowed clearing events prefixed by '${this.controllerName}:'`); } this.controllerMessenger.clearEventSubscriptions(event); } } exports.RestrictedControllerMessenger = RestrictedControllerMessenger; /** * A messaging system for controllers. * * The controller messenger allows registering functions as 'actions' that can be called elsewhere, * and it allows publishing and subscribing to events. Both actions and events are identified by * unique strings. * * @template Action - A type union of all Action types. * @template Event - A type union of all Event types. */ class ControllerMessenger { constructor() { this.actions = new Map(); this.events = new Map(); /** * A cache of selector return values for their respective handlers. */ this.eventPayloadCache = new Map(); } /** * Register an action handler. * * This will make the registered function available to call via the `call` method. * * @param actionType - The action type. This is a unqiue identifier for this action. * @param handler - The action handler. This function gets called when the `call` method is * invoked with the given action type. * @throws Will throw when a handler has been registered for this action type already. * @template T - A type union of Action type strings. */ registerActionHandler(actionType, handler) { if (this.actions.has(actionType)) { throw new Error(`A handler for ${actionType} has already been registered`); } this.actions.set(actionType, handler); } /** * Unregister an action handler. * * This will prevent this action from being called. * * @param actionType - The action type. This is a unqiue identifier for this action. * @template T - A type union of Action type strings. */ unregisterActionHandler(actionType) { this.actions.delete(actionType); } /** * Unregister all action handlers. * * This prevents all actions from being called. */ clearActions() { this.actions.clear(); } /** * Call an action. * * This function will call the action handler corresponding to the given action type, passing * along any parameters given. * * @param actionType - The action type. This is a unqiue identifier for this action. * @param params - The action parameters. These must match the type of the parameters of the * registered action handler. * @throws Will throw when no handler has been registered for the given type. * @template T - A type union of Action type strings. * @returns The action return value. */ call(actionType, ...params) { const handler = this.actions.get(actionType); if (!handler) { throw new Error(`A handler for ${actionType} has not been registered`); } return handler(...params); } /** * Publish an event. * * Publishes the given payload to all subscribers of the given event type. * * @param eventType - The event type. This is a unique identifier for this event. * @param payload - The event payload. The type of the parameters for each event handler must * match the type of this payload. * @template E - A type union of Event type strings. */ publish(eventType, ...payload) { const subscribers = this.events.get(eventType); if (subscribers) { for (const [handler, selector] of subscribers.entries()) { if (selector) { const previousValue = this.eventPayloadCache.get(handler); const newValue = selector(...payload); if (newValue !== previousValue) { this.eventPayloadCache.set(handler, newValue); handler(newValue, previousValue); } } else { handler(...payload); } } } } subscribe(eventType, handler, selector) { let subscribers = this.events.get(eventType); if (!subscribers) { subscribers = new Map(); this.events.set(eventType, subscribers); } subscribers.set(handler, selector); } /** * Unsubscribe from an event. * * Unregisters the given function as an event handler for the given event. * * @param eventType - The event type. This is a unique identifier for this event. * @param handler - The event handler to unregister. * @throws Will throw when the given event handler is not registered for this event. * @template E - A type union of Event type strings. */ unsubscribe(eventType, handler) { const subscribers = this.events.get(eventType); if (!subscribers || !subscribers.has(handler)) { throw new Error(`Subscription not found for event: ${eventType}`); } const selector = subscribers.get(handler); if (selector) { this.eventPayloadCache.delete(handler); } subscribers.delete(handler); } /** * Clear subscriptions for a specific event. * * This will remove all subscribed handlers for this event. * * @param eventType - The event type. This is a unique identifier for this event. * @template E - A type union of Event type strings. */ clearEventSubscriptions(eventType) { this.events.delete(eventType); } /** * Clear all subscriptions. * * This will remove all subscribed handlers for all events. */ clearSubscriptions() { this.events.clear(); } /** * Get a restricted controller messenger * * Returns a wrapper around the controller messenger instance that restricts access to actions * and events. The provided allowlists grant the ability to call the listed actions and subscribe * to the listed events. The "name" provided grants ownership of any actions and events under * that namespace. Ownership allows registering actions and publishing events, as well as * unregistering actions and clearing event subscriptions. * * @param options - Controller messenger options. * @param options.name - The name of the thing this messenger will be handed to (e.g. the * controller name). This grants "ownership" of actions and events under this namespace to the * restricted controller messenger returned. * @param options.allowedActions - The list of actions that this restricted controller messenger * should be alowed to call. * @param options.allowedEvents - The list of events that this restricted controller messenger * should be allowed to subscribe to. * @template N - The namespace for this messenger. Typically this is the name of the controller or * module that this messenger has been created for. The authority to publish events and register * actions under this namespace is granted to this restricted messenger instance. * @template AllowedAction - A type union of the 'type' string for any allowed actions. * @template AllowedEvent - A type union of the 'type' string for any allowed events. * @returns The restricted controller messenger. */ getRestricted({ name, allowedActions, allowedEvents, }) { return new RestrictedControllerMessenger({ controllerMessenger: this, name, allowedActions, allowedEvents, }); } } exports.ControllerMessenger = ControllerMessenger; //# sourceMappingURL=ControllerMessenger.js.map