UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

241 lines (187 loc) 6.58 kB
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; };