import React, { useImperativeHandle, useRef, useState, useEffect } from 'react'; import type WebView from 'react-native-webview'; import { HeightEvent, PageMessage } from '../../messages/webMessages/pageMessage'; import { RequestRefreshEvent, RequestRenderingEvent, UnitComponentsMessage } from '../../messages/webMessages/unitComponentsMessages'; import { WebComponent } from '../../webComponent/WebComponent'; import type { WebViewMessage } from '../../messages/webMessages'; import { getCardParams, getCardScript, injectRefreshEventIfNeeded, injectOpenActionsMenuScript, injectRequestCardActionScript, injectRequestShowSensitiveDataScript, injectRequestHideSensitiveDataScript, } from './UNCardComponent.utils'; import { UNComponentsOnLoadResponse, UNCard, UNComponentsError, UNCardMenuAction, UNCardMenuItem } from '../../types/shared'; import { CardMessage } from '../../messages/webMessages/cardMessage'; import { NativeModules } from 'react-native'; import { RESPONSE_KEYS, UnitOnLoadResponseEvent } from '../../messages/webMessages/onLoadMessage'; import { BottomSheetRenderingType, SlotRendering, } from '../../types/internal/bottomSheet.types'; import type { BottomSheetSlotData } from '../../types/internal/bottomSheet.types'; import { PresentationMode, WebComponentType } from '../../types/internal/webComponent.types'; import { UnitComponentsSDK } from '../../unitComponentsSdkManager/UnitComponentsSdkManager'; import { BottomSheetNativeMessage } from '../../messages/nativeMessages/bottomSheetMessage'; import { useLaunchInitialize } from '../../helpers/pushProvisioningService/hooks/useLaunchInitialize'; import { selectWallet } from '../../slices/pushProvisioningSlice'; import { useSelector } from 'react-redux'; import { withReduxStoreAndRefForwarding } from '../../helpers/store/helpers'; import { useEventListener } from '../../hooks/useEventListener'; import { eventBus } from '../../utils/eventBus'; import { UNBaseView } from '../../nativeComponents/UNBaseView'; export interface UNCardComponentProps { // inputs cardId: string; learnMoreUrl?: string; fee?: number; menuItems?: UNCardMenuItem[]; // ui theme?: string; language?: string; hideActionsMenuButton?: boolean; hideCardTitle?: boolean; hideSensitiveDataButton?: boolean; showCardTypeSubtitle?: boolean; showCardHolderSubtitle?: boolean; // events onLoad?: (response: UNComponentsOnLoadResponse) => void; onStatusChanged?: (card: UNCard) => void; onCardActivated?: (card: UNCard) => void; onAction?: (action: UNCardMenuAction) => void; onActionsMenuClicked?: () => void; pushProvisioningModule?: typeof NativeModules; } export interface CardRef { openActionsMenu: () => void; openAction: (action: UNCardMenuAction) => void; showSensitiveData: () => void; hideSensitiveData: () => void; } const UNCardComponent = React.forwardRef(function UNCardComponent(props, cardRef) { const [height, setHeight] = useState(0); const webRef = useRef(null); const { initializePushProvisioning } = useLaunchInitialize(); const { signedNonce } = useSelector(selectWallet); useImperativeHandle(cardRef, () => ({ openActionsMenu() { injectOpenActionsMenuScript(webRef.current); }, openAction(action: UNCardMenuAction) { injectRequestCardActionScript(webRef.current, action); }, showSensitiveData() { injectRequestShowSensitiveDataScript(webRef.current); }, hideSensitiveData() { injectRequestHideSensitiveDataScript(webRef.current); }, })); const cardStatusChanged = (card: UNCard) => { if (props.onStatusChanged) { props.onStatusChanged(card); } }; const cardActivated = (card: UNCard) => { if (props.onCardActivated) { props.onCardActivated(card); } }; const requestRefresh = (data: RequestRefreshEvent) => { injectRefreshEventIfNeeded(webRef.current, data, props.cardId); }; const handleUnitOnLoad = (response: UnitOnLoadResponseEvent) => { if (!props.onLoad) { return; } if (RESPONSE_KEYS.errors in response) { props.onLoad(response as UNComponentsError); return; } if (RESPONSE_KEYS.card in response) { // CardOnLoadResponse props.onLoad(response[RESPONSE_KEYS.card] as UNComponentsOnLoadResponse); return; } console.error('On Load Error: unexpected response type'); return; }; useEventListener({ busEventKey: UnitComponentsMessage.UNIT_REQUEST_REFRESH, action: requestRefresh }); useEventListener({ busEventKey: CardMessage.CARD_STATUS_CHANGED, action: cardStatusChanged }); useEventListener({ busEventKey: CardMessage.CARD_ACTIVATED, action: cardActivated }); const handleMessage = (message: WebViewMessage) => { if (!message || !message.details) return; switch (message.type) { case UnitComponentsMessage.UNIT_REQUEST_RENDERING: { const slotData: BottomSheetSlotData = { componentName: WebComponentType.card, componentResourceId: props.cardId, requestRenderingEvent: message.details as RequestRenderingEvent, }; const data = { type: BottomSheetRenderingType.Slot, data: slotData, } as SlotRendering; eventBus.emit(BottomSheetNativeMessage.REQUEST_RENDERING, data); sendActionCallbackIfNeeded(slotData.requestRenderingEvent); break; } case PageMessage.PAGE_HEIGHT: setHeight((message.details as HeightEvent).height); break; case UnitComponentsMessage.UNIT_ON_LOAD: handleUnitOnLoad(message.details as UnitOnLoadResponseEvent); break; } }; const sendActionCallbackIfNeeded = (requestRenderingEvent: RequestRenderingEvent) => { switch (requestRenderingEvent.data.nativeComponentName) { case WebComponentType.cardMenu: props.onActionsMenuClicked && props.onActionsMenuClicked(); break; case WebComponentType.cardAction: { // Find the relevant action from the native component const actionMatch = requestRenderingEvent.data.nativeComponent.match(/action=([^\s]+)/); if (actionMatch && props.onAction) { const actionString = actionMatch[1] as string; // Check if the actionString matches any value in the UNCardMenuAction enum if (Object.values(UNCardMenuAction).includes(actionString as UNCardMenuAction)) { props.onAction(actionString as UNCardMenuAction); } } } } }; useEffect(() => { if (props.pushProvisioningModule) { UnitComponentsSDK.setPushProvisioningModule(props.pushProvisioningModule); } }, [props.pushProvisioningModule]); useEffect(() => { if (signedNonce || !UnitComponentsSDK.getPushProvisionModule()) return; initializePushProvisioning(); }, []); return ( }> handleMessage(message)} isScrollable={false} /> ); }); export default withReduxStoreAndRefForwarding(UNCardComponent);