UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

202 lines (176 loc) 5.66 kB
import * as R from "ramda"; import { isFunction } from "../../functionUtils"; import { HOOKS_EVENTS, HOOKS_TRANSITION_TYPE } from "./constants"; import { hooksManagerLogger } from "./logger"; import type { Hook as HookInterface, HookIniObj } from "./types"; const BASE_HOOK_PROPERTIES = ["screen_id", "identifier", "weight"]; /** * returns a numerical value for the given hook event, * given its position in the declaration (and its place in the * sequence of events) * @param {string} hookEventName * @returns {number} index */ const hookState: ( arg0: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS] ) => number = R.indexOf(R.__, R.values(HOOKS_EVENTS)); /** * assigns property to a given context * @param {object} context to attach properties to * @return {function} to assign the property value to the context */ function assignProp(context) { return function (value, property) { context[property] = value; }; } /** * Representation of a hook plugin */ export class Hook implements HookInterface { public manager: HookInterface["manager"]; public lastHook: HookInterface["lastHook"]; public state: HookInterface["state"]; public screen_id: HookInterface["screen_id"]; public identifier: HookInterface["identifier"]; public module: HookInterface["module"]; public general: HookInterface["general"]; public weight: number; public configuration: HookInterface["configuration"]; constructor({ hook, hooks, manager }: HookIniObj) { R.forEachObjIndexed(assignProp(this), hook); this.manager = manager; this.lastHook = R.equals( R.pick(BASE_HOOK_PROPERTIES, hook), R.pick(BASE_HOOK_PROPERTIES, R.last(hooks)) ); this.state = hookState(HOOKS_EVENTS.PENDING); } /** * sets the state of the hook, and invokes the handler for that state * @param {string} event name * @param {Array<Any>} args variadic arguments to invoke the handler with */ setStateAndNotify( event: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS], ...args ) { this.state = hookState(event); this.manager.subscriber.invokeHandler(event, ...args); } /** * returns react component of the the hook modal presenter * @returns {React.ComponentType<any>} */ getModalContainer(): HookInterface["module"]["ModalContainer"] | undefined { return this.module ? this.module.ModalContainer : undefined; } /** * tells whether the hook should be presented as a screen * @returns {boolean} */ shouldPresentScreenHook(): boolean { return !!( (this.screen_id && this.general?.allow_screen_plugin_presentation) || (this.module && this.module.presentFullScreen) || (!this.module?.presentFullScreen && !this.module?.run && this.module?.Component) || this.isModalHook() ); } /** * tells whether the hook should be presented as a modal screen * @returns {boolean} */ isModalHook(): boolean { return ( this.screen_id && this.general?.transition_type === HOOKS_TRANSITION_TYPE.MODAL ); } /** * tells whether the hook should interupt the whole flow * if it doesn't return success * @returns { boolean} */ isFlowBlocker(): boolean { return ( (this.general && this.general.is_flow_blocker) || (this.module && this.module.isFlowBlocker && this.module.isFlowBlocker()) ); } /** * tells whether the hook should be skipped * @param {object} payload to pass to the hook * @returns { boolean} */ skipHook(payload: object): boolean { const _skipHook = R.compose( R.ifElse(isFunction, R.apply(R.__, [payload]), R.F), R.path(["module", "skipHook"]) )(this); if (_skipHook) { hooksManagerLogger.log({ message: "skipping hook", data: this, jsOnly: true, }); } return _skipHook; } /** * executes the hook * @param {object} payload to pass to the hook * @param {function} next callback to invoke for processing next hook */ execute<T extends object>( payload: T, next: (arg0: { payload: T; success?: boolean }) => void ): void { if (this.skipHook(payload)) { next({ payload, success: true }); return; } if (this.state < hookState(HOOKS_EVENTS.START)) { this.setStateAndNotify(HOOKS_EVENTS.START, this); if (this?.module?.runInBackground) { hooksManagerLogger.log({ message: "running hook in background", data: { hook: this, payload }, jsOnly: true, }); this.setStateAndNotify(HOOKS_EVENTS.START_BACKGROUND_HOOK, this); this.manager.runInBackground(this, payload, next, (_payload: any) => this.manager.presentScreenHook(this, _payload || payload, next) ); } else if (this.shouldPresentScreenHook()) { this.manager.presentScreenHook(this, payload, next); } else { this.manager.executeHook(this, payload, next); } } else { // try to go to next hook // no error, success will be undefined next({ payload }); } } /** * tells whether the next hook should run in parallel or not * Will trigger the next in parallel if * - next hook exists * - AND currenthook should not present a screen * - AND next hook has the same weight as the current one * @returns {boolean} */ shouldStartNextHook(nextHook: Hook): boolean { return ( nextHook && !this.shouldPresentScreenHook() && R.eqProps("weight", nextHook, this) ); } isCancelled(): boolean { return this.state === hookState(HOOKS_EVENTS.CANCEL); } }