import { DeviceEventEmitter, NativeEventEmitter, NativeModules, Platform, } from 'react-native'; const LINKING_ERROR = `The package 'reteno-react-native-sdk' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n'; export enum CustomEventTypes { screenView = 'screenView', } export type Address = { region?: string | null; town?: string | null; address?: string | null; postcode?: string | null; }; type Field = { key: string; value: string; }; type Fields = Field[]; export type UserAttributes = { phone?: string | null; email?: string | null; firstName?: string | null; lastName?: string | null; languageCode?: string | null; timeZone?: string | null; address?: Address | null; fields?: Fields | null; }; export type AnonymousUserAttributes = Pick< UserAttributes, 'firstName' | 'lastName' | 'languageCode' | 'timeZone' | 'address' | 'fields' >; export type User = { userAttributes?: UserAttributes | null; subscriptionKeys?: String[] | null; groupNamesInclude?: String[] | null; groupNamesExclude?: String[] | null; }; export type SetUserAttributesPayload = { externalUserId: string; user: User; }; export type SetMultiAccountUserAttributesPayload = { externalUserId: string; user: User; }; export type CustomEventParameter = { name: string; value?: string; }; export type AppInboxStatus = 'OPENED' | 'UNOPENED'; export type GetAppInboxMessages = { page?: number; pageSize?: number; status?: AppInboxStatus; }; export type InAppDisplayData = { id?: string; source?: 'DISPLAY_RULES' | 'PUSH_NOTIFICATION'; }; export type InAppCloseData = { id?: string; source?: 'DISPLAY_RULES' | 'PUSH_NOTIFICATION'; closeAction?: 'OPEN_URL' | 'BUTTON' | 'CLOSE_BUTTON' | 'UNKNOWN'; isCloseButtonClicked?: boolean; isButtonClicked?: boolean; isOpenUrlClicked?: boolean; }; export type InAppErrorData = { id?: string; source?: 'DISPLAY_RULES' | 'PUSH_NOTIFICATION'; errorMessage?: string; }; export type InAppPauseBehaviour = 'SKIP_IN_APPS' | 'POSTPONE_IN_APPS'; export type NotificationPermissionStatus = | 'ALLOWED' | 'DENIED' | 'PERMANENTLY_DENIED'; export type InAppCustomData = { customData?: Record; inapp_id?: string; inapp_source?: 'DISPLAY_RULES' | 'PUSH_NOTIFICATION'; url?: string; }; export type RecommendationsPayload = { recomVariantId: string; productIds: string[]; categoryId: string; filters?: { [key: string]: any }[]; fields: string[]; }; export type RecommendationEvent = { productId: string; }; export type RecommendationEventPayload = { recomVariantId: string; impressions: RecommendationEvent[]; clicks: RecommendationEvent[]; // forcePush is only for IOS forcePush?: boolean; }; export type InboxMessage = { id: string; title: string; createdDate: string; imageURL?: string; linkURL?: string; isNew: boolean; content?: string; // Only on Android category?: string; status?: AppInboxStatus; }; export type UnreadMessagesCountData = { count: number; }; export type UnreadMessagesCountErrorData = { statusCode?: number | null; response?: string | null; error?: string | null; }; export type PushButton = { actionId: string; customData: string | null; actionLink: string | null; userInfo: any; }; const RetenoSdk = NativeModules.RetenoSdk ? NativeModules.RetenoSdk : new Proxy( {}, { get() { throw new Error(LINKING_ERROR); }, } ); export function setDeviceToken(deviceToken: string): Promise { return RetenoSdk.setDeviceToken(deviceToken); } export function setUserAttributes( payload: SetUserAttributesPayload ): Promise { if ( !payload.externalUserId || (payload.externalUserId && payload.externalUserId.length === 0) ) { throw new Error('Missing argument: "externalUserId"'); } return RetenoSdk.setUserAttributes(payload); } export function getInitialNotification(): Promise { return RetenoSdk.getInitialNotification(); } export function getRecommendations( payload: RecommendationsPayload ): Promise { return RetenoSdk.getRecommendations(payload); } export function logRecommendationEvent( payload: RecommendationEventPayload ): Promise { return RetenoSdk.logRecommendationEvent(payload); } const eventEmitter = Platform.OS === 'android' ? DeviceEventEmitter : new NativeEventEmitter(RetenoSdk); /** * Initialize event handler. Call this after setting up all event listeners. * Events that occur before this call will be queued and delivered after initialization. * * @example * ```typescript * // 1. First, set up all your listeners * setOnRetenoPushReceivedListener((event) => { * console.log('Push received:', event); * }); * * // 2. Then initialize to start receiving events * initializeEventHandler(); * ``` * * @returns Promise that resolves to true when initialization is complete */ export function initializeEventHandler(): Promise { return RetenoSdk.initializeEventHandler(); } /** * Control whether SDK automatically opens URLs when user clicks on push notifications or in-app messages. * * @param enabled - true to auto-open URLs (default), false to handle URLs manually via event listeners * @returns Promise that resolves when setting is applied * * @example * ```typescript * // Disable automatic URL opening * setAutoOpenLinks(false); * * // Handle URLs manually * addInAppMessageCustomDataHandler((data) => { * if (data.url) { * // Custom URL handling logic * } * }); * ``` */ export function setAutoOpenLinks(enabled: boolean): Promise { return RetenoSdk.setAutoOpenLinks(enabled); } /** * Get the current auto-open links setting. * * @returns Promise that resolves to the current setting (true = auto-open enabled, false = disabled) * * @example * ```typescript * const isEnabled = await getAutoOpenLinks(); * console.log('Auto open links:', isEnabled); * ``` */ export function getAutoOpenLinks(): Promise { return RetenoSdk.getAutoOpenLinks(); } export function setOnRetenoPushReceivedListener( listener: (event: any) => void ) { return eventEmitter.addListener('reteno-push-received', listener); } export function setOnRetenoPushClickedListener(listener: (event: any) => void) { return eventEmitter.addListener('reteno-push-clicked', listener); } /** * iOS Only */ export function setOnRetenoPushButtonClickedListener( listener: (event: PushButton) => void ) { if (Platform.OS === 'ios') { return eventEmitter.addListener('reteno-push-button-clicked', listener); } return undefined; } export function setInAppLifecycleCallback() { RetenoSdk.setInAppLifecycleCallback(); } /** * Android Only */ export function removeInAppLifecycleCallback() { if (Platform.OS === 'android') { RetenoSdk.removeInAppLifecycleCallback(); } } export function beforeInAppDisplayHandler( callback: (data: InAppDisplayData) => void ) { return eventEmitter.addListener('reteno-before-in-app-display', (data) => { if (callback && typeof callback === 'function') { callback(data); } }); } export function onInAppDisplayHandler( callback: (data: InAppDisplayData) => void ) { return eventEmitter.addListener('reteno-on-in-app-display', (data) => { if (callback && typeof callback === 'function') { callback(data); } }); } export function beforeInAppCloseHandler( callback: (data: InAppCloseData) => void ) { return eventEmitter.addListener('reteno-before-in-app-close', (data) => { if (callback && typeof callback === 'function') { callback(data); } }); } export function afterInAppCloseHandler( callback: (data: InAppCloseData) => void ) { return eventEmitter.addListener('reteno-after-in-app-close', (data) => { if (callback && typeof callback === 'function') { callback(data); } }); } export function onInAppErrorHandler(callback: (data: InAppErrorData) => void) { return eventEmitter.addListener('reteno-on-in-app-error', (data) => { if (callback && typeof callback === 'function') { callback(data); } }); } export function addInAppMessageCustomDataHandler( callback: (data: InAppCustomData) => void ) { return eventEmitter.addListener( 'reteno-in-app-custom-data-received', (data) => { if (callback && typeof callback === 'function') { callback(data); } } ); } /** * Log event * @param eventName name of the event * @param date date parameter should be in ISO8601 format, e.g new Date().toISOString() * @param parameters custom parameters * @param forcePush IOS force push */ export function logEvent( eventName: string, // date parameter should be in ISO8601 format date: string, parameters: CustomEventParameter[], forcePush?: boolean ): Promise { return RetenoSdk.logEvent({ eventName, date, parameters, forcePush, }); } /** * IOS Only */ export function registerForRemoteNotifications() { if (Platform.OS === 'ios') { RetenoSdk.registerForRemoteNotifications(); } } export function setAnonymousUserAttributes( payload: AnonymousUserAttributes ): Promise { return RetenoSdk.setAnonymousUserAttributes(payload); } export function pauseInAppMessages(isPaused: boolean): Promise { return RetenoSdk.pauseInAppMessages(isPaused); } /** * Set the behaviour when InApp messages pause is disabled. * * @param behaviour - 'SKIP_IN_APPS' to skip all messages that occurred during pause, * 'POSTPONE_IN_APPS' to show the first postponed message when pause is lifted * @returns Promise that resolves when the behaviour is set */ export function setInAppMessagesPauseBehaviour( behaviour: InAppPauseBehaviour ): Promise { return RetenoSdk.setInAppMessagesPauseBehaviour(behaviour); } /** * Set user attributes for multi-account mode. * Uses the externalUserId as the account suffix to create a separate device identity. * * @param payload - Contains externalUserId and user attributes * @returns Promise that resolves when attributes are set */ export function setMultiAccountUserAttributes( payload: SetMultiAccountUserAttributesPayload ): Promise { if (!payload.externalUserId) { throw new Error('Missing argument: "externalUserId"'); } return RetenoSdk.setMultiAccountUserAttributes(payload); } /** * * Reteno caches all events (events, device data, user information, user behavior, screen tracking, push statuses, etc) locally into database * Call this function to send all accumulated events */ export function forcePushData(): Promise { if (Platform.OS === 'ios') { // for ios we have to use this hack, because there isn't separate forcePush function as on android, sending an event with forcePush flag does the same thing return logEvent('', new Date().toISOString(), [], true); } else return RetenoSdk.forcePushData(); } /** * Send log screen view event * @param screenName name of the screen */ export function logScreenView(screenName: string) { return logEvent(CustomEventTypes.screenView, new Date().toISOString(), [ { name: CustomEventTypes.screenView, value: screenName }, ]); } /** * * Android only * * Since Android 13 was released you have to make sure you are handling Notification runtime permissions * * When user accepts permission, you have to call updatePushPermissionStatus() function from Reteno interface to notify the Reteno SDK that user has granted the permission. */ export function updatePushPermissionStatusAndroid(): Promise { if (Platform.OS === 'android') { return RetenoSdk.updatePushPermissionStatusAndroid(); } return Promise.resolve(undefined); } export function getAppInboxMessages(payload: GetAppInboxMessages): Promise<{ messages: InboxMessage[]; totalPages: number; }> { return RetenoSdk.getAppInboxMessages(payload); } export function onUnreadMessagesCountChanged(): Promise { return RetenoSdk.onUnreadMessagesCountChanged(); } export function unsubscribeMessagesCountChanged(): Promise { return RetenoSdk.unsubscribeMessagesCountChanged(); } export function unsubscribeAllMessagesCountChanged(): Promise { return RetenoSdk.unsubscribeAllMessagesCountChanged(); } export function unreadMessagesCountHandler( callback: (data: UnreadMessagesCountData) => void ) { return eventEmitter.addListener('reteno-unread-messages-count', (data) => { if (callback && typeof callback === 'function') { callback(data); } }); } /** * Android Only */ export function unreadMessagesCountErrorHandler( callback: (data: UnreadMessagesCountErrorData) => void ) { if (Platform.OS === 'android') { return eventEmitter.addListener( 'reteno-unread-messages-count-error', (data) => { if (callback && typeof callback === 'function') { callback(data); } } ); } return undefined; } export function markAsOpened( messageIds: string[] ): Promise<{ ids: string[]; status: string } | UnreadMessagesCountErrorData> { const response = { ids: messageIds, status: messageIds?.length ? 'OPENED' : '', }; if (Platform.OS === 'android') { return RetenoSdk.markAsOpened(messageIds?.[0]).then( () => response, (error: any) => Promise.reject(error) ); } return RetenoSdk.markAsOpened(messageIds).then( () => response, (error: any) => Promise.reject(error) ); } export function markAllAsOpened(): Promise< { status: string } | UnreadMessagesCountErrorData > { return RetenoSdk.markAllAsOpened().then( () => ({ status: 'OPENED' }), (error: any) => Promise.reject(error) ); } export function getAppInboxMessagesCount(): Promise { return RetenoSdk.getAppInboxMessagesCount(); } //ECOMMERCE EVENTS export type EcomAttribute = { name: string; value: (string | null)[]; }; export type EcomSimpleAttribute = { name: string; value: string; }; export type EcomProductView = { productId: string; price: number; isInStock: boolean; attributes?: EcomAttribute[] | null; }; export type EcomCartItem = { productId: string; quantity: number; price: number; discount?: number | null; name?: string | null; category?: string | null; }; export enum OrderStatus { Initialized, InProgress, Delivered, Cancelled, } export type EcomOrder = { externalOrderId: string; externalCustomerId?: string | null; totalCost: number; status: OrderStatus; cartId?: string | null; email?: string | null; phone?: string | null; firstName?: string | null; lastName?: string | null; shipping?: number | null; discount?: number | null; taxes?: number | null; restoreId?: string | null; statusDescription?: string | null; storeId?: string | null; source?: string | null; deliveryMethod?: string | null; deliveryAddress?: string | null; paymentMethod?: string | null; orderItems?: EcomOrderItem[] | null; attributes?: EcomSimpleAttribute[] | null; }; export type EcomOrderItem = { externalItemId: string; name: string; category: string; quantity: number; price: number; url: string; imageUrl?: string | null; description?: string | null; }; export type EcomCategoryView = { productCategoryId: string; attributes?: EcomAttribute[] | null; }; export type EcomEventProductViewedPayload = { product: EcomProductView; currencyCode?: string | null; }; // Function to log product viewed event export function logEcomEventProductViewed( payload: EcomEventProductViewedPayload ): Promise { return RetenoSdk.logEcomEventProductViewed(payload); } /** * 2. Product Category Viewed Event */ // Type for product category viewed event export type EcomEventProductCategoryViewedPayload = { category: EcomCategoryView; }; // Function to log product category viewed event export function logEcomEventProductCategoryViewed( payload: EcomEventProductCategoryViewedPayload ): Promise { return RetenoSdk.logEcomEventProductCategoryViewed(payload); } /** * 3. Product Added to Wishlist Event */ // Type for product added to wishlist event export type EcomEventProductAddedToWishlistPayload = { product: EcomProductView; currencyCode?: string | null; }; // Function to log product added to wishlist event export function logEcomEventProductAddedToWishlist( payload: EcomEventProductAddedToWishlistPayload ): Promise { return RetenoSdk.logEcomEventProductAddedToWishlist(payload); } /** * 4. Cart Updated Event */ export type EcomEventCartUpdatedPayload = { cartItems: EcomCartItem[]; currencyCode?: string | null; cartId: string; }; // Function to log product added to cart event export function logEcomEventCartUpdated( payload: EcomEventCartUpdatedPayload ): Promise { return RetenoSdk.logEcomEventCartUpdated(payload); } /** * 5. Order Created Event */ export type EcomEventOrderCreatedPayload = { order: EcomOrder; currencyCode?: string | null; }; export function logEcomEventOrderCreated( payload: EcomEventOrderCreatedPayload ): Promise { return RetenoSdk.logEcomEventOrderCreated(payload); } /** * 6. Order Updated */ export type EcomEventOrderUpdatedPayload = { order: EcomOrder; currencyCode?: string | null; }; export function logEcomEventOrderUpdated( payload: EcomEventOrderUpdatedPayload ): Promise { return RetenoSdk.logEcomEventOrderUpdated(payload); } /** * 7. Order Delivered */ export type EcomEventOrderDeliveredPayload = { externalOrderId: string; }; // Function to log checkout started event export function logEcomEventOrderDelivered( payload: EcomEventOrderDeliveredPayload ): Promise { return RetenoSdk.logEcomEventOrderDelivered(payload); } /** * 8. Order Cancelled */ export type EcomEventOrderCancelledPayload = { externalOrderId: string; }; // Function to log order placed event export function logEcomEventOrderCancelled( payload: EcomEventOrderCancelledPayload ): Promise { return RetenoSdk.logEcomEventOrderCancelled(payload); } /** * 9. Search Request Event */ export type EcomEventSearchRequestPayload = { searchQuery: string; isFound: boolean; }; export function logEcomEventSearchRequest( payload: EcomEventSearchRequestPayload ): Promise { return RetenoSdk.logEcomEventSearchRequest(payload); } /** * Android Only * Listen for push notification dismissed (swiped away) events. */ export function setOnRetenoPushDismissedListener( listener: (event: any) => void ): ReturnType | undefined { if (Platform.OS === 'android') { return eventEmitter.addListener('reteno-push-dismissed', listener); } return undefined; } /** * Android Only * Listen for custom push data events (silent/data-only push messages). */ export function setOnRetenoCustomPushDataListener( listener: (event: any) => void ): ReturnType | undefined { if (Platform.OS === 'android') { return eventEmitter.addListener('reteno-custom-push-received', listener); } return undefined; } /** * Android Only * Request notification permission. Returns true if granted, false otherwise. * Uses the new RetenoNotifications API introduced in SDK 2.9.0. */ export function requestNotificationPermission(): Promise { if (Platform.OS === 'android') { return RetenoSdk.requestNotificationPermission(); } return Promise.resolve(false); } /** * Android Only * Get current notification permission status. * Returns 'ALLOWED', 'DENIED', or 'PERMANENTLY_DENIED'. * Uses the new RetenoNotifications API introduced in SDK 2.9.0. */ export function getNotificationPermissionStatus(): Promise { if (Platform.OS === 'android') { return RetenoSdk.getNotificationPermissionStatus(); } return Promise.resolve('ALLOWED' as NotificationPermissionStatus); } /** * Android Only * Pause or resume push-triggered in-app messages specifically. * Introduced in Android SDK 2.9.0. */ export function pausePushInAppMessages(isPaused: boolean): Promise { if (Platform.OS === 'android') { return RetenoSdk.pausePushInAppMessages(isPaused); } return Promise.resolve(undefined); } /** * Android Only * Set the behaviour for push-triggered in-app messages when paused. * Introduced in Android SDK 2.9.0. */ export function setPushInAppMessagesPauseBehaviour( behaviour: InAppPauseBehaviour ): Promise { if (Platform.OS === 'android') { return RetenoSdk.setPushInAppMessagesPauseBehaviour(behaviour); } return Promise.resolve(undefined); }