import { Text, View, Alert, Modal, Linking, Platform, TextInput, StyleSheet, ScrollView, TouchableOpacity, } from 'react-native'; import React, { useState, useEffect } from 'react'; import { FirebaseMessagingTypes, getMessaging, getToken, onMessage, onTokenRefresh, getInitialNotification, onNotificationOpenedApp as onNotificationOpenedAppListener, } from '@react-native-firebase/messaging'; import Ometria from 'react-native-ometria'; import { RESULTS, requestNotifications } from 'react-native-permissions'; import { AuthModalProps } from './models'; import { version, ometria_sdk_version } from '../../package.json'; import { Events, customOmetriaOptions, demoBasketItems } from './data'; import { E2EPanel } from './e2e'; import { getOmetriaTokenFromStorage, openUrl, setOmetriaTokenToStorage, setThrottledBackgroundCallback, } from './utils'; const App = () => { // Set this as a state if you want to use the reinitialization feature, otherwise you can use a static token const [ometriaToken, setOmetriaToken] = useState(''); const [authModal, setAuthModal] = useState(false); const [evtsModal, setEvtsModal] = useState(false); // In RN 0.76+, the new architecture uses Bridgeless mode (RCTHost) // Check for TurboModule support using multiple methods const isBridgeless = (global as any).RN$Bridgeless === true; const hasTurboModuleProxy = (global as any).__turboModuleProxy != null; const isTurboModuleEnabled = isBridgeless || hasTurboModuleProxy; // A flag to prevent the user from sending events before the SDK is initialized in the sample app - not needed in production const [ometriaIsInitalized, setOmetriaIsInitalized] = useState(false); const [notificationContent, setNotificationContent] = useState( 'Receive or interract with a notification to see its content here.', ); /* METHODS */ /** * Initialize Ometria with the API Token * * Additionally you can pass an options object to customize the SDK behavior, * for example you can set the notification channel name for Android or set the appGroupIdentifier for iOS * * @param token String */ const handleOmetriaInit = async (token: string) => { try { console.log('About to initialize Ometria SDK...'); Ometria.initializeWithApiToken(token, customOmetriaOptions).then( async () => { setOmetriaIsInitalized(true); setAuthModal(false); Ometria.isLoggingEnabled(true); console.log('🎉 Ometria has been initialized!'); // Request Push Notifications permission after Ometria SDK initialization await requestNotifications(['alert', 'sound', 'badge']) .then(({ status }) => { console.log('🔔 Push Notification permissions status:', status); if (status === RESULTS.GRANTED) { handlePushNotifications(); console.log('🔔 Push Notification permissions granted!'); } }) .catch(error => { console.error('😕 Error requesting PN permissions: ', error); }); }, error => { throw error; }, ); } catch (error) { setAuthModal(false); setOmetriaIsInitalized(false); console.error('😕 Error: ', error); } }; /** * Handle Push Notifications 🍏 & 🤖 * - A. Provides Ometria SDK with the FCM token * - B. Listen for new FCM tokens and provide them to the Ometria SDK * - C. Captures user interaction with push notifications event emmited by the developer * - D. Handles notification that opened the app from a quit state * - E. Handles notification that opened the app from a background state * - F. Subscribe to foreground PN messages * * @returns unsubscribeFromMessages function */ const handlePushNotifications = () => { const messagingInstance = getMessaging(); // A. Provides Ometria SDK with the FCM token 🍏 & 🤖 getToken(messagingInstance) .then((pushToken: string) => { Ometria.onNewToken(pushToken); console.log('🔑 Firebase token:', pushToken); }) .catch(error => { console.error('😕 Error getting FCM token: ', error); }); // B. Listen for new FCM tokens and provide them to the Ometria SDK 🍏 & 🤖 onTokenRefresh(messagingInstance, (pushToken: string) => Ometria.onNewToken(pushToken), ); // C. Get the new token and send it when moving to the background, with a frequency of once a week (optional) setThrottledBackgroundCallback(async () => { getToken(messagingInstance).then((pushToken: string) => { Ometria.onNewToken(pushToken); console.log('🔑 Firebase token in the background:', pushToken); }); }); // D. Function that handles user interaction with push notifications event 🍏 & 🤖 const onNotificationOpenedApp = async ( remoteMessage: FirebaseMessagingTypes.RemoteMessage, ) => { console.log('🔔 Notification has been interacted with and opened app.'); setNotificationContent(JSON.stringify(remoteMessage, null, 2)); Ometria.onNotificationOpenedApp(remoteMessage); // 🏹 Ometria Event Logged: onNotificationInteracted const notif = await Ometria.parseNotification(remoteMessage); if (notif?.deepLinkActionUrl) { Ometria.trackDeepLinkOpenedEvent(notif.deepLinkActionUrl, 'Browser'); // 🏹 Ometria Event Logged: deepLinkOpened openUrl(notif.deepLinkActionUrl); } Ometria.flush(); }; // E. Check for notification that opened the app from a quit state 🍏 & 🤖 getInitialNotification(messagingInstance).then(remoteMessage => { if (remoteMessage) { console.log('🔔 Notification opened the app from quit state'); onNotificationOpenedApp(remoteMessage); } }); // F. Subscribe to notification that opens the app from a background state 🍏 & 🤖 onNotificationOpenedAppListener(messagingInstance, remoteMessage => { if (remoteMessage) { console.log('🔔 Notification opened the app from background'); onNotificationOpenedApp(remoteMessage); } }); // G. Subscribe to foreground PN messages 🍏 & 🤖 const unsubscribeFromMessages = onMessage( messagingInstance, async remoteMessage => { console.log('📭 Foreground message received:', remoteMessage); setNotificationContent(JSON.stringify(remoteMessage, null, 2)); Ometria.onNotificationReceived(remoteMessage); // 🏹 Ometria Event Logged: onNotificationReceived /* Keep in mind that foreground notifications are NOT shown to the user. Instead, you could trigger a local notification or update the in-app UI to signal a new notification. Read more at: https://rnfirebase.io/messaging/usage#foreground-state-messages Don't forget to call onNotificationOpenedApp() if you want to handle the notification interaction event */ }, ); return unsubscribeFromMessages; }; /** * Handle Deeplinking * @param payload {url: String} */ const handleDeepLinking = ({ url }: any) => { if (url.startsWith('https://') || url.startsWith('http://')) { Ometria.processUniversalLink(url).then( response => { Alert.alert('🔗 URL processed:', response); }, error => { console.log(error); Alert.alert('🔗 Unable to process URL: ' + url); }, ); } else { Linking.openURL(url).catch(() => { Alert.alert('🔗 Unable to open URL: ' + url); }); } }; /** * Handle Identification by Email or UserId and maybe storeId * @param method {email?: String, customerId?: String, storeId?: String} */ const handleProfileIdentified = (method: { email?: string; customerId?: string; storeId?: string; }) => { // I want it to be null, not empty string const updatedStoreId = method.storeId ? method.storeId : null; if (!!method.email && !!method.customerId) { Ometria.trackProfileIdentifiedEvent( method.customerId, method.email, updatedStoreId, ); setAuthModal(false); return; } method.email && Ometria.trackProfileIdentifiedByEmailEvent(method.email, updatedStoreId); method.customerId && Ometria.trackProfileIdentifiedByCustomerIdEvent( method.customerId, updatedStoreId, ); setAuthModal(false); }; /** * Saving Ometria API Token to Local Storage * * - Save token to Local Storage * - Initialize Ometria SDK * * (Optional: You could use a static token instead of Local Storage) */ const saveNewOmetriaToken = async ( newToken: string, initializeWithNewToken: (token: string) => Promise, ) => { if (newToken === '') { Alert.alert('🔐 Token cannot be empty'); return; } await setOmetriaTokenToStorage(newToken); console.log('🔐 New Token. Ometria will reinitialize'); initializeWithNewToken(newToken); }; const handleOmetriaTokenInit = async () => { const savedToken = await getOmetriaTokenFromStorage(); if (savedToken === '') { // Do not initialize Ometria SDK if there is no token setAuthModal(true); return; } setOmetriaToken(savedToken); handleOmetriaInit(savedToken); console.log('💾 Token from LocalStorage:', savedToken); }; const handleUpdateStoreId = (storeId: string | null) => { Ometria.updateStoreId(storeId); setAuthModal(false); }; // EFFECTS /** * Initialize with useEffect: * 1. Ometria init handler with or without reinitialization * 2. Deeplink handler */ useEffect(() => { // This is optional if you want to use the reinitialization feature // If not, you can call handleOmetriaInit here directly with the static token as a parameter handleOmetriaTokenInit(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { const subscribe = Linking.addEventListener('url', handleDeepLinking); return () => subscribe.remove(); }, []); return ( Ometria React Native Demo {version} {Platform.OS === 'android' ? 'Android' : 'iOS'} SDK version{' '} { (ometria_sdk_version || {})[ Platform.OS === 'android' ? 'android' : 'ios' ] } {isTurboModuleEnabled ? '⚡ New Architecture' : '🌉 Old Architecture'} setAuthModal(true)}> Change Login Info 🔐 {ometriaIsInitalized && ( setEvtsModal(true)}> Go to Events 📝 )} {ometriaIsInitalized && } 🔔 Notification Content: {notificationContent} setEvtsModal(false)} /> setAuthModal(false)} onProfileIdentified={handleProfileIdentified} /* Only for reinitialization feature */ reinitialization={{ ometriaToken, setOmetriaToken, saveNewOmetriaToken, handleOmetriaInit, }} ometriaIsInitialized={ometriaIsInitalized} /> ); }; // MODALS /* * A component that lists events and allows you to send them * */ const EventsModal: React.FC<{ isVisible: boolean; onClose: () => void; }> = ({ isVisible, onClose }) => { const simulatePushTokenRefresh = () => { getToken(getMessaging()).then((pushToken: string) => { Ometria.onNewToken(pushToken); console.log('🔑 Firebase token:', pushToken); }); }; const deleteStoreId = () => { Ometria.updateStoreId(null); }; const sendEvent = (eventType: string) => { switch (eventType) { case Events.ENABLE_LOGGING: Ometria.isLoggingEnabled(true); break; case Events.DEEPLINK_OPENED_EVENT: Ometria.trackDeepLinkOpenedEvent('/profile', 'ProfileScreen'); break; case Events.SCREEN_VIEWED: Ometria.trackScreenViewedEvent('OnboardingScreen', { a: '1', b: '2' }); break; case Events.HOME_SCREEN_VIEWED: Ometria.trackHomeScreenViewedEvent(); break; case Events.PROFILE_IDENTIFIED_BY_EMAIL: Ometria.trackProfileIdentifiedByEmailEvent('test@gmail.com'); break; case Events.PROFILE_IDENTIFIED_BY_CUSTOMER_ID: Ometria.trackProfileIdentifiedByCustomerIdEvent('test_customer_id'); break; case Events.PROFILE_DEIDENTIFIED: Ometria.trackProfileDeidentifiedEvent(); break; case Events.PRODUCT_VIEWED: Ometria.trackProductViewedEvent('productId-1'); break; case Events.PRODUCT_LISTING_VIEWED: Ometria.trackProductListingViewedEvent('product_list', {}); break; case Events.BASKET_VIEWED: Ometria.trackBasketViewedEvent(); break; case Events.BASKET_UPDATED: Ometria.trackBasketUpdatedEvent({ totalPrice: 12.0, id: 'basket_id_eg', currency: 'USD', items: demoBasketItems, link: 'link_eg', }); break; case Events.CHECKOUT_STARTED: Ometria.trackCheckoutStartedEvent('orderId-1'); break; case Events.ORDER_COMPLETED: Ometria.trackOrderCompletedEvent('orderId-1', { totalPrice: 12.0, currency: 'USD', items: demoBasketItems, link: 'link_eg', id: 'basket_id_eg', }); break; case Events.CUSTOM: Ometria.trackCustomEvent('my_custom_type'); break; case Events.FLUSH: Ometria.flush(); break; case Events.CLEAR: Ometria.clear(); break; case Events.SIMULATE_TOKEN_REFRESH: simulatePushTokenRefresh(); break; case Events.RESET_STORE_ID: deleteStoreId(); break; default: return; } }; return ( Events 📝 CLOSE EVENTS {/*See https://github.com/facebook/react-native/issues/48822 for scrollView height issue*/} {Object.values(Events).map(eventValue => ( sendEvent(eventValue)} > {eventValue} ))} ); }; /* * * A component to allow the user to change the login info * It is used to change the customerId or the userEmail * It is also used to save a new Ometria API Token if you want to use the reinitialization feature * */ const AuthModal: React.FC = ({ isVisible, onClose, onProfileIdentified, onUpdateStoreId, reinitialization, ometriaIsInitialized, }) => { const [userId, setUserId] = useState(''); const [userEmail, setUserEmail] = useState(''); const [storeId, setStoreId] = useState(''); return ( Change Login Info 🔐 reinitialization.saveNewOmetriaToken( reinitialization.ometriaToken, reinitialization.handleOmetriaInit, ) } > Save Ometria Token {ometriaIsInitialized && ( <> Customer Id: Email: Store Id (optional): { if (!userEmail) { Alert.alert('Please provide a customer Email'); return; } onProfileIdentified({ email: userEmail, storeId: storeId }); }} > Identify with Customer Email (± Store Id) { if (!userId) { Alert.alert('Please provide a customer Id'); return; } onProfileIdentified({ customerId: userId, storeId: storeId }); }} > Identify with Customer Id (± Store Id) { if (!userId || !userEmail) { Alert.alert('Please provide a customer Id and Email'); return; } onProfileIdentified({ customerId: userId, storeId: storeId, email: userEmail, }); }} > Identify with Customer Id, Email (± Store Id) onUpdateStoreId(storeId)} > Update Store Id only )} Close settings ); }; export default App; // STYLES const styles = StyleSheet.create({ container: { flex: 1, paddingHorizontal: 20, backgroundColor: 'white', paddingTop: 50, }, title: { fontSize: 18, marginTop: 20, marginBottom: 10, fontWeight: 'bold', textAlign: 'center', color: '#000', }, subtitle: { fontSize: 14, marginBottom: 20, textAlign: 'center', color: '#000', }, text: { color: '#FFF', }, textBold: { fontWeight: 'bold', paddingVertical: 4, color: '#000', }, textDefault: { color: '#000', }, input: { marginTop: 4, marginBottom: 12, padding: 12, color: '#000', borderColor: '#33323A', borderWidth: StyleSheet.hairlineWidth, }, btn: { padding: 12, marginVertical: 6, alignItems: 'center', backgroundColor: '#1e1f4d', }, closeBtn: { padding: 12, marginVertical: 12, alignItems: 'center', backgroundColor: 'grey', }, archBadge: { alignSelf: 'center', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, backgroundColor: '#e8f5e9', marginBottom: 16, }, archText: { fontSize: 14, fontWeight: '600', color: '#2e7d32', }, line: { height: 1, backgroundColor: '#33323A', marginTop: 12, marginBottom: 24, }, });