@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
241 lines (187 loc) • 6.58 kB
text/typescript
import { useNavigation, useRivers, useScreenContext } from "../../reactHooks";
import { createLogger } from "../../logger";
import { useCallback, useMemo, useRef, useEffect } from "react";
export enum NavigationCallbackOptions {
DEFAULT = "default",
GO_HOME = "go_home",
GO_BACK = "go_back",
GO_TO_SCREEN = "go_to_screen",
}
export enum ResultType {
login = "login",
logout = "logout",
}
export type CallbackResult = hookCallbackArgs & {
options?: {
resultType?: ResultType;
navigator?: QuickBrickAppNavigator;
};
};
export type ScreenResultCallback = (args: CallbackResult) => void | undefined;
export const CALLBACK_NAVIGATION_KEY = "completion_action";
export const CALLBACK_NAVIGATION_GO_TO_SCREEN_KEY =
"completion_action_navigation_go_to_screen";
type NavKeys = {
action: NavigationCallbackOptions;
targetScreenId: string | null;
} | null;
type General = Record<string, unknown>;
const LogPrefix = "useCallbackNavigationAction:";
const { log_info, log_verbose, log_debug } = createLogger({
subsystem: "hook-navigation-callback",
});
const legacyMappingKeys = {
"quick-brick-login-flow": {
actionType: "login_completion_action",
targetScreen: "navigate_to_login_screen",
},
"quick-brick-user-account-ui-component": {
actionType: "callbackAction",
},
"quick-brick-login-multi-login-providers.login": {
actionType: "login_completion_action",
targetScreen: "navigate_to_login_screen",
},
"quick-brick-login-multi-login-providers.logout": {
actionType: "logout_completion_action",
targetScreen: "navigate_to_logout_screen",
},
"quick-brick-storefront": {
actionType: "purchase_completion_action",
targetScreen: "navigate_to_screen_after_purchase",
},
"zapp_login_plugin_oauth_tv_2_0.login": {
actionType: "login_completion_action",
targetScreen: "navigate_to_login_screen",
},
"zapp_login_plugin_oauth_tv_2_0.logout": {
actionType: "logout_completion_action",
targetScreen: "navigate_to_logout_screen",
},
};
const NAV_ACTIONS = (
Object.values(NavigationCallbackOptions) as string[]
).filter((value) => value !== NavigationCallbackOptions.DEFAULT);
const isNavAction = (v: unknown): v is NavigationCallbackOptions =>
typeof v === "string" && NAV_ACTIONS.includes(v);
export const getNavigationKeys = (
item?: ZappUIComponent | ZappRiver,
resultType: ResultType | null = null
): NavKeys => {
const general = (item?.general ?? {}) as General;
const pluginIdentifier = (item as any).identifier ?? item?.type ?? "";
const legacy =
legacyMappingKeys[`${pluginIdentifier}.${resultType}`] ??
legacyMappingKeys[pluginIdentifier] ??
{};
const actionKey = resultType
? `${resultType}_${CALLBACK_NAVIGATION_KEY}`
: CALLBACK_NAVIGATION_KEY;
const rawAction =
(general as General)[actionKey] ??
(legacy.actionType ? (general as General)[legacy.actionType] : undefined);
const action: NavigationCallbackOptions | null = isNavAction(rawAction)
? rawAction
: null;
if (!action) return null;
let targetScreenId: string | null = null;
if (action === NavigationCallbackOptions.GO_TO_SCREEN) {
const screenKey = resultType
? `${resultType}_${CALLBACK_NAVIGATION_GO_TO_SCREEN_KEY}`
: CALLBACK_NAVIGATION_GO_TO_SCREEN_KEY;
const screenId: string | null =
((general as General)[screenKey] as string) ??
(legacy.targetScreen
? ((general as General)[legacy.targetScreen] as string)
: undefined);
if (screenId) {
targetScreenId = screenId.length > 0 ? screenId : null;
}
}
return { action, targetScreenId };
};
export const useCallbackNavigationAction = (
item?: ZappUIComponent | ZappRiver
): ((
args: CallbackResult,
hookCallback?: hookCallback
) => void | undefined) => {
const navigation = useNavigation();
const rivers = useRivers();
const screenContext = useScreenContext();
const navigationRef = useRef(navigation);
useEffect(() => {
navigationRef.current = navigation;
}, [navigation]);
const overrideCallbackFromComponent = useMemo(() => {
log_verbose(`${LogPrefix}: overridden callbackAction by component`);
// TODO: Check if we have better option where to store overridden callback action
return screenContext?.options?.callback;
}, [screenContext?.options?.callback]);
if (typeof __DEV__ !== "undefined" && __DEV__) {
log_verbose(`${LogPrefix} screenContext`, { screenContext, item });
}
const callbackAction = useCallback<hookCallback>(
(args: CallbackResult, hookCallback: hookCallback = null) => {
if (!args.success) {
log_debug(
`${LogPrefix} callback called with no success, use original callback`
);
hookCallback?.(args);
return;
}
if (args.cancelled) {
log_debug(
`${LogPrefix} callback called but cancelled, use original callback`
);
hookCallback?.(args);
return;
}
const data = getNavigationKeys(item, args.options?.resultType ?? null);
if (!data) {
hookCallback?.(args);
return;
}
hookCallback?.({ ...args, success: false, cancelled: true });
const currentNavigation = navigationRef.current;
switch (data.action) {
case NavigationCallbackOptions.GO_BACK: {
if (currentNavigation.canGoBack()) {
currentNavigation.goBack();
log_info(`${LogPrefix} performing 'GO BACK' action`);
} else {
log_info(`${LogPrefix} cannot perform 'GO BACK' action — ignoring`);
}
break;
}
case NavigationCallbackOptions.GO_HOME: {
currentNavigation.goHome();
log_info(`${LogPrefix} performing 'GO HOME' action`);
break;
}
case NavigationCallbackOptions.GO_TO_SCREEN: {
const screenId = data.targetScreenId;
if (!screenId) {
log_info(`${LogPrefix} no screenId provided — ignoring`);
break;
}
const screen = rivers[screenId];
if (screen) {
currentNavigation.replace(screen);
log_info(
`${LogPrefix} performing 'GO TO SCREEN' action to screen: ${screenId}`
);
} else {
log_info(`${LogPrefix} no screen provided — ignoring`);
}
break;
}
default: {
break;
}
}
},
[item, rivers]
);
return overrideCallbackFromComponent || callbackAction;
};