UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

186 lines (162 loc) 4.98 kB
import * as R from "ramda"; import { isFunction } from "../../functionUtils"; import { HOOKS_EVENTS, HOOKS_TRANSITION_TYPE } from "./constants"; import { hooksManagerLogger } from "./logger"; 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 = 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 with full data */ export class Hook { /** * constructs the hook * @param {object} Options * @param {object} Options.hook hook object * @param {Array<Object>} Options.hooks array of hooks siblings in the current process * @param {object} manager hook manager to process the hooks */ constructor({ hook, hooks, manager }) { 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, ...args) { this.state = hookState(event); this.manager.invokeHandler(event, ...args); } /** * returns react component of the the hook modal presenter * @returns {React.ComponentType<any>} */ getModalContainer() { return this.module && this.module.ModalContainer; } /** * tells whether the hook should be presented as a screen * @returns {boolean} */ shouldPresentScreenHook() { 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() { 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() { 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) { 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(payload, next) { 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) => 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) { return ( nextHook && !this.shouldPresentScreenHook() && R.eqProps("weight", nextHook, this) ); } }