@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
186 lines (162 loc) • 4.98 kB
JavaScript
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)
);
}
}