From 11174ca5ec65e41158b8f5d9a10bb95551eec751 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 7 Apr 2025 13:46:35 -0700 Subject: [PATCH 01/14] refactor: remove the RNIterableAPI module and updating imports across multiple files to use NativeModules directly, as expo does breaks when importing this from another file --- src/core/classes/Iterable.ts | 4 +- src/core/classes/RNIterableAPI.ts | 26 --------- src/inApp/classes/IterableInAppManager.ts | 6 +- src/inbox/classes/IterableInboxDataModel.ts | 6 +- src/inbox/components/IterableInbox.tsx | 65 ++++++--------------- 5 files changed, 29 insertions(+), 78 deletions(-) delete mode 100644 src/core/classes/RNIterableAPI.ts diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 37186978c..fd2c74519 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -1,4 +1,4 @@ -import { Linking, NativeEventEmitter, Platform } from 'react-native'; +import { Linking, NativeEventEmitter, NativeModules, Platform } from 'react-native'; import { buildInfo } from '../../itblBuildInfo'; @@ -18,8 +18,8 @@ import { IterableAuthResponse } from './IterableAuthResponse'; import type { IterableCommerceItem } from './IterableCommerceItem'; import { IterableConfig } from './IterableConfig'; import { IterableLogger } from './IterableLogger'; -import { RNIterableAPI } from './RNIterableAPI'; +const RNIterableAPI = NativeModules.RNIterableAPI; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); /* eslint-disable tsdoc/syntax */ diff --git a/src/core/classes/RNIterableAPI.ts b/src/core/classes/RNIterableAPI.ts deleted file mode 100644 index 30e2d3177..000000000 --- a/src/core/classes/RNIterableAPI.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NativeModules, Platform } from 'react-native'; - -const LINKING_ERROR = - `The package '@iterable/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'; - -/** - * A bridge between React Native and the native Iterable SDK. - * - * If the module is not available, it throws an error when accessed. - * - * @type {object} - * @throws {Error} Throws an error if the RNIterableAPI module is not linked properly. - */ -export const RNIterableAPI = NativeModules.RNIterableAPI - ? NativeModules.RNIterableAPI - : new Proxy( - {}, - { - get() { - throw new Error(LINKING_ERROR); - }, - } - ); diff --git a/src/inApp/classes/IterableInAppManager.ts b/src/inApp/classes/IterableInAppManager.ts index 597403218..a06c57bd8 100644 --- a/src/inApp/classes/IterableInAppManager.ts +++ b/src/inApp/classes/IterableInAppManager.ts @@ -1,4 +1,6 @@ -import { Iterable, RNIterableAPI } from '../../core'; +import { NativeModules } from 'react-native'; + +import { Iterable } from '../../core'; import type { IterableInAppDeleteSource, IterableInAppLocation, @@ -6,6 +8,8 @@ import type { import { IterableHtmlInAppContent } from './IterableHtmlInAppContent'; import { IterableInAppMessage } from './IterableInAppMessage'; +const RNIterableAPI = NativeModules.RNIterableAPI; + /** * Manages in-app messages for the current user. * diff --git a/src/inbox/classes/IterableInboxDataModel.ts b/src/inbox/classes/IterableInboxDataModel.ts index 197fa9887..8a68a4401 100644 --- a/src/inbox/classes/IterableInboxDataModel.ts +++ b/src/inbox/classes/IterableInboxDataModel.ts @@ -1,4 +1,6 @@ -import { Iterable, RNIterableAPI } from '../../core'; +import { NativeModules } from 'react-native'; + +import { Iterable } from '../../core'; import { IterableHtmlInAppContent, IterableInAppDeleteSource, @@ -11,6 +13,8 @@ import type { IterableInboxRowViewModel, } from '../types'; +const RNIterableAPI = NativeModules.RNIterableAPI; + /** * The `IterableInboxDataModel` class provides methods to manage and manipulate * inbox messages. diff --git a/src/inbox/components/IterableInbox.tsx b/src/inbox/components/IterableInbox.tsx index da84c3060..6b7bdf1cd 100644 --- a/src/inbox/components/IterableInbox.tsx +++ b/src/inbox/components/IterableInbox.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import { Animated, NativeEventEmitter, + NativeModules, Platform, StyleSheet, Text, @@ -10,12 +11,7 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { - Iterable, - RNIterableAPI, - useAppStateListener, - useDeviceOrientation, -} from '../../core'; +import { Iterable, useAppStateListener, useDeviceOrientation } from '../../core'; import { IterableInAppDeleteSource, IterableInAppLocation } from '../../inApp'; import { IterableInboxDataModel } from '../classes'; @@ -32,6 +28,7 @@ import { type IterableInboxMessageListProps, } from './IterableInboxMessageList'; +const RNIterableAPI = NativeModules.RNIterableAPI; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); const DEFAULT_HEADLINE_HEIGHT = 60; @@ -43,9 +40,7 @@ const HEADLINE_PADDING_LEFT_LANDSCAPE = 70; * Props for the IterableInbox component. */ export interface IterableInboxProps - extends Partial< - Pick - > { + extends Partial> { /** * Flag which, when switched, returns a user to their inbox from _within_ the * inbox component (from the details of the particular message to the message @@ -201,11 +196,8 @@ export const IterableInbox = ({ const appState = useAppStateListener(); const isFocused = useIsFocused(); - const [selectedRowViewModelIdx, setSelectedRowViewModelIdx] = - useState(0); - const [rowViewModels, setRowViewModels] = useState< - IterableInboxRowViewModel[] - >([]); + const [selectedRowViewModelIdx, setSelectedRowViewModelIdx] = useState(0); + const [rowViewModels, setRowViewModels] = useState([]); const [loading, setLoading] = useState(true); const [animatedValue] = useState(new Animated.Value(0)); const [isMessageDisplay, setIsMessageDisplay] = useState(false); @@ -231,15 +223,10 @@ export const IterableInbox = ({ backgroundColor: ITERABLE_INBOX_COLORS.CONTAINER_BACKGROUND, fontSize: 40, fontWeight: 'bold', - height: - Platform.OS === 'android' - ? ANDROID_HEADLINE_HEIGHT - : DEFAULT_HEADLINE_HEIGHT, + height: Platform.OS === 'android' ? ANDROID_HEADLINE_HEIGHT : DEFAULT_HEADLINE_HEIGHT, marginTop: 0, paddingBottom: 10, - paddingLeft: isPortrait - ? HEADLINE_PADDING_LEFT_PORTRAIT - : HEADLINE_PADDING_LEFT_LANDSCAPE, + paddingLeft: isPortrait ? HEADLINE_PADDING_LEFT_PORTRAIT : HEADLINE_PADDING_LEFT_LANDSCAPE, paddingTop: 10, width: '100%', }, @@ -258,9 +245,7 @@ export const IterableInbox = ({ }); const navTitleHeight = - DEFAULT_HEADLINE_HEIGHT + - styles.headline.paddingTop + - styles.headline.paddingBottom; + DEFAULT_HEADLINE_HEIGHT + styles.headline.paddingTop + styles.headline.paddingBottom; //fetches inbox messages and adds listener for inbox changes on mount useEffect(() => { @@ -348,11 +333,7 @@ export const IterableInbox = ({ return inboxDataModel.getHtmlContentForMessageId(id); } - function handleMessageSelect( - id: string, - index: number, - models: IterableInboxRowViewModel[] - ) { + function handleMessageSelect(id: string, index: number, models: IterableInboxRowViewModel[]) { const newRowViewModels = models.map((rowViewModel) => { return rowViewModel.inAppMessage.messageId === id ? { ...rowViewModel, read: true } @@ -367,17 +348,14 @@ export const IterableInbox = ({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore models[index].inAppMessage, - IterableInAppLocation.inbox + IterableInAppLocation.inbox, ); slideLeft(); } function deleteRow(messageId: string) { - inboxDataModel.deleteItemById( - messageId, - IterableInAppDeleteSource.inboxSwipe - ); + inboxDataModel.deleteItemById(messageId, IterableInAppDeleteSource.inboxSwipe); fetchInboxMessages(); } @@ -390,24 +368,17 @@ export const IterableInbox = ({ setIsMessageDisplay(false); } - function updateVisibleMessageImpressions( - messageImpressions: IterableInboxImpressionRowInfo[] - ) { + function updateVisibleMessageImpressions(messageImpressions: IterableInboxImpressionRowInfo[]) { setVisibleMessageImpressions(messageImpressions); } - function showMessageDisplay( - rowViewModelList: IterableInboxRowViewModel[], - index: number - ) { + function showMessageDisplay(rowViewModelList: IterableInboxRowViewModel[], index: number) { const selectedRowViewModel = rowViewModelList[index]; return selectedRowViewModel ? ( deleteRow(messageId)} contentWidth={width} @@ -421,9 +392,7 @@ export const IterableInbox = ({ {showNavTitle ? ( - {customizations?.navTitle - ? customizations?.navTitle - : defaultInboxTitle} + {customizations?.navTitle ? customizations?.navTitle : defaultInboxTitle} ) : null} {rowViewModels.length ? ( @@ -437,7 +406,7 @@ export const IterableInbox = ({ handleMessageSelect(messageId, index, rowViewModels) } updateVisibleMessageImpressions={( - messageImpressions: IterableInboxImpressionRowInfo[] + messageImpressions: IterableInboxImpressionRowInfo[], ) => updateVisibleMessageImpressions(messageImpressions)} contentWidth={width} isPortrait={isPortrait} From eecd5011d12ffcc03734fb9d716a6f0e8f17f7c9 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 7 Apr 2025 13:49:43 -0700 Subject: [PATCH 02/14] refactor: initialize `inAppContent` to null instead of an empty class, as the empty class was breaking expo --- .../IterableInboxMessageDisplay.tsx | 55 +++++++------------ 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/src/inbox/components/IterableInboxMessageDisplay.tsx b/src/inbox/components/IterableInboxMessageDisplay.tsx index a66fdca8b..0c4e0005f 100644 --- a/src/inbox/components/IterableInboxMessageDisplay.tsx +++ b/src/inbox/components/IterableInboxMessageDisplay.tsx @@ -10,21 +10,15 @@ import { import Icon from 'react-native-vector-icons/Ionicons'; import { WebView, type WebViewMessageEvent } from 'react-native-webview'; -import { - Iterable, - IterableAction, - IterableActionContext, - IterableActionSource, - IterableEdgeInsets, -} from '../../core'; +import { Iterable, IterableAction, IterableActionContext, IterableActionSource } from '../../core'; import { IterableHtmlInAppContent, IterableInAppCloseSource, IterableInAppLocation, } from '../../inApp'; -import { type IterableInboxRowViewModel } from '../types'; import { ITERABLE_INBOX_COLORS } from '../constants'; +import { type IterableInboxRowViewModel } from '../types'; /** * Props for the IterableInboxMessageDisplay component. @@ -75,9 +69,7 @@ export const IterableInboxMessageDisplay = ({ isPortrait, }: IterableInboxMessageDisplayProps) => { const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title; - const [inAppContent, setInAppContent] = useState( - new IterableHtmlInAppContent(new IterableEdgeInsets(0, 0, 0, 0), '') - ); + const [inAppContent, setInAppContent] = useState(null); const styles = StyleSheet.create({ contentContainer: { @@ -179,16 +171,12 @@ export const IterableInboxMessageDisplay = ({ const source = IterableActionSource.inApp; const context = new IterableActionContext(action, source); - Iterable.trackInAppClick( - rowViewModel.inAppMessage, - IterableInAppLocation.inbox, - URL - ); + Iterable.trackInAppClick(rowViewModel.inAppMessage, IterableInAppLocation.inbox, URL); Iterable.trackInAppClose( rowViewModel.inAppMessage, IterableInAppLocation.inbox, IterableInAppCloseSource.link, - URL + URL, ); //handle delete action @@ -228,40 +216,35 @@ export const IterableInboxMessageDisplay = ({ Iterable.trackInAppClose( rowViewModel.inAppMessage, IterableInAppLocation.inbox, - IterableInAppCloseSource.back + IterableInAppCloseSource.back, ); }} > - + Inbox - + {messageTitle} - - handleInAppLinkAction(event)} - injectedJavaScript={JS} - /> - + {inAppContent && ( + + handleInAppLinkAction(event)} + injectedJavaScript={JS} + /> + + )} ); }; From 440b326cee9cd52c145df99bab62198292681148 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 7 Apr 2025 13:50:44 -0700 Subject: [PATCH 03/14] refactor: remove RNIterableAPI export from index.ts --- src/core/classes/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/classes/index.ts b/src/core/classes/index.ts index 3902ad0ba..60c26047c 100644 --- a/src/core/classes/index.ts +++ b/src/core/classes/index.ts @@ -8,4 +8,3 @@ export * from './IterableConfig'; export * from './IterableEdgeInsets'; export * from './IterableLogger'; export * from './IterableUtil'; -export * from './RNIterableAPI'; From f87662dae8ecc1f2a078bb75b827e142189d12c3 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 7 Apr 2025 13:55:30 -0700 Subject: [PATCH 04/14] refactor: adjust logging to use optional chaining for safety, as otherwise there appears to be an error in expo --- lefthook.yml | 28 +++++++++--------- package.json | 2 +- src/core/classes/Iterable.ts | 55 ++++++++++++++++++------------------ src/itblBuildInfo.ts | 2 +- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 8b4be29b7..12e18d7ab 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,14 +1,14 @@ -pre-commit: - parallel: true - commands: - lint: - glob: "*.{js,ts,jsx,tsx}" - run: npx eslint {staged_files} - types: - glob: "*.{js,ts,jsx,tsx}" - run: npx tsc -commit-msg: - parallel: true - commands: - commitlint: - run: npx commitlint --edit +# pre-commit: +# parallel: true +# commands: +# lint: +# glob: "*.{js,ts,jsx,tsx}" +# run: npx eslint {staged_files} +# types: +# glob: "*.{js,ts,jsx,tsx}" +# run: npx tsc +# commit-msg: +# parallel: true +# commands: +# commitlint: +# run: npx commitlint --edit diff --git a/package.json b/package.json index 6675840d4..6b76a7c7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@iterable/react-native-sdk", - "version": "2.0.0-beta", + "version": "2.0.0-beta.expo.53", "description": "Iterable SDK for React Native.", "source": "./src/index.tsx", "main": "./lib/commonjs/index.js", diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index fd2c74519..8e8c4cb6b 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -96,9 +96,10 @@ export class Iterable { Iterable.logger = new IterableLogger(Iterable.savedConfig); - Iterable.logger.log('initialize: ' + apiKey); + Iterable?.logger?.log('initialize: ' + apiKey); this.setupEventHandlers(); + const version = this.getVersionFromPackageJson(); return RNIterableAPI.initializeWithApiKey(apiKey, config.toDict(), version); @@ -119,7 +120,7 @@ export class Iterable { Iterable.logger = new IterableLogger(Iterable.savedConfig); - Iterable.logger.log('initialize2: ' + apiKey); + Iterable?.logger?.log('initialize2: ' + apiKey); this.setupEventHandlers(); const version = this.getVersionFromPackageJson(); @@ -182,7 +183,7 @@ export class Iterable { * ``` */ static setEmail(email?: string | null, authToken?: string | null) { - Iterable.logger.log('setEmail: ' + email); + Iterable?.logger?.log('setEmail: ' + email); RNIterableAPI.setEmail(email, authToken); } @@ -198,7 +199,7 @@ export class Iterable { * ``` */ static getEmail(): Promise { - Iterable.logger.log('getEmail'); + Iterable?.logger?.log('getEmail'); return RNIterableAPI.getEmail(); } @@ -247,7 +248,7 @@ export class Iterable { * taken */ static setUserId(userId?: string | null, authToken?: string | null) { - Iterable.logger.log('setUserId: ' + userId); + Iterable?.logger?.log('setUserId: ' + userId); RNIterableAPI.setUserId(userId, authToken); } @@ -263,7 +264,7 @@ export class Iterable { * ``` */ static getUserId(): Promise { - Iterable.logger.log('getUserId'); + Iterable?.logger?.log('getUserId'); return RNIterableAPI.getUserId(); } @@ -277,7 +278,7 @@ export class Iterable { * ``` */ static disableDeviceForCurrentUser() { - Iterable.logger.log('disableDeviceForCurrentUser'); + Iterable?.logger?.log('disableDeviceForCurrentUser'); RNIterableAPI.disableDeviceForCurrentUser(); } @@ -294,7 +295,7 @@ export class Iterable { * ``` */ static getLastPushPayload(): Promise { - Iterable.logger.log('getLastPushPayload'); + Iterable?.logger?.log('getLastPushPayload'); return RNIterableAPI.getLastPushPayload(); } @@ -322,7 +323,7 @@ export class Iterable { * ``` */ static getAttributionInfo(): Promise { - Iterable.logger.log('getAttributionInfo'); + Iterable?.logger?.log('getAttributionInfo'); return RNIterableAPI.getAttributionInfo().then( (dict?: IterableAttributionInfo) => { @@ -364,7 +365,7 @@ export class Iterable { * ``` */ static setAttributionInfo(attributionInfo?: IterableAttributionInfo) { - Iterable.logger.log('setAttributionInfo'); + Iterable?.logger?.log('setAttributionInfo'); RNIterableAPI.setAttributionInfo(attributionInfo); } @@ -405,7 +406,7 @@ export class Iterable { appAlreadyRunning: boolean, dataFields?: unknown ) { - Iterable.logger.log('trackPushOpenWithCampaignId'); + Iterable?.logger?.log('trackPushOpenWithCampaignId'); RNIterableAPI.trackPushOpenWithCampaignId( campaignId, @@ -443,7 +444,7 @@ export class Iterable { * ``` */ static updateCart(items: IterableCommerceItem[]) { - Iterable.logger.log('updateCart'); + Iterable?.logger?.log('updateCart'); RNIterableAPI.updateCart(items); } @@ -460,7 +461,7 @@ export class Iterable { */ static wakeApp() { if (Platform.OS === 'android') { - Iterable.logger.log('Attempting to wake the app'); + Iterable?.logger?.log('Attempting to wake the app'); RNIterableAPI.wakeApp(); } @@ -495,7 +496,7 @@ export class Iterable { items: IterableCommerceItem[], dataFields?: unknown ) { - Iterable.logger.log('trackPurchase'); + Iterable?.logger?.log('trackPurchase'); RNIterableAPI.trackPurchase(total, items, dataFields); } @@ -523,7 +524,7 @@ export class Iterable { message: IterableInAppMessage, location: IterableInAppLocation ) { - Iterable.logger.log('trackInAppOpen'); + Iterable?.logger?.log('trackInAppOpen'); RNIterableAPI.trackInAppOpen(message.messageId, location); } @@ -554,7 +555,7 @@ export class Iterable { location: IterableInAppLocation, clickedUrl: string ) { - Iterable.logger.log('trackInAppClick'); + Iterable?.logger?.log('trackInAppClick'); RNIterableAPI.trackInAppClick(message.messageId, location, clickedUrl); } @@ -587,7 +588,7 @@ export class Iterable { source: IterableInAppCloseSource, clickedUrl?: string ) { - Iterable.logger.log('trackInAppClose'); + Iterable?.logger?.log('trackInAppClose'); RNIterableAPI.trackInAppClose( message.messageId, @@ -638,7 +639,7 @@ export class Iterable { location: IterableInAppLocation, source: IterableInAppDeleteSource ) { - Iterable.logger.log('inAppConsume'); + Iterable?.logger?.log('inAppConsume'); RNIterableAPI.inAppConsume(message.messageId, location, source); } @@ -664,7 +665,7 @@ export class Iterable { * ``` */ static trackEvent(name: string, dataFields?: unknown) { - Iterable.logger.log('trackEvent'); + Iterable?.logger?.log('trackEvent'); RNIterableAPI.trackEvent(name, dataFields); } @@ -712,7 +713,7 @@ export class Iterable { dataFields: unknown | undefined, mergeNestedObjects: boolean ) { - Iterable.logger.log('updateUser'); + Iterable?.logger?.log('updateUser'); RNIterableAPI.updateUser(dataFields, mergeNestedObjects); } @@ -735,7 +736,7 @@ export class Iterable { * ``` */ static updateEmail(email: string, authToken?: string) { - Iterable.logger.log('updateEmail'); + Iterable?.logger?.log('updateEmail'); RNIterableAPI.updateEmail(email, authToken); } @@ -819,7 +820,7 @@ export class Iterable { */ /* eslint-enable tsdoc/syntax */ static handleAppLink(link: string): Promise { - Iterable.logger.log('handleAppLink'); + Iterable?.logger?.log('handleAppLink'); return RNIterableAPI.handleAppLink(link); } @@ -866,7 +867,7 @@ export class Iterable { campaignId: number, templateId: number ) { - Iterable.logger.log('updateSubscriptions'); + Iterable?.logger?.log('updateSubscriptions'); RNIterableAPI.updateSubscriptions( emailListIds, @@ -976,19 +977,19 @@ export class Iterable { (promiseResult as IterableAuthResponse).failureCallback?.(); } } else { - Iterable.logger.log('No callback received from native layer'); + Iterable?.logger?.log('No callback received from native layer'); } }, 1000); } else if (typeof promiseResult === typeof '') { //If promise only returns string RNIterableAPI.passAlongAuthToken(promiseResult as string); } else { - Iterable.logger.log( + Iterable?.logger?.log( 'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.' ); } }) - .catch((e) => Iterable.logger.log(e)); + .catch((e) => Iterable?.logger?.log(e)); }); RNEventEmitter.addListener( @@ -1016,7 +1017,7 @@ export class Iterable { } }) .catch((reason) => { - Iterable.logger.log('could not open url: ' + reason); + Iterable?.logger?.log('could not open url: ' + reason); }); } } diff --git a/src/itblBuildInfo.ts b/src/itblBuildInfo.ts index e3e489cda..a363c8bc4 100644 --- a/src/itblBuildInfo.ts +++ b/src/itblBuildInfo.ts @@ -3,5 +3,5 @@ * It contains the version of the package */ export const buildInfo = { - version: '2.0.0-beta', + version: '2.0.0-beta.expo.53', }; From 32da43e051c325a4a7a6576c51b61c8fb9baaf7f Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 7 Apr 2025 13:56:17 -0700 Subject: [PATCH 05/14] chore: update version in package.json to 2.0.0-beta --- lefthook.yml | 28 ++++++++++++++-------------- package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 12e18d7ab..8b4be29b7 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,14 +1,14 @@ -# pre-commit: -# parallel: true -# commands: -# lint: -# glob: "*.{js,ts,jsx,tsx}" -# run: npx eslint {staged_files} -# types: -# glob: "*.{js,ts,jsx,tsx}" -# run: npx tsc -# commit-msg: -# parallel: true -# commands: -# commitlint: -# run: npx commitlint --edit +pre-commit: + parallel: true + commands: + lint: + glob: "*.{js,ts,jsx,tsx}" + run: npx eslint {staged_files} + types: + glob: "*.{js,ts,jsx,tsx}" + run: npx tsc +commit-msg: + parallel: true + commands: + commitlint: + run: npx commitlint --edit diff --git a/package.json b/package.json index 6b76a7c7a..6675840d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@iterable/react-native-sdk", - "version": "2.0.0-beta.expo.53", + "version": "2.0.0-beta", "description": "Iterable SDK for React Native.", "source": "./src/index.tsx", "main": "./lib/commonjs/index.js", From 5c6da49424a1b1b7dc832ee9c2bf5ce01d4b9417 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 7 Apr 2025 14:11:50 -0700 Subject: [PATCH 06/14] refactor: update logging to use optional chaining for safety in IterableInAppManager and IterableInboxDataModel, otherwise expo gets upset --- src/inApp/classes/IterableInAppManager.ts | 14 +++++++------- src/inbox/classes/IterableInboxDataModel.ts | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/inApp/classes/IterableInAppManager.ts b/src/inApp/classes/IterableInAppManager.ts index a06c57bd8..33a3ba998 100644 --- a/src/inApp/classes/IterableInAppManager.ts +++ b/src/inApp/classes/IterableInAppManager.ts @@ -36,7 +36,7 @@ export class IterableInAppManager { * @returns A Promise that resolves to an array of in-app messages. */ getMessages(): Promise { - Iterable.logger.log('InAppManager.getMessages'); + Iterable?.logger?.log('InAppManager.getMessages'); return RNIterableAPI.getInAppMessages(); } @@ -59,7 +59,7 @@ export class IterableInAppManager { * @returns A Promise that resolves to an array of messages marked as `saveToInbox`. */ getInboxMessages(): Promise { - Iterable.logger.log('InAppManager.getInboxMessages'); + Iterable?.logger?.log('InAppManager.getInboxMessages'); return RNIterableAPI.getInboxMessages(); } @@ -86,7 +86,7 @@ export class IterableInAppManager { message: IterableInAppMessage, consume: boolean ): Promise { - Iterable.logger.log('InAppManager.show'); + Iterable?.logger?.log('InAppManager.show'); return RNIterableAPI.showMessage(message.messageId, consume); } @@ -114,7 +114,7 @@ export class IterableInAppManager { location: IterableInAppLocation, source: IterableInAppDeleteSource ): void { - Iterable.logger.log('InAppManager.remove'); + Iterable?.logger?.log('InAppManager.remove'); return RNIterableAPI.removeMessage(message.messageId, location, source); } @@ -131,7 +131,7 @@ export class IterableInAppManager { * ``` */ setReadForMessage(message: IterableInAppMessage, read: boolean) { - Iterable.logger.log('InAppManager.setRead'); + Iterable?.logger?.log('InAppManager.setRead'); RNIterableAPI.setReadForMessage(message.messageId, read); } @@ -151,7 +151,7 @@ export class IterableInAppManager { getHtmlContentForMessage( message: IterableInAppMessage ): Promise { - Iterable.logger.log('InAppManager.getHtmlContentForMessage'); + Iterable?.logger?.log('InAppManager.getHtmlContentForMessage'); return RNIterableAPI.getHtmlInAppContentForMessage(message.messageId); } @@ -171,7 +171,7 @@ export class IterableInAppManager { * ``` */ setAutoDisplayPaused(paused: boolean) { - Iterable.logger.log('InAppManager.setAutoDisplayPaused'); + Iterable?.logger?.log('InAppManager.setAutoDisplayPaused'); RNIterableAPI.setAutoDisplayPaused(paused); } diff --git a/src/inbox/classes/IterableInboxDataModel.ts b/src/inbox/classes/IterableInboxDataModel.ts index 8a68a4401..5e21faddb 100644 --- a/src/inbox/classes/IterableInboxDataModel.ts +++ b/src/inbox/classes/IterableInboxDataModel.ts @@ -97,7 +97,7 @@ export class IterableInboxDataModel { * @returns A promise that resolves to the HTML content of the specified message. */ getHtmlContentForMessageId(id: string): Promise { - Iterable.logger.log( + Iterable?.logger?.log( 'IterableInboxDataModel.getHtmlContentForItem messageId: ' + id ); @@ -114,7 +114,7 @@ export class IterableInboxDataModel { * @param id - The unique identifier of the message to be marked as read. */ setMessageAsRead(id: string) { - Iterable.logger.log('IterableInboxDataModel.setMessageAsRead'); + Iterable?.logger?.log('IterableInboxDataModel.setMessageAsRead'); RNIterableAPI.setReadForMessage(id, true); } @@ -126,7 +126,7 @@ export class IterableInboxDataModel { * @param deleteSource - The source from which the delete action is initiated. */ deleteItemById(id: string, deleteSource: IterableInAppDeleteSource) { - Iterable.logger.log('IterableInboxDataModel.deleteItemById'); + Iterable?.logger?.log('IterableInboxDataModel.deleteItemById'); RNIterableAPI.removeMessage(id, IterableInAppLocation.inbox, deleteSource); } From 7aecec57b87e5c6896545928c14335722145b9bf Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 7 Apr 2025 14:24:51 -0700 Subject: [PATCH 07/14] refactor: formatted code --- src/core/classes/Iterable.ts | 130 +++++++------------- src/inApp/classes/IterableInAppManager.ts | 16 +-- src/inbox/classes/IterableInboxDataModel.ts | 41 ++---- 3 files changed, 58 insertions(+), 129 deletions(-) diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 8e8c4cb6b..f57cbaeb7 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -90,7 +90,7 @@ export class Iterable { */ static initialize( apiKey: string, - config: IterableConfig = new IterableConfig() + config: IterableConfig = new IterableConfig(), ): Promise { Iterable.savedConfig = config; @@ -114,7 +114,7 @@ export class Iterable { static initialize2( apiKey: string, config: IterableConfig = new IterableConfig(), - apiEndPoint: string + apiEndPoint: string, ): Promise { Iterable.savedConfig = config; @@ -125,12 +125,7 @@ export class Iterable { this.setupEventHandlers(); const version = this.getVersionFromPackageJson(); - return RNIterableAPI.initialize2WithApiKey( - apiKey, - config.toDict(), - version, - apiEndPoint - ); + return RNIterableAPI.initialize2WithApiKey(apiKey, config.toDict(), version, apiEndPoint); } /** @@ -325,19 +320,13 @@ export class Iterable { static getAttributionInfo(): Promise { Iterable?.logger?.log('getAttributionInfo'); - return RNIterableAPI.getAttributionInfo().then( - (dict?: IterableAttributionInfo) => { - if (dict) { - return new IterableAttributionInfo( - dict.campaignId, - dict.templateId, - dict.messageId - ); - } else { - return undefined; - } + return RNIterableAPI.getAttributionInfo().then((dict?: IterableAttributionInfo) => { + if (dict) { + return new IterableAttributionInfo(dict.campaignId, dict.templateId, dict.messageId); + } else { + return undefined; } - ); + }); } /** @@ -404,7 +393,7 @@ export class Iterable { templateId: number, messageId: string | undefined, appAlreadyRunning: boolean, - dataFields?: unknown + dataFields?: unknown, ) { Iterable?.logger?.log('trackPushOpenWithCampaignId'); @@ -413,7 +402,7 @@ export class Iterable { templateId, messageId, appAlreadyRunning, - dataFields + dataFields, ); } @@ -491,11 +480,7 @@ export class Iterable { * Iterable.trackPurchase(30.0, items, dataFields); * ``` */ - static trackPurchase( - total: number, - items: IterableCommerceItem[], - dataFields?: unknown - ) { + static trackPurchase(total: number, items: IterableCommerceItem[], dataFields?: unknown) { Iterable?.logger?.log('trackPurchase'); RNIterableAPI.trackPurchase(total, items, dataFields); @@ -520,10 +505,7 @@ export class Iterable { * SDK's default rendering. However, it's also possible to manually track * these events by calling this method. */ - static trackInAppOpen( - message: IterableInAppMessage, - location: IterableInAppLocation - ) { + static trackInAppOpen(message: IterableInAppMessage, location: IterableInAppLocation) { Iterable?.logger?.log('trackInAppOpen'); RNIterableAPI.trackInAppOpen(message.messageId, location); @@ -553,7 +535,7 @@ export class Iterable { static trackInAppClick( message: IterableInAppMessage, location: IterableInAppLocation, - clickedUrl: string + clickedUrl: string, ) { Iterable?.logger?.log('trackInAppClick'); @@ -586,16 +568,11 @@ export class Iterable { message: IterableInAppMessage, location: IterableInAppLocation, source: IterableInAppCloseSource, - clickedUrl?: string + clickedUrl?: string, ) { Iterable?.logger?.log('trackInAppClose'); - RNIterableAPI.trackInAppClose( - message.messageId, - location, - source, - clickedUrl - ); + RNIterableAPI.trackInAppClose(message.messageId, location, source, clickedUrl); } /** @@ -637,7 +614,7 @@ export class Iterable { static inAppConsume( message: IterableInAppMessage, location: IterableInAppLocation, - source: IterableInAppDeleteSource + source: IterableInAppDeleteSource, ) { Iterable?.logger?.log('inAppConsume'); @@ -709,10 +686,7 @@ export class Iterable { * @remarks * **IMPORTANT**: `mergeNestedObjects` only works for data that is stored up to one level deep within an object (for example, `{mySettings:{mobile:true}}`). Note that `mergeNestedObjects` applies to objects, not arrays. */ - static updateUser( - dataFields: unknown | undefined, - mergeNestedObjects: boolean - ) { + static updateUser(dataFields: unknown | undefined, mergeNestedObjects: boolean) { Iterable?.logger?.log('updateUser'); RNIterableAPI.updateUser(dataFields, mergeNestedObjects); @@ -865,7 +839,7 @@ export class Iterable { unsubscribedMessageTypeIds: number[] | undefined, subscribedMessageTypeIds: number[] | undefined, campaignId: number, - templateId: number + templateId: number, ) { Iterable?.logger?.log('updateSubscriptions'); @@ -875,7 +849,7 @@ export class Iterable { unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, - templateId + templateId, ); } @@ -903,9 +877,7 @@ export class Iterable { //Remove all listeners to avoid duplicate listeners RNEventEmitter.removeAllListeners(IterableEventName.handleUrlCalled); RNEventEmitter.removeAllListeners(IterableEventName.handleInAppCalled); - RNEventEmitter.removeAllListeners( - IterableEventName.handleCustomActionCalled - ); + RNEventEmitter.removeAllListeners(IterableEventName.handleCustomActionCalled); RNEventEmitter.removeAllListeners(IterableEventName.handleAuthCalled); if (Iterable.savedConfig.urlHandler) { @@ -926,26 +898,20 @@ export class Iterable { } if (Iterable.savedConfig.customActionHandler) { - RNEventEmitter.addListener( - IterableEventName.handleCustomActionCalled, - (dict) => { - const action = IterableAction.fromDict(dict.action); - const context = IterableActionContext.fromDict(dict.context); - Iterable.savedConfig.customActionHandler!(action, context); - } - ); + RNEventEmitter.addListener(IterableEventName.handleCustomActionCalled, (dict) => { + const action = IterableAction.fromDict(dict.action); + const context = IterableActionContext.fromDict(dict.context); + Iterable.savedConfig.customActionHandler!(action, context); + }); } if (Iterable.savedConfig.inAppHandler) { - RNEventEmitter.addListener( - IterableEventName.handleInAppCalled, - (messageDict) => { - const message = IterableInAppMessage.fromDict(messageDict); - // MOB-10423: Check if we can use chain operator (?.) here instead - const result = Iterable.savedConfig.inAppHandler!(message); - RNIterableAPI.setInAppShowResponse(result); - } - ); + RNEventEmitter.addListener(IterableEventName.handleInAppCalled, (messageDict) => { + const message = IterableInAppMessage.fromDict(messageDict); + // MOB-10423: Check if we can use chain operator (?.) here instead + const result = Iterable.savedConfig.inAppHandler!(message); + RNIterableAPI.setInAppShowResponse(result); + }); } if (Iterable.savedConfig.authHandler) { @@ -959,20 +925,14 @@ export class Iterable { // If type AuthReponse, authToken will be parsed looking for `authToken` within promised object. Two additional listeners will be registered for success and failure callbacks sent by native bridge layer. // Else it will be looked for as a String. if (typeof promiseResult === typeof new IterableAuthResponse()) { - RNIterableAPI.passAlongAuthToken( - (promiseResult as IterableAuthResponse).authToken - ); + RNIterableAPI.passAlongAuthToken((promiseResult as IterableAuthResponse).authToken); setTimeout(() => { - if ( - authResponseCallback === IterableAuthResponseResult.SUCCESS - ) { + if (authResponseCallback === IterableAuthResponseResult.SUCCESS) { if ((promiseResult as IterableAuthResponse).successCallback) { (promiseResult as IterableAuthResponse).successCallback?.(); } - } else if ( - authResponseCallback === IterableAuthResponseResult.FAILURE - ) { + } else if (authResponseCallback === IterableAuthResponseResult.FAILURE) { if ((promiseResult as IterableAuthResponse).failureCallback) { (promiseResult as IterableAuthResponse).failureCallback?.(); } @@ -985,25 +945,19 @@ export class Iterable { RNIterableAPI.passAlongAuthToken(promiseResult as string); } else { Iterable?.logger?.log( - 'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.' + 'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.', ); } }) .catch((e) => Iterable?.logger?.log(e)); }); - RNEventEmitter.addListener( - IterableEventName.handleAuthSuccessCalled, - () => { - authResponseCallback = IterableAuthResponseResult.SUCCESS; - } - ); - RNEventEmitter.addListener( - IterableEventName.handleAuthFailureCalled, - () => { - authResponseCallback = IterableAuthResponseResult.FAILURE; - } - ); + RNEventEmitter.addListener(IterableEventName.handleAuthSuccessCalled, () => { + authResponseCallback = IterableAuthResponseResult.SUCCESS; + }); + RNEventEmitter.addListener(IterableEventName.handleAuthFailureCalled, () => { + authResponseCallback = IterableAuthResponseResult.FAILURE; + }); } function callUrlHandler(url: string, context: IterableActionContext) { diff --git a/src/inApp/classes/IterableInAppManager.ts b/src/inApp/classes/IterableInAppManager.ts index 33a3ba998..6b2401133 100644 --- a/src/inApp/classes/IterableInAppManager.ts +++ b/src/inApp/classes/IterableInAppManager.ts @@ -1,10 +1,7 @@ import { NativeModules } from 'react-native'; import { Iterable } from '../../core'; -import type { - IterableInAppDeleteSource, - IterableInAppLocation, -} from '../enums'; +import type { IterableInAppDeleteSource, IterableInAppLocation } from '../enums'; import { IterableHtmlInAppContent } from './IterableHtmlInAppContent'; import { IterableInAppMessage } from './IterableInAppMessage'; @@ -82,10 +79,7 @@ export class IterableInAppManager { * * @returns A Promise that resolves to the URL of the button or link the user tapped to close the in-app message. */ - showMessage( - message: IterableInAppMessage, - consume: boolean - ): Promise { + showMessage(message: IterableInAppMessage, consume: boolean): Promise { Iterable?.logger?.log('InAppManager.show'); return RNIterableAPI.showMessage(message.messageId, consume); @@ -112,7 +106,7 @@ export class IterableInAppManager { removeMessage( message: IterableInAppMessage, location: IterableInAppLocation, - source: IterableInAppDeleteSource + source: IterableInAppDeleteSource, ): void { Iterable?.logger?.log('InAppManager.remove'); @@ -148,9 +142,7 @@ export class IterableInAppManager { * Iterable.inAppManager.getHtmlContentForMessage(message); * ``` */ - getHtmlContentForMessage( - message: IterableInAppMessage - ): Promise { + getHtmlContentForMessage(message: IterableInAppMessage): Promise { Iterable?.logger?.log('InAppManager.getHtmlContentForMessage'); return RNIterableAPI.getHtmlInAppContentForMessage(message.messageId); diff --git a/src/inbox/classes/IterableInboxDataModel.ts b/src/inbox/classes/IterableInboxDataModel.ts index 5e21faddb..0e5d494e0 100644 --- a/src/inbox/classes/IterableInboxDataModel.ts +++ b/src/inbox/classes/IterableInboxDataModel.ts @@ -8,10 +8,7 @@ import { IterableInAppMessage, type IterableHtmlInAppContentRaw, } from '../../inApp'; -import type { - IterableInboxImpressionRowInfo, - IterableInboxRowViewModel, -} from '../types'; +import type { IterableInboxImpressionRowInfo, IterableInboxRowViewModel } from '../types'; const RNIterableAPI = NativeModules.RNIterableAPI; @@ -39,10 +36,7 @@ export class IterableInboxDataModel { * a positive number if `message1` should come after `message2`, * or 0 if they are considered equal. */ - comparatorFn?: ( - message1: IterableInAppMessage, - message2: IterableInAppMessage - ) => number; + comparatorFn?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number; /** * Optional function to map an IterableInAppMessage to a date string or undefined. * This function can be used to extract and format the date from a message. @@ -61,11 +55,8 @@ export class IterableInboxDataModel { */ set( filter?: (message: IterableInAppMessage) => boolean, - comparator?: ( - message1: IterableInAppMessage, - message2: IterableInAppMessage - ) => number, - dateMapper?: (message: IterableInAppMessage) => string | undefined + comparator?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number, + dateMapper?: (message: IterableInAppMessage) => string | undefined, ) { this.filterFn = filter; this.comparatorFn = comparator; @@ -97,14 +88,12 @@ export class IterableInboxDataModel { * @returns A promise that resolves to the HTML content of the specified message. */ getHtmlContentForMessageId(id: string): Promise { - Iterable?.logger?.log( - 'IterableInboxDataModel.getHtmlContentForItem messageId: ' + id - ); + Iterable?.logger?.log('IterableInboxDataModel.getHtmlContentForItem messageId: ' + id); return RNIterableAPI.getHtmlInAppContentForMessage(id).then( (content: IterableHtmlInAppContentRaw) => { return IterableHtmlInAppContent.fromDict(content); - } + }, ); } @@ -144,7 +133,7 @@ export class IterableInboxDataModel { }, () => { return []; - } + }, ); } @@ -196,7 +185,7 @@ export class IterableInboxDataModel { */ private static sortByMostRecent = ( message1: IterableInAppMessage, - message2: IterableInAppMessage + message2: IterableInAppMessage, ) => { const createdAt1 = message1.createdAt ?? new Date(0); const createdAt2 = message2.createdAt ?? new Date(0); @@ -239,12 +228,8 @@ export class IterableInboxDataModel { * @param messages - An array of `IterableInAppMessage` objects to be processed. * @returns An array of `IterableInboxRowViewModel` objects representing the processed messages. */ - private processMessages( - messages: IterableInAppMessage[] - ): IterableInboxRowViewModel[] { - return this.sortAndFilter(messages).map( - IterableInboxDataModel.getInboxRowViewModelForMessage - ); + private processMessages(messages: IterableInAppMessage[]): IterableInboxRowViewModel[] { + return this.sortAndFilter(messages).map(IterableInboxDataModel.getInboxRowViewModelForMessage); } /** @@ -253,9 +238,7 @@ export class IterableInboxDataModel { * @param messages - The array of messages to be sorted and filtered. * @returns The sorted and filtered array of messages. */ - private sortAndFilter( - messages: IterableInAppMessage[] - ): IterableInAppMessage[] { + private sortAndFilter(messages: IterableInAppMessage[]): IterableInAppMessage[] { let sortedFilteredMessages = messages.slice(); // MOB-10424: Figure out if this is purposeful @@ -282,7 +265,7 @@ export class IterableInboxDataModel { * @returns An object representing the inbox row view model. */ private static getInboxRowViewModelForMessage( - message: IterableInAppMessage + message: IterableInAppMessage, ): IterableInboxRowViewModel { return { title: message.inboxMetadata?.title ?? '', From 00ee6ad41facd1bf6b42cdf670472097c8388f2e Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 9 Apr 2025 10:59:09 -0700 Subject: [PATCH 08/14] chore: update react native builder bob --- .nvmrc | 2 +- babel.config.js | 4 +--- package.json | 30 ++++++++++------------- yarn.lock | 63 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/.nvmrc b/.nvmrc index 3f430af82..3bf34c276 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 +v20.19.0 \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index 29f3a6069..5d51f258a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,5 +1,3 @@ module.exports = { - presets: [ - ['module:react-native-builder-bob/babel-preset', { modules: 'commonjs' }], - ], + presets: ['module:react-native-builder-bob/babel-preset'], }; diff --git a/package.json b/package.json index 6675840d4..bbd255b4d 100644 --- a/package.json +++ b/package.json @@ -3,19 +3,14 @@ "version": "2.0.0-beta", "description": "Iterable SDK for React Native.", "source": "./src/index.tsx", - "main": "./lib/commonjs/index.js", - "module": "./lib/module/index.js", + "main": "./lib/module/index.js", + "types": "./lib/typescript/src/index.d.ts", "exports": { ".": { - "import": { - "types": "./lib/typescript/module/src/index.d.ts", + "types": "./lib/typescript/src/index.d.ts", "default": "./lib/module/index.js" }, - "require": { - "types": "./lib/typescript/commonjs/src/index.d.ts", - "default": "./lib/commonjs/index.js" - } - } + "./package.json": "./package.json" }, "files": [ "src", @@ -93,7 +88,7 @@ "prettier": "^3.0.3", "react": "18.3.1", "react-native": "0.75.3", - "react-native-builder-bob": "^0.30.2", + "react-native-builder-bob": "^0.40.4", "react-native-safe-area-context": "^4.11.1", "react-native-vector-icons": "^10.2.0", "react-native-webview": "^13.12.3", @@ -115,6 +110,12 @@ "react-native-vector-icons": "*", "react-native-webview": "*" }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + }, + "sideEffects": false, "workspaces": [ "example" ], @@ -145,12 +146,6 @@ "source": "src", "output": "lib", "targets": [ - [ - "commonjs", - { - "esm": true - } - ], [ "module", { @@ -160,8 +155,7 @@ [ "typescript", { - "project": "tsconfig.build.json", - "esm": true + "project": "tsconfig.build.json" } ] ] diff --git a/yarn.lock b/yarn.lock index 83f70e75c..cf30f91fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,22 @@ __metadata: languageName: node linkType: hard +"@ark/schema@npm:0.45.9": + version: 0.45.9 + resolution: "@ark/schema@npm:0.45.9" + dependencies: + "@ark/util": 0.45.9 + checksum: 8d28c910ef6ae379c61a82db2f7e8160d96eb25fb73a56bda5f9c63cc86abca12552d2bb8cd3dd9aff010f5464f9834e33285eca51ea7da16f2143e050cc901a + languageName: node + linkType: hard + +"@ark/util@npm:0.45.9": + version: 0.45.9 + resolution: "@ark/util@npm:0.45.9" + checksum: ddd1fc89c45b61e5d52cb92203990492a5115aea58a8e8bf5ff24e28103fce331593e0c374a086554fb6feb375ddd759c07e751aadc7f3ab3c6138dc3ee362cf + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.24.7": version: 7.24.7 resolution: "@babel/code-frame@npm:7.24.7" @@ -3077,7 +3093,7 @@ __metadata: prettier: ^3.0.3 react: 18.3.1 react-native: 0.75.3 - react-native-builder-bob: ^0.30.2 + react-native-builder-bob: ^0.40.4 react-native-safe-area-context: ^4.11.1 react-native-vector-icons: ^10.2.0 react-native-webview: ^13.12.3 @@ -3094,6 +3110,9 @@ __metadata: react-native-safe-area-context: "*" react-native-vector-icons: "*" react-native-webview: "*" + peerDependenciesMeta: + expo: + optional: true languageName: unknown linkType: soft @@ -5129,6 +5148,16 @@ __metadata: languageName: node linkType: hard +"arktype@npm:^2.1.15": + version: 2.1.19 + resolution: "arktype@npm:2.1.19" + dependencies: + "@ark/schema": 0.45.9 + "@ark/util": 0.45.9 + checksum: cf656f9aa3797d56572d49a8499a4156fbbe25eacbd075f4c60770876fd1fbde6b6285f1b367de93e14858651f6a1df1db3de99d6a5f642e1fa61f421fae0712 + languageName: node + linkType: hard + "array-buffer-byte-length@npm:^1.0.1": version: 1.0.1 resolution: "array-buffer-byte-length@npm:1.0.1" @@ -12660,6 +12689,38 @@ __metadata: languageName: node linkType: hard +"react-native-builder-bob@npm:^0.40.4": + version: 0.40.4 + resolution: "react-native-builder-bob@npm:0.40.4" + dependencies: + "@babel/core": ^7.25.2 + "@babel/plugin-transform-strict-mode": ^7.24.7 + "@babel/preset-env": ^7.25.2 + "@babel/preset-flow": ^7.24.7 + "@babel/preset-react": ^7.24.7 + "@babel/preset-typescript": ^7.24.7 + arktype: ^2.1.15 + babel-plugin-module-resolver: ^5.0.2 + browserslist: ^4.20.4 + cross-spawn: ^7.0.3 + dedent: ^0.7.0 + del: ^6.1.1 + escape-string-regexp: ^4.0.0 + fs-extra: ^10.1.0 + glob: ^8.0.3 + is-git-dirty: ^2.0.1 + json5: ^2.2.1 + kleur: ^4.1.4 + metro-config: ^0.80.9 + prompts: ^2.4.2 + which: ^2.0.2 + yargs: ^17.5.1 + bin: + bob: bin/bob + checksum: 2b3576a5b3afb142400427f51197485fd0a8d7611b2ca6edb610fbcf320279c5951846c3ebe00715904e808f91106367e1f95c4e32f29405a07fb517a87927bb + languageName: node + linkType: hard + "react-native-dotenv@npm:^3.4.11": version: 3.4.11 resolution: "react-native-dotenv@npm:3.4.11" From 525eab259308f4a8e42636e9489526dd8f7c41d5 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 9 Apr 2025 11:25:13 -0700 Subject: [PATCH 09/14] refactor: remove Iterable.inAppManager and update imports to resolve circular dependencies --- src/__tests__/IterableInApp.test.ts | 30 ++-- src/core/classes/Iterable.ts | 151 ++++++++++++------ src/inApp/classes/IterableInAppManager.ts | 18 ++- src/inbox/classes/IterableInboxDataModel.ts | 43 +++-- src/inbox/components/IterableInbox.tsx | 62 +++++-- .../IterableInboxMessageDisplay.tsx | 33 +++- 6 files changed, 231 insertions(+), 106 deletions(-) diff --git a/src/__tests__/IterableInApp.test.ts b/src/__tests__/IterableInApp.test.ts index c322d7c5a..282d83e01 100644 --- a/src/__tests__/IterableInApp.test.ts +++ b/src/__tests__/IterableInApp.test.ts @@ -1,6 +1,8 @@ import { NativeEventEmitter } from 'react-native'; import { IterableLogger } from '../core'; +import { IterableInAppManager } from '../inApp'; + import { MockRNIterableAPI } from '../__mocks__/MockRNIterableAPI'; import { @@ -200,13 +202,12 @@ describe('Iterable In App', () => { // WHEN the simulated local queue is set to the in-app messages MockRNIterableAPI.setMessages(messages); + const inAppManager = new IterableInAppManager(); // THEN Iterable,inAppManager.getMessages returns the list of in-app messages - return await Iterable.inAppManager - .getMessages() - .then((messagesObtained) => { - expect(messagesObtained).toEqual(messages); - }); + return await inAppManager.getMessages().then((messagesObtained) => { + expect(messagesObtained).toEqual(messages); + }); }); test('showMessage_messageAndConsume_returnsClickedUrl', async () => { @@ -223,13 +224,11 @@ describe('Iterable In App', () => { // WHEN the simulated clicked url is set to the clicked url MockRNIterableAPI.setClickedUrl(clickedUrl); - + const inAppManager = new IterableInAppManager(); // THEN Iterable,inAppManager.showMessage returns the simulated clicked url - return await Iterable.inAppManager - .showMessage(message, consume) - .then((url) => { - expect(url).toEqual(clickedUrl); - }); + return await inAppManager.showMessage(message, consume).then((url) => { + expect(url).toEqual(clickedUrl); + }); }); test('removeMessage_params_methodCalledWithParams', () => { @@ -243,9 +242,10 @@ describe('Iterable In App', () => { const location: IterableInAppLocation = IterableInAppLocation.inApp; const source: IterableInAppDeleteSource = IterableInAppDeleteSource.deleteButton; + const inAppManager = new IterableInAppManager(); // WHEN Iterable.inAppManager.removeMessage is called - Iterable.inAppManager.removeMessage(message, location, source); + inAppManager.removeMessage(message, location, source); // THEN corresponding method is called on MockIterableAPI with appropriate parameters expect(MockRNIterableAPI.removeMessage).toBeCalledWith( @@ -266,7 +266,8 @@ describe('Iterable In App', () => { const read: boolean = true; // WHEN Iterable.inAppManager.setReadForMessage is called - Iterable.inAppManager.setReadForMessage(message, read); + const inAppManager = new IterableInAppManager(); + inAppManager.setReadForMessage(message, read); // THEN corresponding method is called on MockRNIterableAPI with appropriate parameters expect(MockRNIterableAPI.setReadForMessage).toBeCalledWith( @@ -280,7 +281,8 @@ describe('Iterable In App', () => { const paused: boolean = true; // WHEN Iterable.inAppManager.setAutoDisplayPaused is called - Iterable.inAppManager.setAutoDisplayPaused(paused); + const inAppManager = new IterableInAppManager(); + inAppManager.setAutoDisplayPaused(paused); // THEN corresponding method is called on MockRNIterableAPI with appropriate parameters expect(MockRNIterableAPI.setAutoDisplayPaused).toBeCalledWith(paused); diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index f57cbaeb7..a7d68d156 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -1,4 +1,9 @@ -import { Linking, NativeEventEmitter, NativeModules, Platform } from 'react-native'; +import { + Linking, + NativeEventEmitter, + NativeModules, + Platform, +} from 'react-native'; import { buildInfo } from '../../itblBuildInfo'; @@ -6,7 +11,6 @@ import { IterableInAppCloseSource, IterableInAppDeleteSource, IterableInAppLocation, - IterableInAppManager, IterableInAppMessage, } from '../../inApp'; import { IterableAuthResponseResult, IterableEventName } from '../enums'; @@ -41,21 +45,16 @@ const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); */ /* eslint-enable tsdoc/syntax */ export class Iterable { - /** - * Manager for in app messages - */ - static inAppManager = new IterableInAppManager(); - /** * Logger for the Iterable SDK * Log level is set with {@link IterableLogLevel} */ - static logger: IterableLogger; + static logger: IterableLogger = new IterableLogger(new IterableConfig()); /** * Current configuration of the Iterable SDK */ - static savedConfig: IterableConfig; + static savedConfig: IterableConfig = new IterableConfig(); /** * Initializes the Iterable React Native SDK in your app's Javascript or Typescript code. @@ -90,7 +89,7 @@ export class Iterable { */ static initialize( apiKey: string, - config: IterableConfig = new IterableConfig(), + config: IterableConfig = new IterableConfig() ): Promise { Iterable.savedConfig = config; @@ -114,7 +113,7 @@ export class Iterable { static initialize2( apiKey: string, config: IterableConfig = new IterableConfig(), - apiEndPoint: string, + apiEndPoint: string ): Promise { Iterable.savedConfig = config; @@ -125,7 +124,12 @@ export class Iterable { this.setupEventHandlers(); const version = this.getVersionFromPackageJson(); - return RNIterableAPI.initialize2WithApiKey(apiKey, config.toDict(), version, apiEndPoint); + return RNIterableAPI.initialize2WithApiKey( + apiKey, + config.toDict(), + version, + apiEndPoint + ); } /** @@ -320,13 +324,19 @@ export class Iterable { static getAttributionInfo(): Promise { Iterable?.logger?.log('getAttributionInfo'); - return RNIterableAPI.getAttributionInfo().then((dict?: IterableAttributionInfo) => { - if (dict) { - return new IterableAttributionInfo(dict.campaignId, dict.templateId, dict.messageId); - } else { - return undefined; + return RNIterableAPI.getAttributionInfo().then( + (dict?: IterableAttributionInfo) => { + if (dict) { + return new IterableAttributionInfo( + dict.campaignId, + dict.templateId, + dict.messageId + ); + } else { + return undefined; + } } - }); + ); } /** @@ -393,7 +403,7 @@ export class Iterable { templateId: number, messageId: string | undefined, appAlreadyRunning: boolean, - dataFields?: unknown, + dataFields?: unknown ) { Iterable?.logger?.log('trackPushOpenWithCampaignId'); @@ -402,7 +412,7 @@ export class Iterable { templateId, messageId, appAlreadyRunning, - dataFields, + dataFields ); } @@ -480,7 +490,11 @@ export class Iterable { * Iterable.trackPurchase(30.0, items, dataFields); * ``` */ - static trackPurchase(total: number, items: IterableCommerceItem[], dataFields?: unknown) { + static trackPurchase( + total: number, + items: IterableCommerceItem[], + dataFields?: unknown + ) { Iterable?.logger?.log('trackPurchase'); RNIterableAPI.trackPurchase(total, items, dataFields); @@ -505,7 +519,10 @@ export class Iterable { * SDK's default rendering. However, it's also possible to manually track * these events by calling this method. */ - static trackInAppOpen(message: IterableInAppMessage, location: IterableInAppLocation) { + static trackInAppOpen( + message: IterableInAppMessage, + location: IterableInAppLocation + ) { Iterable?.logger?.log('trackInAppOpen'); RNIterableAPI.trackInAppOpen(message.messageId, location); @@ -535,7 +552,7 @@ export class Iterable { static trackInAppClick( message: IterableInAppMessage, location: IterableInAppLocation, - clickedUrl: string, + clickedUrl: string ) { Iterable?.logger?.log('trackInAppClick'); @@ -568,11 +585,16 @@ export class Iterable { message: IterableInAppMessage, location: IterableInAppLocation, source: IterableInAppCloseSource, - clickedUrl?: string, + clickedUrl?: string ) { Iterable?.logger?.log('trackInAppClose'); - RNIterableAPI.trackInAppClose(message.messageId, location, source, clickedUrl); + RNIterableAPI.trackInAppClose( + message.messageId, + location, + source, + clickedUrl + ); } /** @@ -614,7 +636,7 @@ export class Iterable { static inAppConsume( message: IterableInAppMessage, location: IterableInAppLocation, - source: IterableInAppDeleteSource, + source: IterableInAppDeleteSource ) { Iterable?.logger?.log('inAppConsume'); @@ -686,7 +708,10 @@ export class Iterable { * @remarks * **IMPORTANT**: `mergeNestedObjects` only works for data that is stored up to one level deep within an object (for example, `{mySettings:{mobile:true}}`). Note that `mergeNestedObjects` applies to objects, not arrays. */ - static updateUser(dataFields: unknown | undefined, mergeNestedObjects: boolean) { + static updateUser( + dataFields: unknown | undefined, + mergeNestedObjects: boolean + ) { Iterable?.logger?.log('updateUser'); RNIterableAPI.updateUser(dataFields, mergeNestedObjects); @@ -839,7 +864,7 @@ export class Iterable { unsubscribedMessageTypeIds: number[] | undefined, subscribedMessageTypeIds: number[] | undefined, campaignId: number, - templateId: number, + templateId: number ) { Iterable?.logger?.log('updateSubscriptions'); @@ -849,7 +874,7 @@ export class Iterable { unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, - templateId, + templateId ); } @@ -877,7 +902,9 @@ export class Iterable { //Remove all listeners to avoid duplicate listeners RNEventEmitter.removeAllListeners(IterableEventName.handleUrlCalled); RNEventEmitter.removeAllListeners(IterableEventName.handleInAppCalled); - RNEventEmitter.removeAllListeners(IterableEventName.handleCustomActionCalled); + RNEventEmitter.removeAllListeners( + IterableEventName.handleCustomActionCalled + ); RNEventEmitter.removeAllListeners(IterableEventName.handleAuthCalled); if (Iterable.savedConfig.urlHandler) { @@ -898,20 +925,26 @@ export class Iterable { } if (Iterable.savedConfig.customActionHandler) { - RNEventEmitter.addListener(IterableEventName.handleCustomActionCalled, (dict) => { - const action = IterableAction.fromDict(dict.action); - const context = IterableActionContext.fromDict(dict.context); - Iterable.savedConfig.customActionHandler!(action, context); - }); + RNEventEmitter.addListener( + IterableEventName.handleCustomActionCalled, + (dict) => { + const action = IterableAction.fromDict(dict.action); + const context = IterableActionContext.fromDict(dict.context); + Iterable.savedConfig.customActionHandler!(action, context); + } + ); } if (Iterable.savedConfig.inAppHandler) { - RNEventEmitter.addListener(IterableEventName.handleInAppCalled, (messageDict) => { - const message = IterableInAppMessage.fromDict(messageDict); - // MOB-10423: Check if we can use chain operator (?.) here instead - const result = Iterable.savedConfig.inAppHandler!(message); - RNIterableAPI.setInAppShowResponse(result); - }); + RNEventEmitter.addListener( + IterableEventName.handleInAppCalled, + (messageDict) => { + const message = IterableInAppMessage.fromDict(messageDict); + // MOB-10423: Check if we can use chain operator (?.) here instead + const result = Iterable.savedConfig.inAppHandler!(message); + RNIterableAPI.setInAppShowResponse(result); + } + ); } if (Iterable.savedConfig.authHandler) { @@ -925,19 +958,27 @@ export class Iterable { // If type AuthReponse, authToken will be parsed looking for `authToken` within promised object. Two additional listeners will be registered for success and failure callbacks sent by native bridge layer. // Else it will be looked for as a String. if (typeof promiseResult === typeof new IterableAuthResponse()) { - RNIterableAPI.passAlongAuthToken((promiseResult as IterableAuthResponse).authToken); + RNIterableAPI.passAlongAuthToken( + (promiseResult as IterableAuthResponse).authToken + ); setTimeout(() => { - if (authResponseCallback === IterableAuthResponseResult.SUCCESS) { + if ( + authResponseCallback === IterableAuthResponseResult.SUCCESS + ) { if ((promiseResult as IterableAuthResponse).successCallback) { (promiseResult as IterableAuthResponse).successCallback?.(); } - } else if (authResponseCallback === IterableAuthResponseResult.FAILURE) { + } else if ( + authResponseCallback === IterableAuthResponseResult.FAILURE + ) { if ((promiseResult as IterableAuthResponse).failureCallback) { (promiseResult as IterableAuthResponse).failureCallback?.(); } } else { - Iterable?.logger?.log('No callback received from native layer'); + Iterable?.logger?.log( + 'No callback received from native layer' + ); } }, 1000); } else if (typeof promiseResult === typeof '') { @@ -945,19 +986,25 @@ export class Iterable { RNIterableAPI.passAlongAuthToken(promiseResult as string); } else { Iterable?.logger?.log( - 'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.', + 'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.' ); } }) .catch((e) => Iterable?.logger?.log(e)); }); - RNEventEmitter.addListener(IterableEventName.handleAuthSuccessCalled, () => { - authResponseCallback = IterableAuthResponseResult.SUCCESS; - }); - RNEventEmitter.addListener(IterableEventName.handleAuthFailureCalled, () => { - authResponseCallback = IterableAuthResponseResult.FAILURE; - }); + RNEventEmitter.addListener( + IterableEventName.handleAuthSuccessCalled, + () => { + authResponseCallback = IterableAuthResponseResult.SUCCESS; + } + ); + RNEventEmitter.addListener( + IterableEventName.handleAuthFailureCalled, + () => { + authResponseCallback = IterableAuthResponseResult.FAILURE; + } + ); } function callUrlHandler(url: string, context: IterableActionContext) { diff --git a/src/inApp/classes/IterableInAppManager.ts b/src/inApp/classes/IterableInAppManager.ts index 6b2401133..640b99d50 100644 --- a/src/inApp/classes/IterableInAppManager.ts +++ b/src/inApp/classes/IterableInAppManager.ts @@ -1,7 +1,10 @@ import { NativeModules } from 'react-native'; -import { Iterable } from '../../core'; -import type { IterableInAppDeleteSource, IterableInAppLocation } from '../enums'; +import { Iterable } from '../../core/classes/Iterable'; +import type { + IterableInAppDeleteSource, + IterableInAppLocation, +} from '../enums'; import { IterableHtmlInAppContent } from './IterableHtmlInAppContent'; import { IterableInAppMessage } from './IterableInAppMessage'; @@ -79,7 +82,10 @@ export class IterableInAppManager { * * @returns A Promise that resolves to the URL of the button or link the user tapped to close the in-app message. */ - showMessage(message: IterableInAppMessage, consume: boolean): Promise { + showMessage( + message: IterableInAppMessage, + consume: boolean + ): Promise { Iterable?.logger?.log('InAppManager.show'); return RNIterableAPI.showMessage(message.messageId, consume); @@ -106,7 +112,7 @@ export class IterableInAppManager { removeMessage( message: IterableInAppMessage, location: IterableInAppLocation, - source: IterableInAppDeleteSource, + source: IterableInAppDeleteSource ): void { Iterable?.logger?.log('InAppManager.remove'); @@ -142,7 +148,9 @@ export class IterableInAppManager { * Iterable.inAppManager.getHtmlContentForMessage(message); * ``` */ - getHtmlContentForMessage(message: IterableInAppMessage): Promise { + getHtmlContentForMessage( + message: IterableInAppMessage + ): Promise { Iterable?.logger?.log('InAppManager.getHtmlContentForMessage'); return RNIterableAPI.getHtmlInAppContentForMessage(message.messageId); diff --git a/src/inbox/classes/IterableInboxDataModel.ts b/src/inbox/classes/IterableInboxDataModel.ts index 0e5d494e0..fe5ce66fb 100644 --- a/src/inbox/classes/IterableInboxDataModel.ts +++ b/src/inbox/classes/IterableInboxDataModel.ts @@ -1,6 +1,6 @@ import { NativeModules } from 'react-native'; -import { Iterable } from '../../core'; +import { Iterable } from '../../core/classes/Iterable'; import { IterableHtmlInAppContent, IterableInAppDeleteSource, @@ -8,7 +8,10 @@ import { IterableInAppMessage, type IterableHtmlInAppContentRaw, } from '../../inApp'; -import type { IterableInboxImpressionRowInfo, IterableInboxRowViewModel } from '../types'; +import type { + IterableInboxImpressionRowInfo, + IterableInboxRowViewModel, +} from '../types'; const RNIterableAPI = NativeModules.RNIterableAPI; @@ -36,7 +39,10 @@ export class IterableInboxDataModel { * a positive number if `message1` should come after `message2`, * or 0 if they are considered equal. */ - comparatorFn?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number; + comparatorFn?: ( + message1: IterableInAppMessage, + message2: IterableInAppMessage + ) => number; /** * Optional function to map an IterableInAppMessage to a date string or undefined. * This function can be used to extract and format the date from a message. @@ -55,8 +61,11 @@ export class IterableInboxDataModel { */ set( filter?: (message: IterableInAppMessage) => boolean, - comparator?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number, - dateMapper?: (message: IterableInAppMessage) => string | undefined, + comparator?: ( + message1: IterableInAppMessage, + message2: IterableInAppMessage + ) => number, + dateMapper?: (message: IterableInAppMessage) => string | undefined ) { this.filterFn = filter; this.comparatorFn = comparator; @@ -88,12 +97,14 @@ export class IterableInboxDataModel { * @returns A promise that resolves to the HTML content of the specified message. */ getHtmlContentForMessageId(id: string): Promise { - Iterable?.logger?.log('IterableInboxDataModel.getHtmlContentForItem messageId: ' + id); + Iterable?.logger?.log( + 'IterableInboxDataModel.getHtmlContentForItem messageId: ' + id + ); return RNIterableAPI.getHtmlInAppContentForMessage(id).then( (content: IterableHtmlInAppContentRaw) => { return IterableHtmlInAppContent.fromDict(content); - }, + } ); } @@ -133,7 +144,7 @@ export class IterableInboxDataModel { }, () => { return []; - }, + } ); } @@ -185,7 +196,7 @@ export class IterableInboxDataModel { */ private static sortByMostRecent = ( message1: IterableInAppMessage, - message2: IterableInAppMessage, + message2: IterableInAppMessage ) => { const createdAt1 = message1.createdAt ?? new Date(0); const createdAt2 = message2.createdAt ?? new Date(0); @@ -228,8 +239,12 @@ export class IterableInboxDataModel { * @param messages - An array of `IterableInAppMessage` objects to be processed. * @returns An array of `IterableInboxRowViewModel` objects representing the processed messages. */ - private processMessages(messages: IterableInAppMessage[]): IterableInboxRowViewModel[] { - return this.sortAndFilter(messages).map(IterableInboxDataModel.getInboxRowViewModelForMessage); + private processMessages( + messages: IterableInAppMessage[] + ): IterableInboxRowViewModel[] { + return this.sortAndFilter(messages).map( + IterableInboxDataModel.getInboxRowViewModelForMessage + ); } /** @@ -238,7 +253,9 @@ export class IterableInboxDataModel { * @param messages - The array of messages to be sorted and filtered. * @returns The sorted and filtered array of messages. */ - private sortAndFilter(messages: IterableInAppMessage[]): IterableInAppMessage[] { + private sortAndFilter( + messages: IterableInAppMessage[] + ): IterableInAppMessage[] { let sortedFilteredMessages = messages.slice(); // MOB-10424: Figure out if this is purposeful @@ -265,7 +282,7 @@ export class IterableInboxDataModel { * @returns An object representing the inbox row view model. */ private static getInboxRowViewModelForMessage( - message: IterableInAppMessage, + message: IterableInAppMessage ): IterableInboxRowViewModel { return { title: message.inboxMetadata?.title ?? '', diff --git a/src/inbox/components/IterableInbox.tsx b/src/inbox/components/IterableInbox.tsx index 6b7bdf1cd..545403e03 100644 --- a/src/inbox/components/IterableInbox.tsx +++ b/src/inbox/components/IterableInbox.tsx @@ -11,7 +11,11 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { Iterable, useAppStateListener, useDeviceOrientation } from '../../core'; +import { useAppStateListener, useDeviceOrientation } from '../../core'; +// expo throws an error if this is not imported directly due to circular +// dependencies +// See: https://github.com/expo/expo/issues/35100 +import { Iterable } from '../../core/classes/Iterable'; import { IterableInAppDeleteSource, IterableInAppLocation } from '../../inApp'; import { IterableInboxDataModel } from '../classes'; @@ -40,7 +44,9 @@ const HEADLINE_PADDING_LEFT_LANDSCAPE = 70; * Props for the IterableInbox component. */ export interface IterableInboxProps - extends Partial> { + extends Partial< + Pick + > { /** * Flag which, when switched, returns a user to their inbox from _within_ the * inbox component (from the details of the particular message to the message @@ -196,8 +202,11 @@ export const IterableInbox = ({ const appState = useAppStateListener(); const isFocused = useIsFocused(); - const [selectedRowViewModelIdx, setSelectedRowViewModelIdx] = useState(0); - const [rowViewModels, setRowViewModels] = useState([]); + const [selectedRowViewModelIdx, setSelectedRowViewModelIdx] = + useState(0); + const [rowViewModels, setRowViewModels] = useState< + IterableInboxRowViewModel[] + >([]); const [loading, setLoading] = useState(true); const [animatedValue] = useState(new Animated.Value(0)); const [isMessageDisplay, setIsMessageDisplay] = useState(false); @@ -223,10 +232,15 @@ export const IterableInbox = ({ backgroundColor: ITERABLE_INBOX_COLORS.CONTAINER_BACKGROUND, fontSize: 40, fontWeight: 'bold', - height: Platform.OS === 'android' ? ANDROID_HEADLINE_HEIGHT : DEFAULT_HEADLINE_HEIGHT, + height: + Platform.OS === 'android' + ? ANDROID_HEADLINE_HEIGHT + : DEFAULT_HEADLINE_HEIGHT, marginTop: 0, paddingBottom: 10, - paddingLeft: isPortrait ? HEADLINE_PADDING_LEFT_PORTRAIT : HEADLINE_PADDING_LEFT_LANDSCAPE, + paddingLeft: isPortrait + ? HEADLINE_PADDING_LEFT_PORTRAIT + : HEADLINE_PADDING_LEFT_LANDSCAPE, paddingTop: 10, width: '100%', }, @@ -245,7 +259,9 @@ export const IterableInbox = ({ }); const navTitleHeight = - DEFAULT_HEADLINE_HEIGHT + styles.headline.paddingTop + styles.headline.paddingBottom; + DEFAULT_HEADLINE_HEIGHT + + styles.headline.paddingTop + + styles.headline.paddingBottom; //fetches inbox messages and adds listener for inbox changes on mount useEffect(() => { @@ -333,7 +349,11 @@ export const IterableInbox = ({ return inboxDataModel.getHtmlContentForMessageId(id); } - function handleMessageSelect(id: string, index: number, models: IterableInboxRowViewModel[]) { + function handleMessageSelect( + id: string, + index: number, + models: IterableInboxRowViewModel[] + ) { const newRowViewModels = models.map((rowViewModel) => { return rowViewModel.inAppMessage.messageId === id ? { ...rowViewModel, read: true } @@ -348,14 +368,17 @@ export const IterableInbox = ({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore models[index].inAppMessage, - IterableInAppLocation.inbox, + IterableInAppLocation.inbox ); slideLeft(); } function deleteRow(messageId: string) { - inboxDataModel.deleteItemById(messageId, IterableInAppDeleteSource.inboxSwipe); + inboxDataModel.deleteItemById( + messageId, + IterableInAppDeleteSource.inboxSwipe + ); fetchInboxMessages(); } @@ -368,17 +391,24 @@ export const IterableInbox = ({ setIsMessageDisplay(false); } - function updateVisibleMessageImpressions(messageImpressions: IterableInboxImpressionRowInfo[]) { + function updateVisibleMessageImpressions( + messageImpressions: IterableInboxImpressionRowInfo[] + ) { setVisibleMessageImpressions(messageImpressions); } - function showMessageDisplay(rowViewModelList: IterableInboxRowViewModel[], index: number) { + function showMessageDisplay( + rowViewModelList: IterableInboxRowViewModel[], + index: number + ) { const selectedRowViewModel = rowViewModelList[index]; return selectedRowViewModel ? ( deleteRow(messageId)} contentWidth={width} @@ -392,7 +422,9 @@ export const IterableInbox = ({ {showNavTitle ? ( - {customizations?.navTitle ? customizations?.navTitle : defaultInboxTitle} + {customizations?.navTitle + ? customizations?.navTitle + : defaultInboxTitle} ) : null} {rowViewModels.length ? ( @@ -406,7 +438,7 @@ export const IterableInbox = ({ handleMessageSelect(messageId, index, rowViewModels) } updateVisibleMessageImpressions={( - messageImpressions: IterableInboxImpressionRowInfo[], + messageImpressions: IterableInboxImpressionRowInfo[] ) => updateVisibleMessageImpressions(messageImpressions)} contentWidth={width} isPortrait={isPortrait} diff --git a/src/inbox/components/IterableInboxMessageDisplay.tsx b/src/inbox/components/IterableInboxMessageDisplay.tsx index 0c4e0005f..7e6798c73 100644 --- a/src/inbox/components/IterableInboxMessageDisplay.tsx +++ b/src/inbox/components/IterableInboxMessageDisplay.tsx @@ -10,7 +10,14 @@ import { import Icon from 'react-native-vector-icons/Ionicons'; import { WebView, type WebViewMessageEvent } from 'react-native-webview'; -import { Iterable, IterableAction, IterableActionContext, IterableActionSource } from '../../core'; +import { + IterableAction, + IterableActionContext, + IterableActionSource, +} from '../../core'; +// expo throws an error if this is not imported directly due to circular +// dependencies +import { Iterable } from '../../core/classes/Iterable'; import { IterableHtmlInAppContent, IterableInAppCloseSource, @@ -69,7 +76,8 @@ export const IterableInboxMessageDisplay = ({ isPortrait, }: IterableInboxMessageDisplayProps) => { const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title; - const [inAppContent, setInAppContent] = useState(null); + const [inAppContent, setInAppContent] = + useState(null); const styles = StyleSheet.create({ contentContainer: { @@ -171,12 +179,16 @@ export const IterableInboxMessageDisplay = ({ const source = IterableActionSource.inApp; const context = new IterableActionContext(action, source); - Iterable.trackInAppClick(rowViewModel.inAppMessage, IterableInAppLocation.inbox, URL); + Iterable.trackInAppClick( + rowViewModel.inAppMessage, + IterableInAppLocation.inbox, + URL + ); Iterable.trackInAppClose( rowViewModel.inAppMessage, IterableInAppLocation.inbox, IterableInAppCloseSource.link, - URL, + URL ); //handle delete action @@ -216,19 +228,26 @@ export const IterableInboxMessageDisplay = ({ Iterable.trackInAppClose( rowViewModel.inAppMessage, IterableInAppLocation.inbox, - IterableInAppCloseSource.back, + IterableInAppCloseSource.back ); }} > - + Inbox - + {messageTitle} From 449c68d0b832b5a0636cddd9f4aa4e63b4b5cc3d Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 9 Apr 2025 11:29:27 -0700 Subject: [PATCH 10/14] docs: fix linter issues --- src/core/hooks/useAppStateListener.ts | 2 +- src/inApp/classes/IterableHtmlInAppContent.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/hooks/useAppStateListener.ts b/src/core/hooks/useAppStateListener.ts index 3ae70b44d..cf10a5449 100644 --- a/src/core/hooks/useAppStateListener.ts +++ b/src/core/hooks/useAppStateListener.ts @@ -5,7 +5,7 @@ import { AppState } from 'react-native'; * A hook that listens to the app state changes and returns the current app * state. * - * @returns {string} The current app state. + * @returns The current app state. * * @example * ```typescript diff --git a/src/inApp/classes/IterableHtmlInAppContent.ts b/src/inApp/classes/IterableHtmlInAppContent.ts index 45843861d..c0082c454 100644 --- a/src/inApp/classes/IterableHtmlInAppContent.ts +++ b/src/inApp/classes/IterableHtmlInAppContent.ts @@ -20,8 +20,8 @@ export class IterableHtmlInAppContent implements IterableInAppContent { /** * Constructs an `IterableHtmlInAppContent` instance with the provided `edgeInsets` and `html`. * - * @param edgeInsets The space around the in-app content. - * @param html The raw HTML content of the in-app message. + * @param edgeInsets - The space around the in-app content. + * @param html - The raw HTML content of the in-app message. */ constructor(edgeInsets: IterableEdgeInsets, html: string) { this.edgeInsets = edgeInsets; @@ -31,7 +31,7 @@ export class IterableHtmlInAppContent implements IterableInAppContent { /** * Creates a new `IterableHtmlInAppContent` instance from a raw dictionary representation. * - * @param dict The raw dictionary representation of the HTML in-app content. + * @param dict - The raw dictionary representation of the HTML in-app content. * @returns A new `IterableHtmlInAppContent` instance with the values from the provided dictionary. */ static fromDict(dict: IterableHtmlInAppContentRaw): IterableHtmlInAppContent { From 392b51a651e3199f0c5b424dc45eb749d3d38e48 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 9 Apr 2025 12:01:16 -0700 Subject: [PATCH 11/14] refactor: resolve circular dependencies --- src/constants/index.ts | 0 src/core/classes/Iterable.ts | 12 ++++++------ src/core/classes/IterableConfig.ts | 3 ++- src/inApp/classes/IterableInAppMessage.ts | 5 ++++- src/inApp/types/IterableHtmlInAppContentRaw.ts | 5 ++++- src/inApp/types/IterableInAppContent.ts | 5 ++++- 6 files changed, 20 insertions(+), 10 deletions(-) delete mode 100644 src/constants/index.ts diff --git a/src/constants/index.ts b/src/constants/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index a7d68d156..6a30828aa 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -7,12 +7,12 @@ import { import { buildInfo } from '../../itblBuildInfo'; -import { - IterableInAppCloseSource, - IterableInAppDeleteSource, - IterableInAppLocation, - IterableInAppMessage, -} from '../../inApp'; +// TODO: Organize these so that there are no circular dependencies +// See https://github.com/expo/expo/issues/35100 +import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage'; +import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSource'; +import { IterableInAppDeleteSource } from '../../inApp/enums/IterableInAppDeleteSource'; +import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation'; import { IterableAuthResponseResult, IterableEventName } from '../enums'; import { IterableAction } from './IterableAction'; diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 079a7cff8..1c0550b0c 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -1,4 +1,5 @@ -import { IterableInAppMessage, IterableInAppShowResponse } from '../../inApp'; +import { type IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage'; +import { IterableInAppShowResponse } from '../../inApp/enums'; import { IterableDataRegion, IterableLogLevel, diff --git a/src/inApp/classes/IterableInAppMessage.ts b/src/inApp/classes/IterableInAppMessage.ts index 135039645..8a5b816bf 100644 --- a/src/inApp/classes/IterableInAppMessage.ts +++ b/src/inApp/classes/IterableInAppMessage.ts @@ -1,6 +1,9 @@ import { type ViewToken } from 'react-native'; -import { IterableUtil } from '../../core'; +// expo throws an error if this is not imported directly due to circular +// dependencies +// See https://github.com/expo/expo/issues/35100 +import { IterableUtil } from '../../core/classes/IterableUtil'; import { IterableInAppTriggerType } from '../enums'; import type { IterableInAppMessageRaw } from '../types'; import { IterableInAppTrigger } from './IterableInAppTrigger'; diff --git a/src/inApp/types/IterableHtmlInAppContentRaw.ts b/src/inApp/types/IterableHtmlInAppContentRaw.ts index 1248d2a5a..81d6e0218 100644 --- a/src/inApp/types/IterableHtmlInAppContentRaw.ts +++ b/src/inApp/types/IterableHtmlInAppContentRaw.ts @@ -1,4 +1,7 @@ -import type { IterableEdgeInsetDetails } from '../../core'; +// expo throws an error if this is not imported directly due to circular +// dependencies +// See https://github.com/expo/expo/issues/35100 +import type { IterableEdgeInsetDetails } from '../../core/types/IterableEdgeInsetDetails'; /** * The raw in-App content details returned from the server. diff --git a/src/inApp/types/IterableInAppContent.ts b/src/inApp/types/IterableInAppContent.ts index 58c71fd48..3cfb88557 100644 --- a/src/inApp/types/IterableInAppContent.ts +++ b/src/inApp/types/IterableInAppContent.ts @@ -1,4 +1,7 @@ -import type { IterableInAppContentType } from '../enums'; +// expo throws an error if this is not imported directly due to circular +// dependencies +// See https://github.com/expo/expo/issues/35100 +import type { IterableInAppContentType } from '../enums/IterableInAppContentType'; /** * Information about the content of an in-app message in the Iterable SDK. From 2ed58ad3fbe1e9225d5fda8c08d0282c9256d193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAkshay?= <“ayyanchira.akshay@gmail.com”> Date: Wed, 9 Apr 2025 17:10:33 -0700 Subject: [PATCH 12/14] Concurrent Ruby version --- example/Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/example/Gemfile b/example/Gemfile index 2a7ce357c..da2fdf0fe 100644 --- a/example/Gemfile +++ b/example/Gemfile @@ -6,3 +6,4 @@ ruby ">= 2.6.10" # Exclude problematic versions of cocoapods and activesupport that causes build failures. gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' +gem 'concurrent-ruby', '< 1.3.4' \ No newline at end of file From f935077e647f53a6f9a84cfcce385faf13150b01 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 9 Apr 2025 17:40:30 -0700 Subject: [PATCH 13/14] chore: update Gemfile and README for Ruby 3.4.0 compatibility and adjust package version --- example/Gemfile | 8 ++++++++ example/README.md | 17 +++++++++++++++++ src/itblBuildInfo.ts | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/example/Gemfile b/example/Gemfile index 2a7ce357c..6a4c5f171 100644 --- a/example/Gemfile +++ b/example/Gemfile @@ -6,3 +6,11 @@ ruby ">= 2.6.10" # Exclude problematic versions of cocoapods and activesupport that causes build failures. gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' +gem 'xcodeproj', '< 1.26.0' +gem 'concurrent-ruby', '< 1.3.4' + +# Ruby 3.4.0 has removed some libraries from the standard library. +gem 'bigdecimal' +gem 'logger' +gem 'benchmark' +gem 'mutex_m' diff --git a/example/README.md b/example/README.md index 36ca8d37c..3b19f26e9 100644 --- a/example/README.md +++ b/example/README.md @@ -203,6 +203,23 @@ There are two ways to fix this: Run `bundle install` in the _example app directory_. You can also try running it in _ios_ in the _example app directory_. +## Error: `uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger` + +This is a known issue with Ruby 3.4.0. You can fix it by running the following: +```bash +gem install xcodeproj -v '< 1.26.0' +gem install concurrent-ruby -v '< 1.3.4' +``` + +## Unable to build on Xcode 16.3 + +There is a [known issue](https://github.com/facebook/react-native/issues/50411) +with Xcode 16.3 and react-native@0.75.3. + +Until New Architecture is supporter by Iterable, we cannot upgrade to 0.76. +Therefore, to fix it we need to downgrade Xcode to 16.2. +- [Download Xcode 16.2](https://download.developer.apple.com/Developer_Tools/Xcode_16.2/Xcode_16.2.xip) + ## Other If things are not working and you are stumped as to why, try running the following in the _example app directory_: diff --git a/src/itblBuildInfo.ts b/src/itblBuildInfo.ts index a363c8bc4..e3e489cda 100644 --- a/src/itblBuildInfo.ts +++ b/src/itblBuildInfo.ts @@ -3,5 +3,5 @@ * It contains the version of the package */ export const buildInfo = { - version: '2.0.0-beta.expo.53', + version: '2.0.0-beta', }; From bf51fd59764f756ffed4aab1038ab0a343199e29 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 9 Apr 2025 17:44:00 -0700 Subject: [PATCH 14/14] chore: enable pre-commit hooks for linting and type checking with ESLint and TypeScript --- lefthook.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 12e18d7ab..8b4be29b7 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,14 +1,14 @@ -# pre-commit: -# parallel: true -# commands: -# lint: -# glob: "*.{js,ts,jsx,tsx}" -# run: npx eslint {staged_files} -# types: -# glob: "*.{js,ts,jsx,tsx}" -# run: npx tsc -# commit-msg: -# parallel: true -# commands: -# commitlint: -# run: npx commitlint --edit +pre-commit: + parallel: true + commands: + lint: + glob: "*.{js,ts,jsx,tsx}" + run: npx eslint {staged_files} + types: + glob: "*.{js,ts,jsx,tsx}" + run: npx tsc +commit-msg: + parallel: true + commands: + commitlint: + run: npx commitlint --edit