From 6514543fa315ecd973d7a4c4983ce6f84b6c45d4 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 14:48:34 -0700 Subject: [PATCH 01/30] Renamed index and removed 'use strict' --- src/InboxImpressionRowInfo.ts | 13 +- src/InboxRowViewModel.ts | 23 +- src/Iterable.ts | 682 +++++++++++++++------------- src/IterableConfig.ts | 134 +++--- src/IterableInAppClasses.ts | 68 +-- src/IterableInAppManager.ts | 87 ++-- src/IterableInAppMessage.ts | 102 ++--- src/IterableInbox.tsx | 611 ++++++++++++------------- src/IterableInboxDataModel.ts | 235 +++++----- src/IterableInboxEmptyState.tsx | 119 +++-- src/IterableInboxMessageCell.tsx | 560 ++++++++++++----------- src/IterableInboxMessageDisplay.tsx | 425 ++++++++--------- src/IterableInboxMessageList.tsx | 181 ++++---- src/IterableUtil.ts | 8 +- src/index.ts | 75 --- src/index.tsx | 91 +++- src/index_OLD.tsx | 22 + 17 files changed, 1729 insertions(+), 1707 deletions(-) delete mode 100644 src/index.ts create mode 100644 src/index_OLD.tsx diff --git a/src/InboxImpressionRowInfo.ts b/src/InboxImpressionRowInfo.ts index f6987e545..5fa74fec6 100644 --- a/src/InboxImpressionRowInfo.ts +++ b/src/InboxImpressionRowInfo.ts @@ -1,8 +1,5 @@ -'use strict' - -type InboxImpressionRowInfo = { - messageId: string - silentInbox: boolean -} - -export default InboxImpressionRowInfo \ No newline at end of file +// TODO: Add description +export type InboxImpressionRowInfo = { + messageId: string; + silentInbox: boolean; +}; diff --git a/src/InboxRowViewModel.ts b/src/InboxRowViewModel.ts index ada9dbf4f..966d8f95e 100644 --- a/src/InboxRowViewModel.ts +++ b/src/InboxRowViewModel.ts @@ -1,14 +1,11 @@ -'use strict' +import { IterableInAppMessage } from '.'; -import { IterableInAppMessage } from '.' - -type InboxRowViewModel = { - title: string - subtitle?: string - imageUrl?: string - createdAt?: Date - read: boolean - inAppMessage: IterableInAppMessage -} - -export default InboxRowViewModel \ No newline at end of file +// TODO: Add description +export type InboxRowViewModel = { + title: string; + subtitle?: string; + imageUrl?: string; + createdAt?: Date; + read: boolean; + inAppMessage: IterableInAppMessage; +}; diff --git a/src/Iterable.ts b/src/Iterable.ts index ff90040c4..af03bdba3 100644 --- a/src/Iterable.ts +++ b/src/Iterable.ts @@ -1,33 +1,26 @@ -'use strict' - -import { - NativeModules, - NativeEventEmitter, - Linking, - Platform -} from 'react-native' +import { NativeModules, NativeEventEmitter, Linking, Platform } from 'react-native'; import { IterableInAppLocation, IterableInAppCloseSource, - IterableInAppDeleteSource -} from './IterableInAppClasses' + IterableInAppDeleteSource, +} from './IterableInAppClasses'; -import IterableInAppManager from './IterableInAppManager' -import IterableInAppMessage from './IterableInAppMessage' -import IterableConfig, { AuthResponse } from './IterableConfig' -import { IterableLogger } from './IterableLogger' +import IterableInAppManager from './IterableInAppManager'; +import IterableInAppMessage from './IterableInAppMessage'; +import IterableConfig, { AuthResponse } from './IterableConfig'; +import { IterableLogger } from './IterableLogger'; -const RNIterableAPI = NativeModules.RNIterableAPI -const RNEventEmitter = new NativeEventEmitter(RNIterableAPI) +const RNIterableAPI = NativeModules.RNIterableAPI; +const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); /** -* Enum representing the source of IterableAction. -*/ + * Enum representing the source of IterableAction. + */ enum IterableActionSource { push = 0, appLink = 1, - inApp = 2 + inApp = 2, } enum AuthResponseCallback { @@ -36,280 +29,298 @@ enum AuthResponseCallback { } /** -* Enum representing what level of logs will Android and iOS project be printing on their consoles respectively. -*/ + * Enum representing what level of logs will Android and iOS project be printing on their consoles respectively. + */ enum IterableLogLevel { debug = 1, info = 2, - error = 3 + error = 3, } /** -* IterableAction represents an action defined as a response to user events. -* It is currently used in push notification actions (open push & action buttons). -*/ + * IterableAction represents an action defined as a response to user events. + * It is currently used in push notification actions (open push & action buttons). + */ class IterableAction { - type: string - data?: string - userInput?: string + type: string; + data?: string; + userInput?: string; constructor(type: string, data?: string, userInput?: string) { - this.type = type - this.data = data - this.userInput = userInput + this.type = type; + this.data = data; + this.userInput = userInput; } static fromDict(dict: any): IterableAction { - return new IterableAction(dict["type"], dict["data"], dict["userInput"]) + return new IterableAction(dict.type, dict.data, dict.userInput); } } class IterableActionContext { - action: IterableAction - source: IterableActionSource + action: IterableAction; + source: IterableActionSource; constructor(action: IterableAction, source: IterableActionSource) { - this.action = action - this.source = source + this.action = action; + this.source = source; } static fromDict(dict: any): IterableActionContext { - const action = IterableAction.fromDict(dict["action"]) - const source = dict["source"] as IterableActionSource - return new IterableActionContext(action, source) + const action = IterableAction.fromDict(dict.action); + const source = dict.source as IterableActionSource; + return new IterableActionContext(action, source); } } class IterableAttributionInfo { - campaignId: number - templateId: number - messageId: string + campaignId: number; + templateId: number; + messageId: string; constructor(campaignId: number, templateId: number, messageId: string) { - this.campaignId = campaignId - this.templateId = templateId - this.messageId = messageId + this.campaignId = campaignId; + this.templateId = templateId; + this.messageId = messageId; } } class IterableCommerceItem { - id: string - name: string - price: number - quantity: number - sku?: string - description?: string - url?: string - imageUrl?: string - categories?: Array - dataFields?: any - - constructor(id: string, name: string, price: number, quantity: number, sku?: string, description?: string, url?: string, imageUrl?: string, categories?: Array, dataFields?: any | undefined) { - this.id = id - this.name = name - this.price = price - this.quantity = quantity - this.sku = sku - this.description = description - this.url = url - this.imageUrl = imageUrl - this.categories = categories - this.dataFields = dataFields + id: string; + name: string; + price: number; + quantity: number; + sku?: string; + description?: string; + url?: string; + imageUrl?: string; + categories?: Array; + dataFields?: any; + + constructor( + id: string, + name: string, + price: number, + quantity: number, + sku?: string, + description?: string, + url?: string, + imageUrl?: string, + categories?: Array, + dataFields?: any | undefined, + ) { + this.id = id; + this.name = name; + this.price = price; + this.quantity = quantity; + this.sku = sku; + this.description = description; + this.url = url; + this.imageUrl = imageUrl; + this.categories = categories; + this.dataFields = dataFields; } } enum EventName { - handleUrlCalled = "handleUrlCalled", - handleCustomActionCalled = "handleCustomActionCalled", - handleInAppCalled = "handleInAppCalled", - handleAuthCalled = "handleAuthCalled", - receivedIterableInboxChanged = "receivedIterableInboxChanged", - handleAuthSuccessCalled = "handleAuthSuccessCalled", - handleAuthFailureCalled = "handleAuthFailureCalled" + handleUrlCalled = 'handleUrlCalled', + handleCustomActionCalled = 'handleCustomActionCalled', + handleInAppCalled = 'handleInAppCalled', + handleAuthCalled = 'handleAuthCalled', + receivedIterableInboxChanged = 'receivedIterableInboxChanged', + handleAuthSuccessCalled = 'handleAuthSuccessCalled', + handleAuthFailureCalled = 'handleAuthFailureCalled', } class Iterable { - static inAppManager = new IterableInAppManager() + static inAppManager = new IterableInAppManager(); + + static logger: IterableLogger; - static logger: IterableLogger - - static savedConfig: IterableConfig + static savedConfig: IterableConfig; /** * This static method is used to initialize the React Native SDK in your app's Javascript or Typescript code. - * + * * Pass in a mobile API key distributed with the mobile app. * Warning: never user server-side API keys with the React Native SDK, mobile API keys have minimal access for security purposes. - * + * * Pass in an IterableConfig object with the various customization properties setup. - * + * * Note: Use Iterable.initialize and NOT Iterable.initialize2, as Iterable.initialize2 is only used internally. - * + * * @param {string} apiKey mobile API key provided with the application * @param {IterableConfig} config config object with various properties */ - static initialize(apiKey: string, config: IterableConfig = new IterableConfig()): Promise { - Iterable.savedConfig = config + static initialize( + apiKey: string, + config: IterableConfig = new IterableConfig(), + ): Promise { + Iterable.savedConfig = config; - Iterable.logger = new IterableLogger(Iterable.savedConfig) + Iterable.logger = new IterableLogger(Iterable.savedConfig); - Iterable.logger.log("initialize: " + apiKey) + Iterable.logger.log('initialize: ' + apiKey); - this.setupEventHandlers() - const version = this.getVersionFromPackageJson() + this.setupEventHandlers(); + const version = this.getVersionFromPackageJson(); - return RNIterableAPI.initializeWithApiKey(apiKey, config.toDict(), version) + return RNIterableAPI.initializeWithApiKey(apiKey, config.toDict(), version); } /** - * DO NOT CALL THIS METHOD. - * This method is used internally to connect to staging environment. - */ - static initialize2(apiKey: string, config: IterableConfig = new IterableConfig(), apiEndPoint: string): Promise { - Iterable.savedConfig = config + * DO NOT CALL THIS METHOD. + * This method is used internally to connect to staging environment. + */ + static initialize2( + apiKey: string, + config: IterableConfig = new IterableConfig(), + apiEndPoint: string, + ): Promise { + Iterable.savedConfig = config; - Iterable.logger = new IterableLogger(Iterable.savedConfig) + Iterable.logger = new IterableLogger(Iterable.savedConfig); - Iterable.logger.log("initialize2: " + apiKey); - - this.setupEventHandlers() - const version = this.getVersionFromPackageJson() + Iterable.logger.log('initialize2: ' + apiKey); - return RNIterableAPI.initialize2WithApiKey(apiKey, config.toDict(), version, apiEndPoint) + this.setupEventHandlers(); + const version = this.getVersionFromPackageJson(); + + return RNIterableAPI.initialize2WithApiKey(apiKey, config.toDict(), version, apiEndPoint); } /** * This static method associates the current user with the passed in email parameter. - * - * Iterable's React Native SDK persists the user across app sessions and restarts, until you manually change the user using + * + * Iterable's React Native SDK persists the user across app sessions and restarts, until you manually change the user using * Iterable.setEmail or Iterable.setUserId. * * User profile creation: - * + * * If your Iterable project does not have a user with the passed in email, setEmail creates one and adds the email address - * to the user's Iterable profile. - * + * to the user's Iterable profile. + * * Registering device token: - * - * If IterableConfig.autoPushRegisteration is set to true, calling setEmail automatically registers the device for push + * + * If IterableConfig.autoPushRegisteration is set to true, calling setEmail automatically registers the device for push * notifications and sends the deviceId and token to Iterable. - * + * * Optional JWT token parameter: - * + * * An optional valid, pre-fetched JWT can be passed in to avoid race conditions. * The SDK uses this JWT to authenticate API requests for this user. - * + * * Signing out a user from the SDK: - * + * * To tell the SDK to sign out the current user, pass null into Iterable.setEmail. * If IterableConfig.autoPushRegisteration is set to true, calling Iterable.setEmail(null) prevents Iterable from sending further * push notifications to that user, for that app, on that device. * On the user's Iterable profile, endpointEnabled is set to false for the device. - * + * * Note: specify a user by calling Iterable.setEmail or Iterable.setUserId, but NOT both. - * + * * @param {string | nullĀ | undefined} email email address to associate with the current user * @param {string | null | undefined} authToken valid, pre-fetched JWT the SDK can use to authenticate API requests, optional - if null/undefined, no JWT related action will be taken - */ + */ static setEmail(email?: string | null, authToken?: string | null) { - Iterable.logger.log("setEmail: " + email) + Iterable.logger.log('setEmail: ' + email); - RNIterableAPI.setEmail(email, authToken) + RNIterableAPI.setEmail(email, authToken); } /** * This static method returns the email associated with the current user. * Iterable.getEmail returns a promise. Use the keyword `then` to get the result of the promise. - * + * * parameters: none */ static getEmail(): Promise { - Iterable.logger.log("getEmail") + Iterable.logger.log('getEmail'); - return RNIterableAPI.getEmail() + return RNIterableAPI.getEmail(); } /** * This static method associates the current user with the passed in userId parameter. - * - * Iterable's React Native SDK persists the user across app sessions and restarts, until you manually change the user using + * + * Iterable's React Native SDK persists the user across app sessions and restarts, until you manually change the user using * Iterable.setEmail or Iterable.setUserId. - * + * * User profile creation: * - * If your Iterable project does not have a user with the passed in UserId, setUserId creates one and adds a placeholder email - * address to the user's Iterable profile. - * + * If your Iterable project does not have a user with the passed in UserId, setUserId creates one and adds a placeholder email + * address to the user's Iterable profile. + * * Registering device token: - * - * If IterableConfig.autoPushRegisteration is set to true, calling setUserId automatically registers the device for push + * + * If IterableConfig.autoPushRegisteration is set to true, calling setUserId automatically registers the device for push * notifications and sends the deviceId and token to Iterable. - * + * * Optional JWT token parameter: - * + * * An optional valid, pre-fetched JWT can be passed in to avoid race conditions. * The SDK uses this JWT to authenticate API requests for this user. - * + * * Signing out a user from the SDK: - * + * * To tell the SDK to sign out the current user, pass null into Iterable.setUserId. * If IterableConfig.autoPushRegisteration is set to true, calling Iterable.setUserId(null) prevents Iterable from sending further * push notifications to that user, for that app, on that device. * On the user's Iterable profile, endpointEnabled is set to false for the device. - * + * * Note: specify a user by calling Iterable.setEmail or Iterable.setUserId, but NOT both. - * - * parameters: @param {string | null | undefined} userId user ID to associate with the current user + * + * parameters: @param {string | null | undefined} userId user ID to associate with the current user * optional parameter: @param {string | null | undefined} authToken valid, pre-fetched JWT the SDK can use to authenticate API requests, optional - if null/undefined, no JWT related action will be taken - */ - + */ + static setUserId(userId?: string | null, authToken?: string | null) { - Iterable.logger.log("setUserId: " + userId) + Iterable.logger.log('setUserId: ' + userId); - RNIterableAPI.setUserId(userId, authToken) + RNIterableAPI.setUserId(userId, authToken); } /** * This static method returns the userId associated with the current user. * Iterable.getUserId returns a promise. Use the keyword `then` to get the result of the promise. - * + * * parameters: none */ static getUserId(): Promise { - Iterable.logger.log("getUserId") + Iterable.logger.log('getUserId'); - return RNIterableAPI.getUserId() + return RNIterableAPI.getUserId(); } /** * This static method disables the device's token for the current user. - * + * * parameters: none */ static disableDeviceForCurrentUser() { - Iterable.logger.log("disableDeviceForCurrentUser") + Iterable.logger.log('disableDeviceForCurrentUser'); - RNIterableAPI.disableDeviceForCurrentUser() + RNIterableAPI.disableDeviceForCurrentUser(); } /** * This static method returns the payload of the last push notification with which the user * opened the application (by clicking an action button, etc.). - * + * * Iterable.getLastPushPayload returns a promise. Use the keyword `then` to get the result of the promise. - * + * * Parameters: none */ static getLastPushPayload(): Promise { - Iterable.logger.log("getLastPushPayload") + Iterable.logger.log('getLastPushPayload'); - return RNIterableAPI.getLastPushPayload() + return RNIterableAPI.getLastPushPayload(); } /** @@ -317,23 +328,27 @@ class Iterable { * The attribution information contains the campaign ID, template ID, and message ID of the message * that prompted the user to recently click a link. * See IterableAttributionInfo class defined above. - * - * Iterable.getAttributionInfo returns a promise that resolves to an IterableAttributionInfo object. + * + * Iterable.getAttributionInfo returns a promise that resolves to an IterableAttributionInfo object. * Use the keyword `then` to get the result of the promise. - * + * * parameters: none */ static getAttributionInfo(): Promise { - Iterable.logger.log("getAttributionInfo") + Iterable.logger.log('getAttributionInfo'); return RNIterableAPI.getAttributionInfo().then((dict: any | undefined) => { if (dict) { - return new IterableAttributionInfo(dict["campaignId"] as number, dict["templateId"] as number, dict["messageId"] as string) + return new IterableAttributionInfo( + dict.campaignId as number, + dict.templateId as number, + dict.messageId as string, + ); } else { - return undefined + return undefined; } - }) + }); } /** @@ -341,7 +356,7 @@ class Iterable { * The attribution information contains the campaign ID, template ID, and message ID of the message * that prompted the user to recently click a link. * See IterableAttributionInfo class defined above. - * + * * For deep link clicks, Iterable sets attribution information automatically. * However, use this method to set it manually if ever necessary. * @@ -349,15 +364,15 @@ class Iterable { */ static setAttributionInfo(attributionInfo?: IterableAttributionInfo) { - Iterable.logger.log("setAttributionInfo") + Iterable.logger.log('setAttributionInfo'); - RNIterableAPI.setAttributionInfo(attributionInfo) + RNIterableAPI.setAttributionInfo(attributionInfo); } /** * This static method creates a pushOpen event on the current user's Iterable profile, * populating it with data provided to the method call. - * + * * @param {number} campaignId the ID of the campaign to associate with the push open * @param {number} templateId the ID of the template to associate with the push open * @param {string} messageId the ID of the message to associate with the push open @@ -366,40 +381,46 @@ class Iterable { */ static trackPushOpenWithCampaignId( - campaignId: number, - templateId: number, - messageId: string | undefined, - appAlreadyRunning: boolean, - dataFields: any | undefined + campaignId: number, + templateId: number, + messageId: string | undefined, + appAlreadyRunning: boolean, + dataFields: any | undefined, ) { - Iterable.logger.log("trackPushOpenWithCampaignId") - - RNIterableAPI.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields) + Iterable.logger.log('trackPushOpenWithCampaignId'); + + RNIterableAPI.trackPushOpenWithCampaignId( + campaignId, + templateId, + messageId, + appAlreadyRunning, + dataFields, + ); } /** * This static method updates the items saved in the shopping cart (or equivalent). * Represent each item in the updateCart event with an IterableCommerceItem object. * See IterableCommerceItem class defined above. - * + * * @param {Array} items the items added to the shopping cart */ static updateCart(items: Array) { - Iterable.logger.log("updateCart") + Iterable.logger.log('updateCart'); - RNIterableAPI.updateCart(items) + RNIterableAPI.updateCart(items); } /** * This static method launches the application from the background for Android devices. - * - * parameters: none + * + * parameters: none */ static wakeApp() { - if (Platform.OS === "android") { - Iterable.logger.log("Attempting to wake the app") + if (Platform.OS === 'android') { + Iterable.logger.log('Attempting to wake the app'); RNIterableAPI.wakeApp(); } @@ -409,22 +430,26 @@ class Iterable { * This static method creates a purchase event on the current user's Iterable profile. * Represent each item in the purchase event with an IterableCommerceItem object. * See IterableCommerceItem class defined above. - * + * * Note: total is a parameter that is passed in. Iterable does not sum the price fields of the various items in the purchase event. - * + * * @param {number} total the total cost of the purchase * @param {Array} items the items included in the purchase * @param {any | undefined} dataFields descriptive data to store on the purchase event */ - static trackPurchase(total: number, items: Array, dataFields: any | undefined) { - Iterable.logger.log("trackPurchase") + static trackPurchase( + total: number, + items: Array, + dataFields: any | undefined, + ) { + Iterable.logger.log('trackPurchase'); - RNIterableAPI.trackPurchase(total, items, dataFields) + RNIterableAPI.trackPurchase(total, items, dataFields); } /** - * This static method creates an inAppOpen event for the specified message on the current user's profile + * This static method creates an inAppOpen event for the specified message on the current user's profile * for manual tracking purposes. Iterable's SDK automatically tracks in-app message opens when you use the * SDK's default rendering. * @@ -433,43 +458,52 @@ class Iterable { */ static trackInAppOpen(message: IterableInAppMessage, location: IterableInAppLocation) { - Iterable.logger.log("trackInAppOpen") + Iterable.logger.log('trackInAppOpen'); - RNIterableAPI.trackInAppOpen(message.messageId, location) + RNIterableAPI.trackInAppOpen(message.messageId, location); } /** * This static method creates an inAppClick event for the specified message on the current user's profile * for manual tracking purposes. Iterable's SDK automatically tracks in-app message clicks when you use the - * SDK's default rendering. Click events refer to click events within the in-app message to distinguish + * SDK's default rendering. Click events refer to click events within the in-app message to distinguish * from inAppOpen events. - * + * * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) * @param {IterableInAppLocation} location the location of the in-app message (an IterableInAppLocation enum) * @param {string} clickedUrl the URL clicked by the user */ - static trackInAppClick(message: IterableInAppMessage, location: IterableInAppLocation, clickedUrl: string) { - Iterable.logger.log("trackInAppClick") + static trackInAppClick( + message: IterableInAppMessage, + location: IterableInAppLocation, + clickedUrl: string, + ) { + Iterable.logger.log('trackInAppClick'); - RNIterableAPI.trackInAppClick(message.messageId, location, clickedUrl) + RNIterableAPI.trackInAppClick(message.messageId, location, clickedUrl); } /** * This static method creates an inAppClose event for the specified message on the current user's profile * for manual tracking purposes. Iterable's SDK automatically tracks in-app message close events when you use the * SDK's default rendering. - * + * * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) * @param {IterableInAppLocation} location the location of the in-app message (an IterableInAppLocation enum) * @param {IterableInAppCloseSource} source the way the in-app was closed (an IterableInAppCloseSource enum) * @param {string} clickedUrl the URL clicked by the user */ - static trackInAppClose(message: IterableInAppMessage, location: IterableInAppLocation, source: IterableInAppCloseSource, clickedUrl?: string | undefined) { - Iterable.logger.log("trackInAppClose") + static trackInAppClose( + message: IterableInAppMessage, + location: IterableInAppLocation, + source: IterableInAppCloseSource, + clickedUrl?: string | undefined, + ) { + Iterable.logger.log('trackInAppClose'); - RNIterableAPI.trackInAppClose(message.messageId, location, source, clickedUrl) + RNIterableAPI.trackInAppClose(message.messageId, location, source, clickedUrl); } /** @@ -477,93 +511,97 @@ class Iterable { * Also, creates an in-app delete event for the specified message on the current user's profile * unless otherwise specifed (specifying a source of IterableInAppDeleteSource.unknown prevents * an inAppDelete event from being created). - * + * * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) * @param {IterableInAppLocation} location the location of the in-app message (an IterableInAppLocation enum) * @param {IterableInAppDeleteSource} source how the in-app message was deleted (an IterableInAppDeleteSource enum) */ - static inAppConsume(message: IterableInAppMessage, location: IterableInAppLocation, source: IterableInAppDeleteSource) { - Iterable.logger.log("inAppConsume") + static inAppConsume( + message: IterableInAppMessage, + location: IterableInAppLocation, + source: IterableInAppDeleteSource, + ) { + Iterable.logger.log('inAppConsume'); - RNIterableAPI.inAppConsume(message.messageId, location, source) + RNIterableAPI.inAppConsume(message.messageId, location, source); } /** * This static method creates a custom event to the current user's Iterable profile. * Pass in the name of the event stored in eventName key and the data associated with the event. * The eventType is set to "customEvent". - * + * * @param {string} name the eventName of the custom event * @param {any | undefined} dataFields descriptive data to store on the custom event */ static trackEvent(name: string, dataFields: any | undefined) { - Iterable.logger.log("trackEvent") + Iterable.logger.log('trackEvent'); - RNIterableAPI.trackEvent(name, dataFields) + RNIterableAPI.trackEvent(name, dataFields); } /** - * This static method saves data to the current user's Iterable profile. - * - * If mergeNestedObjects is set to true, top-level objects in the passed in dataFields parameter - * are merged with their counterparts that already exist on the user's profile. - * Otherwise, they are added. - * - * If mergeNestedObjects is set to false, the top-level objects in the passed in dataFields parameter - * overwrite their counterparts that already exist on the user's profile. - * Otherwise, they are added. - * - * @param {any} dataFields data fields to store in user profile - * @param {boolean} mergeNestedObjects flag indicating whether to merge top-level objects - */ + * This static method saves data to the current user's Iterable profile. + * + * If mergeNestedObjects is set to true, top-level objects in the passed in dataFields parameter + * are merged with their counterparts that already exist on the user's profile. + * Otherwise, they are added. + * + * If mergeNestedObjects is set to false, the top-level objects in the passed in dataFields parameter + * overwrite their counterparts that already exist on the user's profile. + * Otherwise, they are added. + * + * @param {any} dataFields data fields to store in user profile + * @param {boolean} mergeNestedObjects flag indicating whether to merge top-level objects + */ static updateUser(dataFields: any, mergeNestedObjects: boolean) { - Iterable.logger.log("updateUser") + Iterable.logger.log('updateUser'); - RNIterableAPI.updateUser(dataFields, mergeNestedObjects) + RNIterableAPI.updateUser(dataFields, mergeNestedObjects); } /** * This static method changes the value of the email field on the current user's Iterable profile. - * + * * If Iterable.setUserId was used to identify the current user, Iterable.updateEmail can be called to - * give the current user a real (non-placeholder) email address. - * + * give the current user a real (non-placeholder) email address. + * * An optional valid, pre-fetched JWT can be passed in to avoid race conditions. * The SDK uses this JWT to authenticate API requests for this user. - * + * * @param email the new email to set * @param authToken the new auth token (JWT) to set with the new email, optional - if null/undefined, no JWT-related action will be taken */ static updateEmail(email: string, authToken?: string | undefined) { - Iterable.logger.log("updateEmail") + Iterable.logger.log('updateEmail'); - RNIterableAPI.updateEmail(email, authToken) + RNIterableAPI.updateEmail(email, authToken); } /** * This static method handles a universal link whether it is internal to the application or an external link. - * HandleAppLink will hand the passed in URL to IterableConfig.urlHandler, where it is determined whether or not + * HandleAppLink will hand the passed in URL to IterableConfig.urlHandler, where it is determined whether or not * the app can handle the clicked URL. - * + * * @param {string} link URL link to be handled */ static handleAppLink(link: string): Promise { - Iterable.logger.log("handleAppLink") + Iterable.logger.log('handleAppLink'); - return RNIterableAPI.handleAppLink(link) + return RNIterableAPI.handleAppLink(link); } /** - * This static method updates the current user's subscribed email lists, unsubscribed channel IDs, + * This static method updates the current user's subscribed email lists, unsubscribed channel IDs, * unsubscribed message type IDs (for opt-out message types), and subscribed message type IDs (for opt-in message types) * on the current user's profile. - * + * * pass in null for any of emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, or subscribedMessageTypeIds * to indicate that Iterable should not change the current value on the current user's profile. - * + * * @param {Array | undefined} emailListIds the list of email lists (by ID) to which the user should be subscribed * @param {Array | undefined} unsubscribedChannelIds the list of message channels (by ID) to which the user should be unsubscribed * @param {Array | undefined} unsubscribedMessageTypeIds the list of message types (by ID) to which the user should be unsubscribed (for opt-out message types) @@ -572,140 +610,130 @@ class Iterable { * @param {number} templateId the template ID to associate with events generated by this request, use -1 if unknown or not applicable */ - static updateSubscriptions(emailListIds: Array | undefined, + static updateSubscriptions( + emailListIds: Array | undefined, unsubscribedChannelIds: Array | undefined, unsubscribedMessageTypeIds: Array | undefined, subscribedMessageTypeIds: Array | undefined, campaignId: number, - templateId: number) { - Iterable.logger.log("updateSubscriptions") - + templateId: number, + ) { + Iterable.logger.log('updateSubscriptions'); + RNIterableAPI.updateSubscriptions( - emailListIds, - unsubscribedChannelIds, - unsubscribedMessageTypeIds, - subscribedMessageTypeIds, - campaignId, - templateId - ) + emailListIds, + unsubscribedChannelIds, + unsubscribedMessageTypeIds, + subscribedMessageTypeIds, + campaignId, + templateId, + ); } // PRIVATE private static setupEventHandlers() { - //Remove all listeners to avoid duplicate listeners - RNEventEmitter.removeAllListeners(EventName.handleUrlCalled) - RNEventEmitter.removeAllListeners(EventName.handleInAppCalled) - RNEventEmitter.removeAllListeners(EventName.handleCustomActionCalled) - RNEventEmitter.removeAllListeners(EventName.handleAuthCalled) + //Remove all listeners to avoid duplicate listeners + RNEventEmitter.removeAllListeners(EventName.handleUrlCalled); + RNEventEmitter.removeAllListeners(EventName.handleInAppCalled); + RNEventEmitter.removeAllListeners(EventName.handleCustomActionCalled); + RNEventEmitter.removeAllListeners(EventName.handleAuthCalled); if (Iterable.savedConfig.urlHandler) { - RNEventEmitter.addListener( - EventName.handleUrlCalled, - (dict) => { - const url = dict["url"] - const context = IterableActionContext.fromDict(dict["context"]) - Iterable.wakeApp() - - if (Platform.OS === "android") { - //Give enough time for Activity to wake up. - setTimeout(() => { - callUrlHandler(url, context) - }, 1000) - } else { - callUrlHandler(url, context) - } + RNEventEmitter.addListener(EventName.handleUrlCalled, (dict) => { + const url = dict.url; + const context = IterableActionContext.fromDict(dict.context); + Iterable.wakeApp(); + + if (Platform.OS === 'android') { + //Give enough time for Activity to wake up. + setTimeout(() => { + callUrlHandler(url, context); + }, 1000); + } else { + callUrlHandler(url, context); } - ) + }); } if (Iterable.savedConfig.customActionHandler) { - RNEventEmitter.addListener( - EventName.handleCustomActionCalled, - (dict) => { - const action = IterableAction.fromDict(dict["action"]) - const context = IterableActionContext.fromDict(dict["context"]) - Iterable.savedConfig.customActionHandler!(action, context) - } - ) + RNEventEmitter.addListener(EventName.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( - EventName.handleInAppCalled, - (messageDict) => { - const message = IterableInAppMessage.fromDict(messageDict) - const result = Iterable.savedConfig.inAppHandler!(message) - RNIterableAPI.setInAppShowResponse(result) - } - ) + RNEventEmitter.addListener(EventName.handleInAppCalled, (messageDict) => { + const message = IterableInAppMessage.fromDict(messageDict); + const result = Iterable.savedConfig.inAppHandler!(message); + RNIterableAPI.setInAppShowResponse(result); + }); } if (Iterable.savedConfig.authHandler) { - var authResponseCallback: AuthResponseCallback - RNEventEmitter.addListener( - EventName.handleAuthCalled, - () => { - Iterable.savedConfig.authHandler!() - .then(promiseResult => { - // Promise result can be either just String OR of type AuthResponse. + var authResponseCallback: AuthResponseCallback; + RNEventEmitter.addListener(EventName.handleAuthCalled, () => { + Iterable.savedConfig.authHandler!() + .then((promiseResult) => { + // Promise result can be either just String OR of type AuthResponse. // 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 AuthResponse())) { - - RNIterableAPI.passAlongAuthToken((promiseResult as AuthResponse).authToken) - - setTimeout(() => { - if(authResponseCallback === AuthResponseCallback.SUCCESS) { - if((promiseResult as AuthResponse).successCallback){ - (promiseResult as AuthResponse).successCallback!() - } - } else if(authResponseCallback === AuthResponseCallback.FAILURE) { - if((promiseResult as AuthResponse).failureCallback){ - (promiseResult as AuthResponse).failureCallback!() - } - } else { - 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('Unexpected promise returned. Auth token expects promise of String or AuthResponse type.') - } - }).catch(e => Iterable.logger.log(e)) - } - ) + if (typeof promiseResult === typeof new AuthResponse()) { + RNIterableAPI.passAlongAuthToken((promiseResult as AuthResponse).authToken); - RNEventEmitter.addListener( - EventName.handleAuthSuccessCalled, - () => { - authResponseCallback = AuthResponseCallback.SUCCESS - } - ) - RNEventEmitter.addListener( - EventName.handleAuthFailureCalled, - () => { - authResponseCallback = AuthResponseCallback.FAILURE - } - ) + setTimeout(() => { + if (authResponseCallback === AuthResponseCallback.SUCCESS) { + if ((promiseResult as AuthResponse).successCallback) { + (promiseResult as AuthResponse).successCallback!(); + } + } else if (authResponseCallback === AuthResponseCallback.FAILURE) { + if ((promiseResult as AuthResponse).failureCallback) { + (promiseResult as AuthResponse).failureCallback!(); + } + } else { + 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( + 'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.', + ); + } + }) + .catch((e) => Iterable.logger.log(e)); + }); + + RNEventEmitter.addListener(EventName.handleAuthSuccessCalled, () => { + authResponseCallback = AuthResponseCallback.SUCCESS; + }); + RNEventEmitter.addListener(EventName.handleAuthFailureCalled, () => { + authResponseCallback = AuthResponseCallback.FAILURE; + }); } function callUrlHandler(url: any, context: IterableActionContext) { if (Iterable.savedConfig.urlHandler!(url, context) == false) { Linking.canOpenURL(url) - .then(canOpen => { - if (canOpen) { Linking.openURL(url) } + .then((canOpen) => { + if (canOpen) { + Linking.openURL(url); + } }) - .catch(reason => { Iterable.logger.log("could not open url: " + reason) }) + .catch((reason) => { + Iterable.logger.log('could not open url: ' + reason); + }); } } } private static getVersionFromPackageJson(): string { - const json = require('../package.json') - const version = json["version"] as string - return version + const json = require('../package.json'); + const version = json.version as string; + return version; } } @@ -717,5 +745,5 @@ export { IterableCommerceItem, EventName, IterableActionSource, - IterableLogLevel -} \ No newline at end of file + IterableLogLevel, +}; diff --git a/src/IterableConfig.ts b/src/IterableConfig.ts index 20d1cb696..f003fd7b1 100644 --- a/src/IterableConfig.ts +++ b/src/IterableConfig.ts @@ -1,157 +1,151 @@ -'use strict' +import { IterableAction, IterableActionContext, IterableLogLevel } from './IterableAction'; -import { - IterableAction, - IterableActionContext, - IterableLogLevel -} from './IterableAction' +import { IterableInAppShowResponse } from './IterableInAppClasses'; -import { IterableInAppShowResponse } from './IterableInAppClasses' +import IterableInAppMessage from './IterableInAppMessage'; -import IterableInAppMessage from './IterableInAppMessage' +import { IterableDataRegion } from './IterableDataRegion'; -import { IterableDataRegion } from './IterableDataRegion' - -type AuthCallBack = (() => void) +type AuthCallBack = () => void; /** * An IterableConfig object sets various properties of the SDK. * An IterableConfig object is passed into the static initialize method on the Iterable class when initializing the SDK. -*/ + */ class IterableConfig { /** * The name of the Iterable push integration that will send push notifications to your app. * Defaults to your app's application ID or bundle ID for iOS. - * - * Note: Don't specify this value unless you are using an older Iterable push integration that + * + * Note: Don't specify this value unless you are using an older Iterable push integration that * has a custom name. To view your existing integrations, navigate to Settings > Mobile Apps. */ - pushIntegrationName?: string + pushIntegrationName?: string; /** - * When set to true (which is the default value), IterableSDK will automatically register and deregister + * When set to true (which is the default value), IterableSDK will automatically register and deregister * notification tokens when you provide email or userId values to the SDK using Iterable.setEmail or Iterable.setUserId. */ - autoPushRegistration = true + autoPushRegistration = true; /** - * When set to true, it will check for deferred deep links on first time app launch after installation from the App Store. - * This is currently deprecated and will be removed in the future. - */ - checkForDeferredDeeplink = false + * When set to true, it will check for deferred deep links on first time app launch after installation from the App Store. + * This is currently deprecated and will be removed in the future. + */ + checkForDeferredDeeplink = false; /** - * Number of seconds to wait when displaying multiple in-app messages in sequence. + * Number of seconds to wait when displaying multiple in-app messages in sequence. * between each. Defaults to 30 seconds. */ - inAppDisplayInterval: number = 30.0 + inAppDisplayInterval: number = 30.0; /** * A callback function used to handle deep link URLs and in-app message button and link URLs. */ - urlHandler?: (url: string, context: IterableActionContext) => boolean + urlHandler?: (url: string, context: IterableActionContext) => boolean; /** * A function expression used to handle `action://` URLs for in-app buttons and links. */ - customActionHandler?: (action: IterableAction, context: IterableActionContext) => boolean + customActionHandler?: (action: IterableAction, context: IterableActionContext) => boolean; /** - * Implement this callback to override default in-app behavior. - * By default, every single in-app will be shown as soon as it is available. - * If more than 1 in-app is available, we show the first. - * - * See "In-App Messages with Iterable's React Native SDK" in support documentation - * for more information. - */ - inAppHandler?: (message: IterableInAppMessage) => IterableInAppShowResponse + * Implement this callback to override default in-app behavior. + * By default, every single in-app will be shown as soon as it is available. + * If more than 1 in-app is available, we show the first. + * + * See "In-App Messages with Iterable's React Native SDK" in support documentation + * for more information. + */ + inAppHandler?: (message: IterableInAppMessage) => IterableInAppShowResponse; /** * A function expression that provides a valid JWT for the app's current user to Iterable's - * React Native SDK. Provide an implementation for this method only if your app uses a + * React Native SDK. Provide an implementation for this method only if your app uses a * JWT-enabled API key. */ - authHandler?:() => Promise - + authHandler?: () => Promise; + /** - * Set the verbosity of Android and iOS project's log system. - * By default, you will be able to see info level logs printed in IDE when running the app. - */ - logLevel: IterableLogLevel = IterableLogLevel.info + * Set the verbosity of Android and iOS project's log system. + * By default, you will be able to see info level logs printed in IDE when running the app. + */ + logLevel: IterableLogLevel = IterableLogLevel.info; /** * Set whether the React Native SDK should print function calls to console * This is for calls within the React Native layer, and is separate from `logLevel` * which affects the Android and iOS native SDKs */ - logReactNativeSdkCalls: boolean = true + logReactNativeSdkCalls: boolean = true; /** * The number of seconds before the current JWT's expiration that the SDK should call the * authHandler to get an updated JWT. */ - expiringAuthTokenRefreshPeriod: number = 60.0 + expiringAuthTokenRefreshPeriod: number = 60.0; /** * Use this array to declare the specific URL protocols that the SDK can expect to see on incoming * links from Iterable, so it knows that it can safely handle them as needed. This array helps * prevent the SDK from opening links that use unexpected URL protocols. */ - allowedProtocols: Array = [] + allowedProtocols: Array = []; /** * DEPRECATED - please use `useInMemoryStorageForInApps` as a replacement for this config option. - * + * * NOTE: until this option is removed, it will still function with `useInMemoryStorageForInApps` by * doing an OR operation, so if either this or `useInMemoryStorageForInApps` are set to `true`, * the native Android SDK layer will use in memory storage for in-apps. - * + * * This specifies the `useInMemoryStorageForInApps` config option downstream to the Android SDK layer. */ - androidSdkUseInMemoryStorageForInApps: boolean = false + androidSdkUseInMemoryStorageForInApps: boolean = false; /** * This specifies the `useInMemoryStorageForInApps` config option downstream to the native SDK layers. * Please read the respective `IterableConfig` files for specific details on this config option. */ - useInMemoryStorageForInApps: boolean = false + useInMemoryStorageForInApps: boolean = false; /** * This specifies the data region which determines the data center and associated endpoints used by the SDK */ - dataRegion: IterableDataRegion = IterableDataRegion.US - + dataRegion: IterableDataRegion = IterableDataRegion.US; + /** * Android only feature: This controls whether the SDK should enforce encryption for all PII stored on disk. * By default, the SDK will not enforce encryption and may fallback to unencrypted storage in case the encryption fails. */ - encryptionEnforced: boolean = false + encryptionEnforced: boolean = false; toDict(): any { return { - "pushIntegrationName": this.pushIntegrationName, - "autoPushRegistration": this.autoPushRegistration, - "inAppDisplayInterval": this.inAppDisplayInterval, - "urlHandlerPresent": this.urlHandler != undefined, - "customActionHandlerPresent": this.customActionHandler != undefined, - "inAppHandlerPresent": this.inAppHandler != undefined, - "authHandlerPresent": this.authHandler != undefined, - "logLevel": this.logLevel, - "expiringAuthTokenRefreshPeriod": this.expiringAuthTokenRefreshPeriod, - "allowedProtocols": this.allowedProtocols, - "androidSdkUseInMemoryStorageForInApps": this.androidSdkUseInMemoryStorageForInApps, - "useInMemoryStorageForInApps": this.useInMemoryStorageForInApps, - "dataRegion": this.dataRegion, - "encryptionEnforced": this.encryptionEnforced - } + pushIntegrationName: this.pushIntegrationName, + autoPushRegistration: this.autoPushRegistration, + inAppDisplayInterval: this.inAppDisplayInterval, + urlHandlerPresent: this.urlHandler != undefined, + customActionHandlerPresent: this.customActionHandler != undefined, + inAppHandlerPresent: this.inAppHandler != undefined, + authHandlerPresent: this.authHandler != undefined, + logLevel: this.logLevel, + expiringAuthTokenRefreshPeriod: this.expiringAuthTokenRefreshPeriod, + allowedProtocols: this.allowedProtocols, + androidSdkUseInMemoryStorageForInApps: this.androidSdkUseInMemoryStorageForInApps, + useInMemoryStorageForInApps: this.useInMemoryStorageForInApps, + dataRegion: this.dataRegion, + encryptionEnforced: this.encryptionEnforced, + }; } } export class AuthResponse { - authToken?: string = "" - successCallback?: AuthCallBack - failureCallback?: AuthCallBack -} + authToken?: string = ''; + successCallback?: AuthCallBack; + failureCallback?: AuthCallBack; +} -export default IterableConfig \ No newline at end of file +export default IterableConfig; diff --git a/src/IterableInAppClasses.ts b/src/IterableInAppClasses.ts index d10a7ec64..4e02a8f53 100644 --- a/src/IterableInAppClasses.ts +++ b/src/IterableInAppClasses.ts @@ -1,11 +1,9 @@ -'use strict' - /** * `show` to show the in-app otherwise `skip` to skip. */ enum IterableInAppShowResponse { show = 0, - skip = 1 + skip = 1, } /** @@ -20,15 +18,15 @@ enum IterableInAppTriggerType { } class IterableInAppTrigger { - type: IterableInAppTriggerType + type: IterableInAppTriggerType; constructor(type: IterableInAppTriggerType) { - this.type = type + this.type = type; } static fromDict(dict: any): IterableInAppTrigger { - const type = dict["type"] as IterableInAppTriggerType | IterableInAppTriggerType.immediate - return new IterableInAppTrigger(type) + const type = dict.type as IterableInAppTriggerType | IterableInAppTriggerType.immediate; + return new IterableInAppTrigger(type); } } @@ -56,57 +54,63 @@ enum IterableInAppDeleteSource { } class IterableEdgeInsets { - top: number - left: number - bottom: number - right: number + top: number; + left: number; + bottom: number; + right: number; constructor(top: number, left: number, bottom: number, right: number) { - this.top = top - this.left = left - this.bottom = bottom - this.right = right + this.top = top; + this.left = left; + this.bottom = bottom; + this.right = right; } static fromDict(dict: any): IterableEdgeInsets { - return new IterableEdgeInsets(dict["top"] as number, dict["left"] as number, dict["bottom"] as number, dict["right"] as number) + return new IterableEdgeInsets( + dict.top as number, + dict.left as number, + dict.bottom as number, + dict.right as number, + ); } } export interface IterableInAppContent { - type: IterableInAppContentType + type: IterableInAppContentType; } class IterableHtmlInAppContent implements IterableInAppContent { - type: IterableInAppContentType = IterableInAppContentType.html - edgeInsets: IterableEdgeInsets - html: string + type: IterableInAppContentType = IterableInAppContentType.html; + edgeInsets: IterableEdgeInsets; + html: string; constructor(edgeInsets: IterableEdgeInsets, html: string) { - this.edgeInsets = edgeInsets - this.html = html + this.edgeInsets = edgeInsets; + this.html = html; } static fromDict(dict: any): IterableHtmlInAppContent { return new IterableHtmlInAppContent( - IterableEdgeInsets.fromDict(dict["edgeInsets"]), - dict["html"] as string) + IterableEdgeInsets.fromDict(dict.edgeInsets), + dict.html as string, + ); } } class IterableInboxMetadata { - title?: string - subtitle?: string - icon?: string + title?: string; + subtitle?: string; + icon?: string; constructor(title: string | undefined, subtitle: string | undefined, icon: string | undefined) { - this.title = title - this.subtitle = subtitle - this.icon = icon + this.title = title; + this.subtitle = subtitle; + this.icon = icon; } static fromDict(dict: any): IterableInboxMetadata { - return new IterableInboxMetadata(dict["title"], dict["subtitle"], dict["icon"]) + return new IterableInboxMetadata(dict.title, dict.subtitle, dict.icon); } } @@ -121,4 +125,4 @@ export { IterableInAppLocation, IterableInAppCloseSource, IterableInAppDeleteSource, -} +}; diff --git a/src/IterableInAppManager.ts b/src/IterableInAppManager.ts index d9759d6fa..ef35e3aba 100644 --- a/src/IterableInAppManager.ts +++ b/src/IterableInAppManager.ts @@ -1,121 +1,122 @@ -'use strict' - -import { NativeModules } from 'react-native' +import { NativeModules } from 'react-native'; import { IterableHtmlInAppContent, IterableInAppLocation, IterableInAppDeleteSource, -} from './IterableInAppClasses' -import { Iterable } from './Iterable' +} from './IterableInAppClasses'; +import { Iterable } from './Iterable'; -import IterableInAppMessage from './IterableInAppMessage' +import IterableInAppMessage from './IterableInAppMessage'; -const RNIterableAPI = NativeModules.RNIterableAPI +const RNIterableAPI = NativeModules.RNIterableAPI; - /** - * IterableInAppManager is set up as the inAppManager property of an Iterable instance. - */ +/** + * IterableInAppManager is set up as the inAppManager property of an Iterable instance. + */ class IterableInAppManager { - /** * This method returns the current user's list of in-app messages stored in the local queue in the form of a promise. * Use `then` keyword to get the array of IterableInAppMessage objects. - * + * * This method does not cause the application to immediately check for new in-app messages on the server, since the SDK keeps the message list in sync. - * + * * parameters: none */ getMessages(): Promise> { - Iterable.logger.log("InAppManager.getMessages") + Iterable.logger.log('InAppManager.getMessages'); - return RNIterableAPI.getInAppMessages() + return RNIterableAPI.getInAppMessages(); } - /** + /** * This method returns the current user's list of in-app messages designated for the mobile inbox stored in the local queue in the form of a promise. * Use `then` keyword to get the array of IterableInAppMessage objects marked as `saveToInbox`. - * - * This method does not cause the application to immediately check for new in-app messages on the server, since the SDK keeps the message list in sync. - * + * + * This method does not cause the application to immediately check for new in-app messages on the server, since the SDK keeps the message list in sync. + * * parameters: none */ getInboxMessages(): Promise> { - Iterable.logger.log("InAppManager.getInboxMessages") + Iterable.logger.log('InAppManager.getInboxMessages'); - return RNIterableAPI.getInboxMessages() + return RNIterableAPI.getInboxMessages(); } /** * This method renders an in-app message and consumes it from the user's message queue if necessary. - * + * * This method returns a Promise. Use `then` to get the string it returns, which corresponds to the URL * of the button or link the current user tapped in the in-app message to close it. - * + * * @param {IterableInAppMessage} message The message to show (an IterableInAppMessage object) * @param {boolean} consume Whether or not the message should be consumed from the user's message queue after being shown. This should be defaulted to true. */ showMessage(message: IterableInAppMessage, consume: boolean): Promise { - Iterable.logger.log("InAppManager.show") + Iterable.logger.log('InAppManager.show'); - return RNIterableAPI.showMessage(message.messageId, consume) + return RNIterableAPI.showMessage(message.messageId, consume); } /** * This method removes the specifed message from the current user's message queue. - * Also, this method calls the inAppConsume method internally. - * + * Also, this method calls the inAppConsume method internally. + * * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) * @param {IterableInAppLocation} location the location of the in-app message (an IterableInAppLocation enum) * @param {IterableInAppDeleteSource} source how the in-app message was deleted (an IterableInAppDeleteSource enum) */ - removeMessage(message: IterableInAppMessage, location: IterableInAppLocation, source: IterableInAppDeleteSource): void { - Iterable.logger.log("InAppManager.remove") - - return RNIterableAPI.removeMessage(message.messageId, location, source) + removeMessage( + message: IterableInAppMessage, + location: IterableInAppLocation, + source: IterableInAppDeleteSource, + ): void { + Iterable.logger.log('InAppManager.remove'); + + return RNIterableAPI.removeMessage(message.messageId, location, source); } /** * This method sets the read status of specified in-app message. - * + * * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) * @param {boolean} read the boolean value indicating whether the in-app message was read */ setReadForMessage(message: IterableInAppMessage, read: boolean) { - Iterable.logger.log("InAppManager.setRead") + Iterable.logger.log('InAppManager.setRead'); - RNIterableAPI.setReadForMessage(message.messageId, read) + RNIterableAPI.setReadForMessage(message.messageId, read); } /** * This method returns HTML in-app content for a specified in-app message. - * This method returns a Promise. Use `then` to get the HTML content returned as an IterableHtmlInAppContent object. - * + * This method returns a Promise. Use `then` to get the HTML content returned as an IterableHtmlInAppContent object. + * * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) */ getHtmlContentForMessage(message: IterableInAppMessage): Promise { - Iterable.logger.log("InAppManager.getHtmlContentForMessage") + Iterable.logger.log('InAppManager.getHtmlContentForMessage'); - return RNIterableAPI.getHtmlInAppContentForMessage(message.messageId) + return RNIterableAPI.getHtmlInAppContentForMessage(message.messageId); } /** * This method turns on or off automatic displaying of incoming in-app messages. * If set to false, the SDK will immediately retrieve and process in-app messages from the message queue. * The default value of isAutoDisplayPaused is false (in the native code). - * + * * @param {boolean} paused whether the automatic displaying should be paused */ setAutoDisplayPaused(paused: boolean) { - Iterable.logger.log("InAppManager.setAutoDisplayPaused") - - RNIterableAPI.setAutoDisplayPaused(paused) + Iterable.logger.log('InAppManager.setAutoDisplayPaused'); + + RNIterableAPI.setAutoDisplayPaused(paused); } } -export default IterableInAppManager \ No newline at end of file +export default IterableInAppManager; diff --git a/src/IterableInAppMessage.ts b/src/IterableInAppMessage.ts index f7c358f62..a86ce98a6 100644 --- a/src/IterableInAppMessage.ts +++ b/src/IterableInAppMessage.ts @@ -1,14 +1,12 @@ -'use strict' - -import IterableUtil from './IterableUtil' +import IterableUtil from './IterableUtil'; import { IterableInAppTrigger, IterableInAppTriggerType, IterableInboxMetadata, -} from './IterableInAppClasses' +} from './IterableInAppClasses'; -import { ViewToken } from 'react-native' +import { ViewToken } from 'react-native'; /** * Iterable in-app message @@ -17,54 +15,55 @@ class IterableInAppMessage { /** * the ID for the in-app message */ - readonly messageId: string + readonly messageId: string; /** * the campaign ID for this message */ - readonly campaignId: number + readonly campaignId: number; /** * when to trigger this in-app */ - readonly trigger: IterableInAppTrigger + readonly trigger: IterableInAppTrigger; /** * when was this message created */ - readonly createdAt?: Date + readonly createdAt?: Date; /** * when to expire this in-app (undefined means do not expire) */ - readonly expiresAt?: Date + readonly expiresAt?: Date; /** * Whether to save this message to inbox */ - readonly saveToInbox: boolean + readonly saveToInbox: boolean; /** * Metadata such as title, subtitle etc. needed to display this in-app message in inbox. */ - readonly inboxMetadata?: IterableInboxMetadata + readonly inboxMetadata?: IterableInboxMetadata; /** * Custom Payload for this message. */ - readonly customPayload?: any + readonly customPayload?: any; /** * Whether this inbox message has been read */ - readonly read: boolean + readonly read: boolean; /** * the priority value this in-app message has */ - readonly priorityLevel: number + readonly priorityLevel: number; - constructor(messageId: string, + constructor( + messageId: string, campaignId: number, trigger: IterableInAppTrigger, createdAt: Date | undefined, @@ -73,21 +72,22 @@ class IterableInAppMessage { inboxMetadata: IterableInboxMetadata | undefined, customPayload: any | undefined, read: boolean, - priorityLevel: number) { - this.campaignId = campaignId - this.messageId = messageId - this.trigger = trigger - this.createdAt = createdAt - this.expiresAt = expiresAt - this.saveToInbox = saveToInbox - this.inboxMetadata = inboxMetadata - this.customPayload = customPayload - this.read = read - this.priorityLevel = priorityLevel + priorityLevel: number, + ) { + this.campaignId = campaignId; + this.messageId = messageId; + this.trigger = trigger; + this.createdAt = createdAt; + this.expiresAt = expiresAt; + this.saveToInbox = saveToInbox; + this.inboxMetadata = inboxMetadata; + this.customPayload = customPayload; + this.read = read; + this.priorityLevel = priorityLevel; } static fromViewToken(viewToken: ViewToken) { - var inAppMessage = viewToken.item["inAppMessage"] as IterableInAppMessage + var inAppMessage = viewToken.item.inAppMessage as IterableInAppMessage; return new IterableInAppMessage( inAppMessage.messageId, @@ -99,40 +99,40 @@ class IterableInAppMessage { inAppMessage.inboxMetadata, inAppMessage.customPayload, inAppMessage.read, - inAppMessage.priorityLevel - ) + inAppMessage.priorityLevel, + ); } isSilentInbox(): boolean { - return this.saveToInbox && this.trigger.type == IterableInAppTriggerType.never + return this.saveToInbox && this.trigger.type == IterableInAppTriggerType.never; } static fromDict(dict: any): IterableInAppMessage { - const messageId = dict["messageId"] as string - const campaignId = dict["campaignId"] as number - const trigger = IterableInAppTrigger.fromDict(dict["trigger"]) - let createdAt = dict["createdAt"] + const messageId = dict.messageId as string; + const campaignId = dict.campaignId as number; + const trigger = IterableInAppTrigger.fromDict(dict.trigger); + let createdAt = dict.createdAt; if (createdAt) { - var dateObject = new Date(0) - createdAt = dateObject.setUTCMilliseconds(createdAt) + var dateObject = new Date(0); + createdAt = dateObject.setUTCMilliseconds(createdAt); } - let expiresAt = dict["expiresAt"] + let expiresAt = dict.expiresAt; if (expiresAt) { - var dateObject = new Date(0) - expiresAt = dateObject.setUTCMilliseconds(expiresAt) + var dateObject = new Date(0); + expiresAt = dateObject.setUTCMilliseconds(expiresAt); } - let saveToInbox = IterableUtil.readBoolean(dict, "saveToInbox") - let inboxMetadataDict = dict["inboxMetadata"] - let inboxMetadata: IterableInboxMetadata | undefined + let saveToInbox = IterableUtil.readBoolean(dict, 'saveToInbox'); + let inboxMetadataDict = dict.inboxMetadata; + let inboxMetadata: IterableInboxMetadata | undefined; if (inboxMetadataDict) { - inboxMetadata = IterableInboxMetadata.fromDict(inboxMetadataDict) + inboxMetadata = IterableInboxMetadata.fromDict(inboxMetadataDict); } else { - inboxMetadata = undefined + inboxMetadata = undefined; } - let customPayload = dict["customPayload"] - let read = IterableUtil.readBoolean(dict, "read") + let customPayload = dict.customPayload; + let read = IterableUtil.readBoolean(dict, 'read'); - let priorityLevel = dict["priorityLevel"] as number + let priorityLevel = dict.priorityLevel as number; return new IterableInAppMessage( messageId, @@ -144,9 +144,9 @@ class IterableInAppMessage { inboxMetadata, customPayload, read, - priorityLevel - ) + priorityLevel, + ); } } -export default IterableInAppMessage \ No newline at end of file +export default IterableInAppMessage; diff --git a/src/IterableInbox.tsx b/src/IterableInbox.tsx index 3c43d330a..c883a34f3 100644 --- a/src/IterableInbox.tsx +++ b/src/IterableInbox.tsx @@ -1,321 +1,324 @@ -'use strict' - -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect } from 'react'; import { - View, - Text, - StyleSheet, - Animated, - NativeModules, - NativeEventEmitter, - Platform -} from 'react-native' + View, + Text, + StyleSheet, + Animated, + NativeModules, + NativeEventEmitter, + Platform, +} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context' +import { SafeAreaView } from 'react-native-safe-area-context'; -import { - IterableInAppDeleteSource, - IterableInAppLocation -} from './IterableInAppClasses' +import { IterableInAppDeleteSource, IterableInAppLocation } from './IterableInAppClasses'; -import { Iterable } from './Iterable' +import { Iterable } from './Iterable'; -import IterableInboxEmptyState from './IterableInboxEmptyState' -import InboxImpressionRowInfo from './InboxImpressionRowInfo' -import useDeviceOrientation from './useDeviceOrientation' -import useAppStateListener from './useAppStateListener' -import IterableInboxCustomizations from './IterableInboxCustomizations' -import IterableInboxMessageList from './IterableInboxMessageList' -import IterableInboxMessageDisplay from './IterableInboxMessageDisplay' -import IterableInboxDataModel from './IterableInboxDataModel' -import InboxRowViewModel from './InboxRowViewModel' +import IterableInboxEmptyState from './IterableInboxEmptyState'; +import InboxImpressionRowInfo from './InboxImpressionRowInfo'; +import useDeviceOrientation from './useDeviceOrientation'; +import useAppStateListener from './useAppStateListener'; +import IterableInboxCustomizations from './IterableInboxCustomizations'; +import IterableInboxMessageList from './IterableInboxMessageList'; +import IterableInboxMessageDisplay from './IterableInboxMessageDisplay'; +import IterableInboxDataModel from './IterableInboxDataModel'; +import InboxRowViewModel from './InboxRowViewModel'; -import { useIsFocused } from '@react-navigation/native' +import { useIsFocused } from '@react-navigation/native'; -const RNIterableAPI = NativeModules.RNIterableAPI -const RNEventEmitter = new NativeEventEmitter(RNIterableAPI) +const RNIterableAPI = NativeModules.RNIterableAPI; +const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); type inboxProps = { - returnToInboxTrigger?: boolean, - messageListItemLayout?: Function, - customizations?: IterableInboxCustomizations, - tabBarHeight?: number, - tabBarPadding?: number, - safeAreaMode?: boolean, - showNavTitle?: boolean -} + returnToInboxTrigger?: boolean; + messageListItemLayout?: Function; + customizations?: IterableInboxCustomizations; + tabBarHeight?: number; + tabBarPadding?: number; + safeAreaMode?: boolean; + showNavTitle?: boolean; +}; const IterableInbox = ({ - returnToInboxTrigger = true, - messageListItemLayout = () => { return null }, - customizations = {} as IterableInboxCustomizations, - tabBarHeight = 80, - tabBarPadding = 20, - safeAreaMode = true, - showNavTitle = true + returnToInboxTrigger = true, + messageListItemLayout = () => { + return null; + }, + customizations = {} as IterableInboxCustomizations, + tabBarHeight = 80, + tabBarPadding = 20, + safeAreaMode = true, + showNavTitle = true, }: inboxProps) => { - const defaultInboxTitle = "Inbox" - const inboxDataModel = new IterableInboxDataModel() - - let { height, width, isPortrait } = useDeviceOrientation() - const appState = useAppStateListener() - const isFocused = useIsFocused() - - 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) - - const [visibleMessageImpressions, setVisibleMessageImpressions] = useState([]) - - const styles = StyleSheet.create({ - loadingScreen: { - height: '100%', - backgroundColor: 'whitesmoke' - }, - - container: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-start', - height: '100%', - width: 2 * width, - paddingBottom: 0, - paddingLeft: 0, - paddingRight: 0 - }, - - messageListContainer: { - height: '100%', - width: width, - flexDirection: 'column', - justifyContent: 'flex-start', - }, - - headline: { - fontWeight: 'bold', - fontSize: 40, - width: '100%', - height: 60, - marginTop: 0, - paddingTop: 10, - paddingBottom: 10, - paddingLeft: 30, - backgroundColor: 'whitesmoke' - } - }) - - let { - loadingScreen, - container, - headline, - messageListContainer - } = styles - - const navTitleHeight = headline.height + headline.paddingTop + headline.paddingBottom - headline = { ...headline, height: Platform.OS === "android" ? 70 : 60 } - headline = (!isPortrait) ? { ...headline, paddingLeft: 70 } : headline - - //fetches inbox messages and adds listener for inbox changes on mount - useEffect(() => { - fetchInboxMessages() - addInboxChangedListener() - - //removes listener for inbox changes on unmount and ends inbox session - return () => { - removeInboxChangedListener() - inboxDataModel.endSession(visibleMessageImpressions) + const defaultInboxTitle = 'Inbox'; + const inboxDataModel = new IterableInboxDataModel(); + + let { height, width, isPortrait } = useDeviceOrientation(); + const appState = useAppStateListener(); + const isFocused = useIsFocused(); + + 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); + + const [visibleMessageImpressions, setVisibleMessageImpressions] = useState< + InboxImpressionRowInfo[] + >([]); + + const styles = StyleSheet.create({ + loadingScreen: { + height: '100%', + backgroundColor: 'whitesmoke', + }, + + container: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + height: '100%', + width: 2 * width, + paddingBottom: 0, + paddingLeft: 0, + paddingRight: 0, + }, + + messageListContainer: { + height: '100%', + width: width, + flexDirection: 'column', + justifyContent: 'flex-start', + }, + + headline: { + fontWeight: 'bold', + fontSize: 40, + width: '100%', + height: 60, + marginTop: 0, + paddingTop: 10, + paddingBottom: 10, + paddingLeft: 30, + backgroundColor: 'whitesmoke', + }, + }); + + let { loadingScreen, container, headline, messageListContainer } = styles; + + const navTitleHeight = headline.height + headline.paddingTop + headline.paddingBottom; + headline = { ...headline, height: Platform.OS === 'android' ? 70 : 60 }; + headline = !isPortrait ? { ...headline, paddingLeft: 70 } : headline; + + //fetches inbox messages and adds listener for inbox changes on mount + useEffect(() => { + fetchInboxMessages(); + addInboxChangedListener(); + + //removes listener for inbox changes on unmount and ends inbox session + return () => { + removeInboxChangedListener(); + inboxDataModel.endSession(visibleMessageImpressions); + }; + }, []); + + //starts session when user is on inbox and app is active + //ends session when app is in background or app is closed + useEffect(() => { + if (isFocused) { + if (appState === 'active') { + inboxDataModel.startSession(visibleMessageImpressions); + } else if ( + (appState === 'background' && Platform.OS === 'android') || + appState === 'inactive' + ) { + inboxDataModel.endSession(visibleMessageImpressions); } - }, []) - - //starts session when user is on inbox and app is active - //ends session when app is in background or app is closed - useEffect(() => { - if(isFocused) { - if(appState === 'active') { - inboxDataModel.startSession(visibleMessageImpressions) - } else if(appState === 'background' && Platform.OS === 'android' || appState === 'inactive') { - inboxDataModel.endSession(visibleMessageImpressions) - } + } + }, [appState]); + + //starts session when user is on inbox + //ends session when user navigates away from inbox + useEffect(() => { + if (appState === 'active') { + if (isFocused) { + inboxDataModel.startSession(visibleMessageImpressions); + } else { + inboxDataModel.endSession(visibleMessageImpressions); } - }, [appState]) - - //starts session when user is on inbox - //ends session when user navigates away from inbox - useEffect(() => { - if(appState === 'active') { - if(isFocused) { - inboxDataModel.startSession(visibleMessageImpressions) - } else { - inboxDataModel.endSession(visibleMessageImpressions) - } - } - }, [isFocused]) - - //updates the visible rows when visible messages changes - useEffect(() => { - inboxDataModel.updateVisibleRows(visibleMessageImpressions) - }, [visibleMessageImpressions]) - - //if return to inbox trigger is provided, runs the return to inbox animation whenever the trigger is toggled - useEffect(() => { - if(isMessageDisplay) { - returnToInbox() - } - }, [returnToInboxTrigger]) - - function addInboxChangedListener() { - RNEventEmitter.addListener( - "receivedIterableInboxChanged", - () => { - fetchInboxMessages() - } - ) - } - - function removeInboxChangedListener() { - RNEventEmitter.removeAllListeners("receivedIterableInboxChanged") - } - - async function fetchInboxMessages() { - let newMessages = await inboxDataModel.refresh() - - newMessages = newMessages.map((message, index) => { - return { ...message, last: index === newMessages.length - 1 } - }) - - setRowViewModels(newMessages) - setLoading(false) - } - - function getHtmlContentForRow(id: string) { - return inboxDataModel.getHtmlContentForMessageId(id) - } - - function handleMessageSelect(id: string, index: number, rowViewModels: InboxRowViewModel[]) { - let newRowViewModels = rowViewModels.map((rowViewModel) => { - return (rowViewModel.inAppMessage.messageId === id) ? - { ...rowViewModel, read: true } : rowViewModel - }) - setRowViewModels(newRowViewModels) - inboxDataModel.setMessageAsRead(id) - setSelectedRowViewModelIdx(index) - - Iterable.trackInAppOpen(rowViewModels[index].inAppMessage, IterableInAppLocation.inbox) - - slideLeft() - } - - function deleteRow(messageId: string) { - inboxDataModel.deleteItemById(messageId, IterableInAppDeleteSource.inboxSwipe) - fetchInboxMessages() - } - - function returnToInbox(callback?: Function) { - Animated.timing(animatedValue, { - toValue: 0, - duration: 300, - useNativeDriver: false - }).start(() => typeof callback === 'function' && callback()) - setIsMessageDisplay(false) - } - - function updateVisibleMessageImpressions(messageImpressions: InboxImpressionRowInfo[]) { - setVisibleMessageImpressions(messageImpressions) - } - - function showMessageDisplay(rowViewModelList: InboxRowViewModel[], index: number) { - const selectedRowViewModel = rowViewModelList[index] - - return ( - selectedRowViewModel ? - returnToInbox(callback)} - deleteRow={(messageId: string) => deleteRow(messageId)} - contentWidth={width} - isPortrait={isPortrait} - /> : null - ) - } - - function showMessageList(loading: boolean) { - return ( - - {showNavTitle ? - - {customizations?.navTitle ? customizations?.navTitle : defaultInboxTitle} - - : null} - {rowViewModels.length ? - deleteRow(messageId)} - handleMessageSelect={(messageId: string, index: number) => handleMessageSelect(messageId, index, rowViewModels)} - updateVisibleMessageImpressions={(messageImpressions: InboxImpressionRowInfo[]) => updateVisibleMessageImpressions(messageImpressions)} - contentWidth={width} - isPortrait={isPortrait} - /> : - renderEmptyState() - } - ) - } - - function renderEmptyState() { - return loading ? - : - { + inboxDataModel.updateVisibleRows(visibleMessageImpressions); + }, [visibleMessageImpressions]); + + //if return to inbox trigger is provided, runs the return to inbox animation whenever the trigger is toggled + useEffect(() => { + if (isMessageDisplay) { + returnToInbox(); + } + }, [returnToInboxTrigger]); + + function addInboxChangedListener() { + RNEventEmitter.addListener('receivedIterableInboxChanged', () => { + fetchInboxMessages(); + }); + } + + function removeInboxChangedListener() { + RNEventEmitter.removeAllListeners('receivedIterableInboxChanged'); + } + + async function fetchInboxMessages() { + let newMessages = await inboxDataModel.refresh(); + + newMessages = newMessages.map((message, index) => { + return { ...message, last: index === newMessages.length - 1 }; + }); + + setRowViewModels(newMessages); + setLoading(false); + } + + function getHtmlContentForRow(id: string) { + return inboxDataModel.getHtmlContentForMessageId(id); + } + + function handleMessageSelect(id: string, index: number, rowViewModels: InboxRowViewModel[]) { + let newRowViewModels = rowViewModels.map((rowViewModel) => { + return rowViewModel.inAppMessage.messageId === id + ? { ...rowViewModel, read: true } + : rowViewModel; + }); + setRowViewModels(newRowViewModels); + inboxDataModel.setMessageAsRead(id); + setSelectedRowViewModelIdx(index); + + Iterable.trackInAppOpen(rowViewModels[index].inAppMessage, IterableInAppLocation.inbox); + + slideLeft(); + } + + function deleteRow(messageId: string) { + inboxDataModel.deleteItemById(messageId, IterableInAppDeleteSource.inboxSwipe); + fetchInboxMessages(); + } + + function returnToInbox(callback?: Function) { + Animated.timing(animatedValue, { + toValue: 0, + duration: 300, + useNativeDriver: false, + }).start(() => typeof callback === 'function' && callback()); + setIsMessageDisplay(false); + } + + function updateVisibleMessageImpressions(messageImpressions: InboxImpressionRowInfo[]) { + setVisibleMessageImpressions(messageImpressions); + } + + function showMessageDisplay(rowViewModelList: InboxRowViewModel[], index: number) { + const selectedRowViewModel = rowViewModelList[index]; + + return selectedRowViewModel ? ( + returnToInbox(callback)} + deleteRow={(messageId: string) => deleteRow(messageId)} + contentWidth={width} + isPortrait={isPortrait} + /> + ) : null; + } + + function showMessageList(loading: boolean) { + return ( + + {showNavTitle ? ( + + {customizations?.navTitle ? customizations?.navTitle : defaultInboxTitle} + + ) : null} + {rowViewModels.length ? ( + deleteRow(messageId)} + handleMessageSelect={(messageId: string, index: number) => + handleMessageSelect(messageId, index, rowViewModels) + } + updateVisibleMessageImpressions={(messageImpressions: InboxImpressionRowInfo[]) => + updateVisibleMessageImpressions(messageImpressions) + } contentWidth={width} - height={height} isPortrait={isPortrait} - /> - } - - function slideLeft() { - Animated.timing(animatedValue, { - toValue: 1, - duration: 300, - useNativeDriver: false - }).start() - setIsMessageDisplay(true) - } - - const inboxAnimatedView = - - {showMessageList(loading)} - {showMessageDisplay(rowViewModels, selectedRowViewModelIdx)} - - - return( - (safeAreaMode) ? - {inboxAnimatedView} : - {inboxAnimatedView} - ) -} - -export default IterableInbox \ No newline at end of file + /> + ) : ( + renderEmptyState() + )} + + ); + } + + function renderEmptyState() { + return loading ? ( + + ) : ( + + ); + } + + function slideLeft() { + Animated.timing(animatedValue, { + toValue: 1, + duration: 300, + useNativeDriver: false, + }).start(); + setIsMessageDisplay(true); + } + + const inboxAnimatedView = ( + + {showMessageList(loading)} + {showMessageDisplay(rowViewModels, selectedRowViewModelIdx)} + + ); + + return safeAreaMode ? ( + {inboxAnimatedView} + ) : ( + {inboxAnimatedView} + ); +}; + +export default IterableInbox; diff --git a/src/IterableInboxDataModel.ts b/src/IterableInboxDataModel.ts index f16213b49..f00be6dd8 100644 --- a/src/IterableInboxDataModel.ts +++ b/src/IterableInboxDataModel.ts @@ -1,157 +1,156 @@ -'use strict' - -import { NativeModules } from 'react-native' +import { NativeModules } from 'react-native'; import { - IterableInAppLocation, - IterableInAppDeleteSource, - IterableHtmlInAppContent -} from './IterableInAppClasses' + IterableInAppLocation, + IterableInAppDeleteSource, + IterableHtmlInAppContent, +} from './IterableInAppClasses'; -import { Iterable } from './Iterable' +import { Iterable } from './Iterable'; -import InboxImpressionRowInfo from './InboxImpressionRowInfo' -import InboxRowViewModel from './InboxRowViewModel' -import IterableInAppMessage from './IterableInAppMessage' +import InboxImpressionRowInfo from './InboxImpressionRowInfo'; +import InboxRowViewModel from './InboxRowViewModel'; +import IterableInAppMessage from './IterableInAppMessage'; -const RNIterableAPI = NativeModules.RNIterableAPI +const RNIterableAPI = NativeModules.RNIterableAPI; class IterableInboxDataModel { - filterFn?: (message: IterableInAppMessage) => boolean - comparatorFn?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number - dateMapperFn?: (message: IterableInAppMessage) => string | undefined - - constructor() { - + filterFn?: (message: IterableInAppMessage) => boolean; + comparatorFn?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number; + dateMapperFn?: (message: IterableInAppMessage) => string | undefined; + + constructor() {} + + set( + filter?: (message: IterableInAppMessage) => boolean, + comparator?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number, + dateMapper?: (message: IterableInAppMessage) => string | undefined, + ) { + this.filterFn = filter; + this.comparatorFn = comparator; + this.dateMapperFn = dateMapper; + } + + getFormattedDate(message: IterableInAppMessage) { + if (message.createdAt === undefined) { + return ''; } - set(filter?: (message: IterableInAppMessage) => boolean, - comparator?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number, - dateMapper?: (message: IterableInAppMessage) => string | undefined) { - this.filterFn = filter - this.comparatorFn = comparator - this.dateMapperFn = dateMapper + if (this.dateMapperFn) { + return this.dateMapperFn(message); + } else { + return this.defaultDateMapper(message); } + } - getFormattedDate(message: IterableInAppMessage) { - if (message.createdAt === undefined) { - return "" - } + getHtmlContentForMessageId(id: string): Promise { + Iterable.logger.log('IterableInboxDataModel.getHtmlContentForItem messageId: ' + id); - if (this.dateMapperFn) { - return this.dateMapperFn(message) - } else { - return this.defaultDateMapper(message) - } - } + return RNIterableAPI.getHtmlInAppContentForMessage(id).then((content: any) => { + return IterableHtmlInAppContent.fromDict(content); + }); + } - getHtmlContentForMessageId(id: string): Promise { - Iterable.logger.log("IterableInboxDataModel.getHtmlContentForItem messageId: " + id) + setMessageAsRead(id: string) { + Iterable.logger.log('IterableInboxDataModel.setMessageAsRead'); - return RNIterableAPI.getHtmlInAppContentForMessage(id).then( - (content: any) => { - return IterableHtmlInAppContent.fromDict(content) - } - ) - } + RNIterableAPI.setReadForMessage(id, true); + } - setMessageAsRead(id: string) { - Iterable.logger.log("IterableInboxDataModel.setMessageAsRead") + deleteItemById(id: string, deleteSource: IterableInAppDeleteSource) { + Iterable.logger.log('IterableInboxDataModel.deleteItemById'); - RNIterableAPI.setReadForMessage(id, true) - } + RNIterableAPI.removeMessage(id, IterableInAppLocation.inbox, deleteSource); + } - deleteItemById(id: string, deleteSource: IterableInAppDeleteSource) { - Iterable.logger.log("IterableInboxDataModel.deleteItemById") + async refresh(): Promise> { + return RNIterableAPI.getInboxMessages().then( + (messages: Array) => { + return this.processMessages(messages); + }, + () => { + return []; + }, + ); + } - RNIterableAPI.removeMessage(id, IterableInAppLocation.inbox, deleteSource) - } + // inbox session tracking functions - async refresh(): Promise> { - return RNIterableAPI.getInboxMessages().then( - (messages: Array) => { - return this.processMessages(messages) - }, - () => { - return [] - } - ) - } + startSession(visibleRows: Array = []) { + RNIterableAPI.startSession(visibleRows); + } - // inbox session tracking functions + async endSession(visibleRows: Array = []) { + await this.updateVisibleRows(visibleRows); + RNIterableAPI.endSession(); + } - startSession(visibleRows: Array = []) { - RNIterableAPI.startSession(visibleRows) - } + updateVisibleRows(visibleRows: Array = []) { + RNIterableAPI.updateVisibleRows(visibleRows); + } - async endSession(visibleRows: Array = []) { - await this.updateVisibleRows(visibleRows) - RNIterableAPI.endSession() - } - - updateVisibleRows(visibleRows: Array = []) { - RNIterableAPI.updateVisibleRows(visibleRows) - } + // private/internal - // private/internal + private static sortByMostRecent = ( + message1: IterableInAppMessage, + message2: IterableInAppMessage, + ) => { + let createdAt1 = message1.createdAt ?? new Date(0); + let createdAt2 = message2.createdAt ?? new Date(0); - private static sortByMostRecent = (message1: IterableInAppMessage, message2: IterableInAppMessage) => { - let createdAt1 = message1.createdAt ?? new Date(0) - let createdAt2 = message2.createdAt ?? new Date(0) + if (createdAt1 < createdAt2) return 1; + if (createdAt1 > createdAt2) return -1; - if (createdAt1 < createdAt2) return 1 - if (createdAt1 > createdAt2) return -1 + return 0; + }; - return 0 + private defaultDateMapper(message: IterableInAppMessage): string { + if (message.createdAt === undefined) { + return ''; } - private defaultDateMapper(message: IterableInAppMessage): string { - if (message.createdAt === undefined) { - return "" - } - - let createdAt + let createdAt; - if(typeof message.createdAt === "string") { - createdAt = new Date(parseInt(message.createdAt)) - } else { - createdAt = new Date(message.createdAt) - } - - var defaultDateString = `${createdAt.toLocaleDateString()} at ${createdAt.toLocaleTimeString()}` - - return defaultDateString + if (typeof message.createdAt === 'string') { + createdAt = new Date(parseInt(message.createdAt)); + } else { + createdAt = new Date(message.createdAt); } - private processMessages(messages: Array): Array { - return this.sortAndFilter(messages).map(IterableInboxDataModel.getInboxRowViewModelForMessage) - } + var defaultDateString = `${createdAt.toLocaleDateString()} at ${createdAt.toLocaleTimeString()}`; - private sortAndFilter(messages: Array): Array { - var sortedFilteredMessages = messages.slice() + return defaultDateString; + } - if (this.filterFn != undefined) { - sortedFilteredMessages = sortedFilteredMessages.filter(this.filterFn) - } + private processMessages(messages: Array): Array { + return this.sortAndFilter(messages).map(IterableInboxDataModel.getInboxRowViewModelForMessage); + } - if (this.comparatorFn != undefined) { - sortedFilteredMessages.sort(this.comparatorFn) - } else { - sortedFilteredMessages.sort(IterableInboxDataModel.sortByMostRecent) - } + private sortAndFilter(messages: Array): Array { + var sortedFilteredMessages = messages.slice(); - return sortedFilteredMessages + if (this.filterFn != undefined) { + sortedFilteredMessages = sortedFilteredMessages.filter(this.filterFn); } - private static getInboxRowViewModelForMessage(message: IterableInAppMessage): InboxRowViewModel { - return { - title: message.inboxMetadata?.title ?? "", - subtitle: message.inboxMetadata?.subtitle, - imageUrl: message.inboxMetadata?.icon, - createdAt: message.createdAt, - read: message.read, - inAppMessage: message - } + if (this.comparatorFn != undefined) { + sortedFilteredMessages.sort(this.comparatorFn); + } else { + sortedFilteredMessages.sort(IterableInboxDataModel.sortByMostRecent); } + + return sortedFilteredMessages; + } + + private static getInboxRowViewModelForMessage(message: IterableInAppMessage): InboxRowViewModel { + return { + title: message.inboxMetadata?.title ?? '', + subtitle: message.inboxMetadata?.subtitle, + imageUrl: message.inboxMetadata?.icon, + createdAt: message.createdAt, + read: message.read, + inAppMessage: message, + }; + } } -export default IterableInboxDataModel \ No newline at end of file +export default IterableInboxDataModel; diff --git a/src/IterableInboxEmptyState.tsx b/src/IterableInboxEmptyState.tsx index af74046c0..ab2568e86 100644 --- a/src/IterableInboxEmptyState.tsx +++ b/src/IterableInboxEmptyState.tsx @@ -1,81 +1,70 @@ -'use strict' +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; -import React from 'react' -import { - View, - Text, - StyleSheet, -} from 'react-native' - -import IterableInboxCustomizations from './IterableInboxCustomizations' +import IterableInboxCustomizations from './IterableInboxCustomizations'; type emptyStateProps = { - customizations: IterableInboxCustomizations, - tabBarHeight: number, - tabBarPadding: number, - navTitleHeight: number, - contentWidth: number, - height: number, - isPortrait: boolean -} + customizations: IterableInboxCustomizations; + tabBarHeight: number; + tabBarPadding: number; + navTitleHeight: number; + contentWidth: number; + height: number; + isPortrait: boolean; +}; const IterableInboxEmptyState = ({ - customizations, - tabBarHeight, - tabBarPadding, - navTitleHeight, - height, - isPortrait + customizations, + tabBarHeight, + tabBarPadding, + navTitleHeight, + height, + isPortrait, }: emptyStateProps) => { - const defaultTitle = "No saved messages" - const defaultBody = "Check again later!" + const defaultTitle = 'No saved messages'; + const defaultBody = 'Check again later!'; - const emptyStateTitle = customizations.noMessagesTitle - const emptyStateBody = customizations.noMessagesBody + const emptyStateTitle = customizations.noMessagesTitle; + const emptyStateBody = customizations.noMessagesBody; - let { - container, - title, - body - } = styles + let { container, title, body } = styles; - container = { ...container, height: height - navTitleHeight - tabBarHeight - tabBarPadding } + container = { + ...container, + height: height - navTitleHeight - tabBarHeight - tabBarPadding, + }; - if (!isPortrait) { - container = { ...container, height: height - navTitleHeight } - } + if (!isPortrait) { + container = { ...container, height: height - navTitleHeight }; + } - return ( - - - {emptyStateTitle ? emptyStateTitle : defaultTitle} - - - {emptyStateBody ? emptyStateBody : defaultBody} - - - ) -} + return ( + + {emptyStateTitle ? emptyStateTitle : defaultTitle} + {emptyStateBody ? emptyStateBody : defaultBody} + + ); +}; const styles = StyleSheet.create({ - container: { - height: 0, - backgroundColor: 'whitesmoke', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - }, + container: { + height: 0, + backgroundColor: 'whitesmoke', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, - title: { - fontWeight: 'bold', - fontSize: 20, - paddingBottom: 25 - }, + title: { + fontWeight: 'bold', + fontSize: 20, + paddingBottom: 25, + }, - body: { - fontSize: 15, - color: 'grey' - } -}) + body: { + fontSize: 15, + color: 'grey', + }, +}); -export default IterableInboxEmptyState \ No newline at end of file +export default IterableInboxEmptyState; diff --git a/src/IterableInboxMessageCell.tsx b/src/IterableInboxMessageCell.tsx index 6e5cc389f..db2a48110 100644 --- a/src/IterableInboxMessageCell.tsx +++ b/src/IterableInboxMessageCell.tsx @@ -1,297 +1,307 @@ -'use strict' - -import React, { useRef } from 'react' +import React, { useRef } from 'react'; import { - View, - Text, - Image, - Animated, - PanResponder, - ViewStyle, - TextStyle, - StyleSheet, - TouchableOpacity -} from 'react-native' - -import InboxRowViewModel from './InboxRowViewModel' -import IterableInboxCustomizations from './IterableInboxCustomizations' - -import IterableInboxDataModel from './IterableInboxDataModel' + View, + Text, + Image, + Animated, + PanResponder, + ViewStyle, + TextStyle, + StyleSheet, + TouchableOpacity, +} from 'react-native'; + +import InboxRowViewModel from './InboxRowViewModel'; +import IterableInboxCustomizations from './IterableInboxCustomizations'; + +import IterableInboxDataModel from './IterableInboxDataModel'; function defaultMessageListLayout( - last: boolean, - dataModel: IterableInboxDataModel, - rowViewModel: InboxRowViewModel, - customizations: IterableInboxCustomizations, - isPortrait: boolean + last: boolean, + dataModel: IterableInboxDataModel, + rowViewModel: InboxRowViewModel, + customizations: IterableInboxCustomizations, + isPortrait: boolean, ) { - const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title ?? "" - const messageBody = rowViewModel.inAppMessage.inboxMetadata?.subtitle ?? "" - const messageCreatedAt = dataModel.getFormattedDate(rowViewModel.inAppMessage) ?? "" - const thumbnailURL = rowViewModel.imageUrl - - let styles = StyleSheet.create({ - unreadIndicatorContainer: { - height: '100%', - flexDirection: 'column', - justifyContent: 'flex-start' - }, + const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title ?? ''; + const messageBody = rowViewModel.inAppMessage.inboxMetadata?.subtitle ?? ''; + const messageCreatedAt = dataModel.getFormattedDate(rowViewModel.inAppMessage) ?? ''; + const thumbnailURL = rowViewModel.imageUrl; - unreadIndicator: { - width: 15, - height: 15, - borderRadius: 15 / 2, - backgroundColor: 'blue', - marginLeft: 10, - marginRight: 5, - marginTop: 10 - }, + let styles = StyleSheet.create({ + unreadIndicatorContainer: { + height: '100%', + flexDirection: 'column', + justifyContent: 'flex-start', + }, - unreadMessageThumbnailContainer: { - paddingLeft: 10, - flexDirection: 'column', - justifyContent: 'center' - }, + unreadIndicator: { + width: 15, + height: 15, + borderRadius: 15 / 2, + backgroundColor: 'blue', + marginLeft: 10, + marginRight: 5, + marginTop: 10, + }, - readMessageThumbnailContainer: { - paddingLeft: 30, - flexDirection: 'column', - justifyContent: 'center' - }, + unreadMessageThumbnailContainer: { + paddingLeft: 10, + flexDirection: 'column', + justifyContent: 'center', + }, - messageContainer: { - paddingLeft: 10, - width: '75%', - flexDirection: 'column', - justifyContent: 'center' - }, + readMessageThumbnailContainer: { + paddingLeft: 30, + flexDirection: 'column', + justifyContent: 'center', + }, - title: { - fontSize: 22, - width: '85%', - paddingBottom: 10 - }, + messageContainer: { + paddingLeft: 10, + width: '75%', + flexDirection: 'column', + justifyContent: 'center', + }, - body: { - fontSize: 15, - color: 'lightgray', - width: '85%', - flexWrap: "wrap", - paddingBottom: 10 - }, + title: { + fontSize: 22, + width: '85%', + paddingBottom: 10, + }, - createdAt: { - fontSize: 12, - color: 'lightgray' - }, + body: { + fontSize: 15, + color: 'lightgray', + width: '85%', + flexWrap: 'wrap', + paddingBottom: 10, + }, + + createdAt: { + fontSize: 12, + color: 'lightgray', + }, + + messageRow: { + flexDirection: 'row', + backgroundColor: 'white', + paddingTop: 10, + paddingBottom: 10, + width: '100%', + height: 150, + borderStyle: 'solid', + borderColor: 'lightgray', + borderTopWidth: 1, + }, + }); + + const resolvedStyles = { ...styles, ...customizations }; + + let { + unreadIndicatorContainer, + unreadIndicator, + unreadMessageThumbnailContainer, + readMessageThumbnailContainer, + messageContainer, + title, + body, + createdAt, + messageRow, + } = resolvedStyles; - messageRow: { - flexDirection: 'row', - backgroundColor: 'white', - paddingTop: 10, - paddingBottom: 10, - width: '100%', - height: 150, - borderStyle: 'solid', - borderColor: 'lightgray', - borderTopWidth: 1 - } - }) - - const resolvedStyles = { ...styles, ...customizations } - - let { - unreadIndicatorContainer, - unreadIndicator, - unreadMessageThumbnailContainer, - readMessageThumbnailContainer, - messageContainer, - title, - body, - createdAt, - messageRow - } = resolvedStyles - - unreadIndicator = (!isPortrait) ? { ...unreadIndicator, marginLeft: 40 } : unreadIndicator - readMessageThumbnailContainer = (!isPortrait) ? { ...readMessageThumbnailContainer, paddingLeft: 65 } : readMessageThumbnailContainer - messageContainer = (!isPortrait) ? { ...messageContainer, width: '90%' } : messageContainer - - function messageRowStyle(rowViewModel: InboxRowViewModel) { - return last ? { ...messageRow, borderBottomWidth: 1 } : messageRow - } - - return ( - - - {rowViewModel.read ? null : } - - - {thumbnailURL ? : null} - - - {messageTitle as TextStyle} - {messageBody} - {messageCreatedAt as TextStyle} - + unreadIndicator = !isPortrait ? { ...unreadIndicator, marginLeft: 40 } : unreadIndicator; + readMessageThumbnailContainer = !isPortrait + ? { ...readMessageThumbnailContainer, paddingLeft: 65 } + : readMessageThumbnailContainer; + messageContainer = !isPortrait ? { ...messageContainer, width: '90%' } : messageContainer; + + function messageRowStyle(rowViewModel: InboxRowViewModel) { + return last ? { ...messageRow, borderBottomWidth: 1 } : messageRow; + } + + return ( + + + {rowViewModel.read ? null : } + + + {thumbnailURL ? ( + + ) : null} + + + + {messageTitle as TextStyle} + + + {messageBody} + + {messageCreatedAt as TextStyle} - ) + + ); } type MessageCellProps = { - index: number, - last: boolean, - dataModel: IterableInboxDataModel, - rowViewModel: InboxRowViewModel, - customizations: IterableInboxCustomizations, - swipingCheck: Function, - messageListItemLayout: Function, - deleteRow: Function, - handleMessageSelect: Function, - contentWidth: number, - isPortrait: boolean -} + index: number; + last: boolean; + dataModel: IterableInboxDataModel; + rowViewModel: InboxRowViewModel; + customizations: IterableInboxCustomizations; + swipingCheck: Function; + messageListItemLayout: Function; + deleteRow: Function; + handleMessageSelect: Function; + contentWidth: number; + isPortrait: boolean; +}; const IterableInboxMessageCell = ({ - index, - last, - dataModel, - rowViewModel, - customizations, - swipingCheck, - messageListItemLayout, - deleteRow, - handleMessageSelect, - contentWidth, - isPortrait + index, + last, + dataModel, + rowViewModel, + customizations, + swipingCheck, + messageListItemLayout, + deleteRow, + handleMessageSelect, + contentWidth, + isPortrait, }: MessageCellProps) => { - const position = useRef(new Animated.ValueXY()).current - - let deleteSliderHeight = customizations.messageRow?.height ? customizations.messageRow.height: 150 - - if(messageListItemLayout(last, rowViewModel)) { - deleteSliderHeight = messageListItemLayout(last, rowViewModel)[1] - } - - const styles = StyleSheet.create({ - textContainer: { - width: '100%', - elevation: 2 + const position = useRef(new Animated.ValueXY()).current; + + let deleteSliderHeight = customizations.messageRow?.height + ? customizations.messageRow.height + : 150; + + if (messageListItemLayout(last, rowViewModel)) { + deleteSliderHeight = messageListItemLayout(last, rowViewModel)[1]; + } + + const styles = StyleSheet.create({ + textContainer: { + width: '100%', + elevation: 2, + }, + + deleteSlider: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-end', + paddingRight: 10, + backgroundColor: 'red', + position: 'absolute', + elevation: 1, + width: '100%', + height: deleteSliderHeight, + }, + + textStyle: { + fontWeight: 'bold', + fontSize: 15, + color: 'white', + }, + }); + + let { textContainer, deleteSlider, textStyle } = styles; + + deleteSlider = isPortrait ? deleteSlider : { ...deleteSlider, paddingRight: 40 }; + + const scrollThreshold = contentWidth / 15; + const FORCING_DURATION = 350; + + //If user swipes, either complete swipe or reset + function userSwipedLeft(gesture: any) { + if (gesture.dx < -0.6 * contentWidth) { + completeSwipe(); + } else { + resetPosition(); + } + } + + function completeSwipe() { + const x = -2000; + Animated.timing(position, { + toValue: { x, y: 0 }, + duration: FORCING_DURATION, + useNativeDriver: false, + }).start(() => deleteRow(rowViewModel.inAppMessage.messageId)); + } + + function resetPosition() { + Animated.timing(position, { + toValue: { x: 0, y: 0 }, + duration: 200, + useNativeDriver: false, + }).start(); + } + + const panResponder = useRef( + PanResponder.create({ + onStartShouldSetPanResponder: () => false, + onMoveShouldSetPanResponder: (event, gestureState) => { + const { dx, dy } = gestureState; + // return true if user is swiping, return false if it's a single click + return Math.abs(dx) !== 0 && Math.abs(dy) !== 0; }, - - deleteSlider: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-end', - paddingRight: 10, - backgroundColor: 'red', - position: 'absolute', - elevation: 1, - width: '100%', - height: deleteSliderHeight + onMoveShouldSetPanResponderCapture: (event, gestureState) => { + const { dx, dy } = gestureState; + // return true if user is swiping, return false if it's a single click + return Math.abs(dx) !== 0 && Math.abs(dy) !== 0; }, - - textStyle: { - fontWeight: 'bold', - fontSize: 15, - color: 'white' - } - }) - - let { textContainer, deleteSlider, textStyle } = styles - - deleteSlider = (isPortrait) ? deleteSlider : { ...deleteSlider, paddingRight: 40 } - - const scrollThreshold = contentWidth / 15 - const FORCING_DURATION = 350 - - //If user swipes, either complete swipe or reset - function userSwipedLeft(gesture: any) { - if (gesture.dx < -0.6 * contentWidth) { - completeSwipe() - } else { - resetPosition() - } - } - - function completeSwipe() { - const x = -2000 - Animated.timing(position, { - toValue: { x, y: 0 }, - duration: FORCING_DURATION, - useNativeDriver: false - }).start(() => deleteRow(rowViewModel.inAppMessage.messageId)) - } - - function resetPosition() { - Animated.timing(position, { - toValue: { x: 0, y: 0 }, - duration: 200, - useNativeDriver: false - }).start() - } - - const panResponder = useRef( - PanResponder.create({ - onStartShouldSetPanResponder: () => false, - onMoveShouldSetPanResponder: (event, gestureState) => { - const { dx, dy } = gestureState - // return true if user is swiping, return false if it's a single click - return Math.abs(dx) !== 0 && Math.abs(dy) !== 0 - }, - onMoveShouldSetPanResponderCapture: (event, gestureState) => { - const { dx, dy } = gestureState - // return true if user is swiping, return false if it's a single click - return Math.abs(dx) !== 0 && Math.abs(dy) !== 0 - }, - onPanResponderTerminationRequest: () => false, - onPanResponderGrant: () => { - position.setValue({ x: 0, y: 0 }) - }, - onPanResponderMove: (event, gesture) => { - if (gesture.dx <= -scrollThreshold) { - //enables swipeing when threshold is reached - swipingCheck(true) - //threshold value is deleted from movement - const x = gesture.dx - //position is set to the new value - position.setValue({ x, y: 0 }) - } - }, - onPanResponderRelease: (event, gesture) => { - position.flattenOffset() - if (gesture.dx < 0) { - userSwipedLeft(gesture) - } - else { - resetPosition(); - } - swipingCheck(false) - } - }) - ).current - - return ( - <> - - DELETE - - - { - handleMessageSelect(rowViewModel.inAppMessage.messageId, index) - }} - > - {messageListItemLayout(last, rowViewModel) ? - messageListItemLayout(last, rowViewModel)[0] : - defaultMessageListLayout(last, dataModel, rowViewModel, customizations, isPortrait)} - - - - ) -} + onPanResponderTerminationRequest: () => false, + onPanResponderGrant: () => { + position.setValue({ x: 0, y: 0 }); + }, + onPanResponderMove: (event, gesture) => { + if (gesture.dx <= -scrollThreshold) { + //enables swipeing when threshold is reached + swipingCheck(true); + //threshold value is deleted from movement + const x = gesture.dx; + //position is set to the new value + position.setValue({ x, y: 0 }); + } + }, + onPanResponderRelease: (event, gesture) => { + position.flattenOffset(); + if (gesture.dx < 0) { + userSwipedLeft(gesture); + } else { + resetPosition(); + } + swipingCheck(false); + }, + }), + ).current; + + return ( + <> + + DELETE + + + { + handleMessageSelect(rowViewModel.inAppMessage.messageId, index); + }} + > + {messageListItemLayout(last, rowViewModel) + ? messageListItemLayout(last, rowViewModel)[0] + : defaultMessageListLayout(last, dataModel, rowViewModel, customizations, isPortrait)} + + + + ); +}; -export default IterableInboxMessageCell \ No newline at end of file +export default IterableInboxMessageCell; diff --git a/src/IterableInboxMessageDisplay.tsx b/src/IterableInboxMessageDisplay.tsx index 5e474d00d..768239283 100644 --- a/src/IterableInboxMessageDisplay.tsx +++ b/src/IterableInboxMessageDisplay.tsx @@ -1,134 +1,133 @@ -'use strict' +import React, { useState, useEffect } from 'react'; +import { + Text, + View, + ScrollView, + StyleSheet, + Linking, + TouchableWithoutFeedback, +} from 'react-native'; +import { WebView } from 'react-native-webview'; +import Icon from 'react-native-vector-icons/Ionicons'; -import React, { useState, useEffect } from 'react' import { - Text, - View, - ScrollView, - StyleSheet, - Linking, - TouchableWithoutFeedback, -} from 'react-native' -import { WebView } from 'react-native-webview' -import Icon from 'react-native-vector-icons/Ionicons' - -import { - IterableHtmlInAppContent, - IterableEdgeInsets, - IterableInAppLocation, - IterableInAppCloseSource -} from './IterableInAppClasses' - -import { - IterableAction, - IterableActionContext - } from './IterableAction' - -import InboxRowViewModel from './InboxRowViewModel' - -import { Iterable, IterableActionSource } from './Iterable' + IterableHtmlInAppContent, + IterableEdgeInsets, + IterableInAppLocation, + IterableInAppCloseSource, +} from './IterableInAppClasses'; + +import { IterableAction, IterableActionContext } from './IterableAction'; + +import InboxRowViewModel from './InboxRowViewModel'; + +import { Iterable, IterableActionSource } from './Iterable'; type MessageDisplayProps = { - rowViewModel: InboxRowViewModel, - inAppContentPromise: Promise, - returnToInbox: Function, - deleteRow: Function, - contentWidth: number, - isPortrait: boolean -} - -const IterableInboxMessageDisplay = ({ - rowViewModel, - inAppContentPromise, - returnToInbox, - deleteRow, - contentWidth, - isPortrait + rowViewModel: InboxRowViewModel; + inAppContentPromise: Promise; + returnToInbox: Function; + deleteRow: Function; + contentWidth: number; + isPortrait: boolean; +}; + +const IterableInboxMessageDisplay = ({ + rowViewModel, + inAppContentPromise, + returnToInbox, + deleteRow, + contentWidth, + isPortrait, }: MessageDisplayProps) => { - const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title - const [inAppContent, setInAppContent] = useState(new IterableHtmlInAppContent(new IterableEdgeInsets(0, 0, 0, 0), "")) - - const styles = StyleSheet.create({ - messageDisplayContainer: { - height: '100%', - width: contentWidth, - backgroundColor: 'whitesmoke', - flexDirection: 'column', - justifyContent: 'flex-start' - }, - - header: { - flexDirection: 'row', - justifyContent: 'center', - width: '100%' - }, - - returnButtonContainer: { - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - width: '25%', - marginLeft: 0, - marginTop: 0 - }, - - returnButton: { - flexDirection: 'row', - alignItems: 'center' - }, - - returnButtonIcon: { - color: 'deepskyblue', - fontSize: 40, - paddingLeft: 0 - }, - - returnButtonText: { - color: 'deepskyblue', - fontSize: 20 - }, - - messageTitleContainer: { - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - width: '75%', - marginTop: 0 - }, - - messageTitle: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - width: 0.5 * contentWidth, - }, - - messageTitleText: { - fontWeight: 'bold', - fontSize: 20, - backgroundColor: 'whitesmoke' - }, - - contentContainer: { - flex: 1, - } - }) - - let { - header, - returnButtonContainer, - returnButton, - returnButtonIcon, - returnButtonText, - messageTitleContainer, - messageTitleText, - messageDisplayContainer - } = styles - - // orientation dependent styling - returnButtonContainer = (!isPortrait) ? { ...returnButtonContainer, marginLeft: 80 } : returnButtonContainer - - let JS = ` + const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title; + const [inAppContent, setInAppContent] = useState( + new IterableHtmlInAppContent(new IterableEdgeInsets(0, 0, 0, 0), ''), + ); + + const styles = StyleSheet.create({ + messageDisplayContainer: { + height: '100%', + width: contentWidth, + backgroundColor: 'whitesmoke', + flexDirection: 'column', + justifyContent: 'flex-start', + }, + + header: { + flexDirection: 'row', + justifyContent: 'center', + width: '100%', + }, + + returnButtonContainer: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + width: '25%', + marginLeft: 0, + marginTop: 0, + }, + + returnButton: { + flexDirection: 'row', + alignItems: 'center', + }, + + returnButtonIcon: { + color: 'deepskyblue', + fontSize: 40, + paddingLeft: 0, + }, + + returnButtonText: { + color: 'deepskyblue', + fontSize: 20, + }, + + messageTitleContainer: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + width: '75%', + marginTop: 0, + }, + + messageTitle: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + width: 0.5 * contentWidth, + }, + + messageTitleText: { + fontWeight: 'bold', + fontSize: 20, + backgroundColor: 'whitesmoke', + }, + + contentContainer: { + flex: 1, + }, + }); + + let { + header, + returnButtonContainer, + returnButton, + returnButtonIcon, + returnButtonText, + messageTitleContainer, + messageTitleText, + messageDisplayContainer, + } = styles; + + // orientation dependent styling + returnButtonContainer = !isPortrait + ? { ...returnButtonContainer, marginLeft: 80 } + : returnButtonContainer; + + let JS = ` const links = document.querySelectorAll('a') links.forEach(link => { @@ -137,94 +136,104 @@ const IterableInboxMessageDisplay = ({ link.href = "javascript:void(0)" link.addEventListener("click", () => { - window.ReactNativeWebView.postMessage(link.class) + window.ReactNativeWebView.postMessage(link.class) }) }) - ` - - useEffect(() => { - let mounted = true - inAppContentPromise.then( - (value) => { - if(mounted) { - setInAppContent(value) - } - }) - return () => {mounted = false} - }) - - function handleInAppLinkAction(event: any) { - let URL = event.nativeEvent.data - - let action = new IterableAction("openUrl", URL, "") - let source = IterableActionSource.inApp - let context = new IterableActionContext(action, source) + `; - Iterable.trackInAppClick(rowViewModel.inAppMessage, IterableInAppLocation.inbox, URL) - Iterable.trackInAppClose(rowViewModel.inAppMessage, IterableInAppLocation.inbox, IterableInAppCloseSource.link, URL) - - //handle delete action - if (URL === 'iterable://delete') { - returnToInbox(() => deleteRow(rowViewModel.inAppMessage.messageId)) + useEffect(() => { + let mounted = true; + inAppContentPromise.then((value) => { + if (mounted) { + setInAppContent(value); + } + }); + return () => { + mounted = false; + }; + }); + + function handleInAppLinkAction(event: any) { + let URL = event.nativeEvent.data; + + let action = new IterableAction('openUrl', URL, ''); + let source = IterableActionSource.inApp; + let context = new IterableActionContext(action, source); + + Iterable.trackInAppClick(rowViewModel.inAppMessage, IterableInAppLocation.inbox, URL); + Iterable.trackInAppClose( + rowViewModel.inAppMessage, + IterableInAppLocation.inbox, + IterableInAppCloseSource.link, + URL, + ); + + //handle delete action + if (URL === 'iterable://delete') { + returnToInbox(() => deleteRow(rowViewModel.inAppMessage.messageId)); //handle dismiss action - } else if(URL === 'iterable://dismiss') { - returnToInbox() + } else if (URL === 'iterable://dismiss') { + returnToInbox(); //handle external link - } else if (URL.slice(0, 4) === 'http') { - returnToInbox(() => Linking.openURL(URL)) + } else if (URL.slice(0, 4) === 'http') { + returnToInbox(() => Linking.openURL(URL)); //handle custom action - } else if (URL.slice(0,9) === 'action://') { - action.type = URL.replace('action://', '') - returnToInbox(() => { - if(Iterable.savedConfig.customActionHandler) { - Iterable.savedConfig.customActionHandler(action, context) - } - }) + } else if (URL.slice(0, 9) === 'action://') { + action.type = URL.replace('action://', ''); + returnToInbox(() => { + if (Iterable.savedConfig.customActionHandler) { + Iterable.savedConfig.customActionHandler(action, context); + } + }); //handle deep link or error link - } else { - returnToInbox(() => { - if(Iterable.savedConfig.urlHandler) { - Iterable.savedConfig.urlHandler(URL, context) - } - }) - } - } - - return ( - - - - { - returnToInbox() - Iterable.trackInAppClose(rowViewModel.inAppMessage, IterableInAppLocation.inbox, IterableInAppCloseSource.back) - }} - > - - - Inbox - - - - - - {messageTitle} - + } else { + returnToInbox(() => { + if (Iterable.savedConfig.urlHandler) { + Iterable.savedConfig.urlHandler(URL, context); + } + }); + } + } + + return ( + + + + { + returnToInbox(); + Iterable.trackInAppClose( + rowViewModel.inAppMessage, + IterableInAppLocation.inbox, + IterableInAppCloseSource.back, + ); + }} + > + + + Inbox - - - handleInAppLinkAction(event)} - injectedJavaScript={JS} - /> - + + + + + + {messageTitle} + + + - ) -} - - - -export default IterableInboxMessageDisplay \ No newline at end of file + + handleInAppLinkAction(event)} + injectedJavaScript={JS} + /> + + + ); +}; + +export default IterableInboxMessageDisplay; diff --git a/src/IterableInboxMessageList.tsx b/src/IterableInboxMessageList.tsx index 310a68d8d..d4ebb3327 100644 --- a/src/IterableInboxMessageList.tsx +++ b/src/IterableInboxMessageList.tsx @@ -1,106 +1,105 @@ -'use strict' +import React, { useCallback, useRef, useState } from 'react'; +import { ViewabilityConfig, ViewToken, FlatList } from 'react-native'; -import React, { useCallback, useRef, useState } from 'react' -import { - ViewabilityConfig, - ViewToken, - FlatList -} from 'react-native' +import InboxImpressionRowInfo from './InboxImpressionRowInfo'; +import IterableInboxMessageCell from './IterableInboxMessageCell'; +import InboxRowViewModel from './InboxRowViewModel'; +import IterableInboxCustomizations from './IterableInboxCustomizations'; -import InboxImpressionRowInfo from './InboxImpressionRowInfo' -import IterableInboxMessageCell from './IterableInboxMessageCell' -import InboxRowViewModel from './InboxRowViewModel' -import IterableInboxCustomizations from './IterableInboxCustomizations' - -import IterableInAppMessage from './IterableInAppMessage' -import IterableInboxDataModel from './IterableInboxDataModel' +import IterableInAppMessage from './IterableInAppMessage'; +import IterableInboxDataModel from './IterableInboxDataModel'; type MessageListProps = { - dataModel: IterableInboxDataModel, - rowViewModels: InboxRowViewModel[], - customizations: IterableInboxCustomizations, - messageListItemLayout: Function, - deleteRow: Function, - handleMessageSelect: Function, - updateVisibleMessageImpressions: Function, - contentWidth: number, - isPortrait: boolean -} + dataModel: IterableInboxDataModel; + rowViewModels: InboxRowViewModel[]; + customizations: IterableInboxCustomizations; + messageListItemLayout: Function; + deleteRow: Function; + handleMessageSelect: Function; + updateVisibleMessageImpressions: Function; + contentWidth: number; + isPortrait: boolean; +}; const IterableInboxMessageList = ({ - dataModel, - rowViewModels, - customizations, - messageListItemLayout, - deleteRow, - handleMessageSelect, - updateVisibleMessageImpressions, - contentWidth, - isPortrait + dataModel, + rowViewModels, + customizations, + messageListItemLayout, + deleteRow, + handleMessageSelect, + updateVisibleMessageImpressions, + contentWidth, + isPortrait, }: MessageListProps) => { - const [swiping, setSwiping] = useState(false) - const flatListRef = useRef(null) + const [swiping, setSwiping] = useState(false); + const flatListRef = useRef(null); - function renderRowViewModel(rowViewModel: InboxRowViewModel, index: number, last: boolean) { - return ( - setSwiping(swiping)} - messageListItemLayout={messageListItemLayout} - deleteRow={(messageId: string) => deleteRow(messageId)} - handleMessageSelect={(messageId: string, index: number) => handleMessageSelect(messageId, index)} - contentWidth={contentWidth} - isPortrait={isPortrait} - /> - ) - } + function renderRowViewModel(rowViewModel: InboxRowViewModel, index: number, last: boolean) { + return ( + setSwiping(swiping)} + messageListItemLayout={messageListItemLayout} + deleteRow={(messageId: string) => deleteRow(messageId)} + handleMessageSelect={(messageId: string, index: number) => + handleMessageSelect(messageId, index) + } + contentWidth={contentWidth} + isPortrait={isPortrait} + /> + ); + } - function getRowInfosFromViewTokens(viewTokens: Array): Array { - return viewTokens.map( - function(viewToken) { - var inAppMessage = IterableInAppMessage.fromViewToken(viewToken) - - const impression = { - messageId: inAppMessage.messageId, - silentInbox: inAppMessage.isSilentInbox() - } as InboxImpressionRowInfo + function getRowInfosFromViewTokens(viewTokens: Array): Array { + return viewTokens.map(function (viewToken) { + var inAppMessage = IterableInAppMessage.fromViewToken(viewToken); - return impression - } - ) - } + const impression = { + messageId: inAppMessage.messageId, + silentInbox: inAppMessage.isSilentInbox(), + } as InboxImpressionRowInfo; - const inboxSessionViewabilityConfig: ViewabilityConfig = { - minimumViewTime: 500, - itemVisiblePercentThreshold: 100, - waitForInteraction: false - } + return impression; + }); + } - const inboxSessionItemsChanged = useCallback(( - (info: {viewableItems: Array, changed: Array}) => { - const rowInfos = getRowInfosFromViewTokens(info.viewableItems) + const inboxSessionViewabilityConfig: ViewabilityConfig = { + minimumViewTime: 500, + itemVisiblePercentThreshold: 100, + waitForInteraction: false, + }; - updateVisibleMessageImpressions(rowInfos) - } - ), []) + const inboxSessionItemsChanged = useCallback( + (info: { viewableItems: Array; changed: Array }) => { + const rowInfos = getRowInfosFromViewTokens(info.viewableItems); - return ( - renderRowViewModel(item, index, index === rowViewModels.length - 1)} - keyExtractor={(item: InboxRowViewModel) => item.inAppMessage.messageId} - viewabilityConfig={inboxSessionViewabilityConfig} - onViewableItemsChanged={inboxSessionItemsChanged} - onLayout={() => {flatListRef.current?.recordInteraction()}} - /> - ) -} + updateVisibleMessageImpressions(rowInfos); + }, + [], + ); + + return ( + + renderRowViewModel(item, index, index === rowViewModels.length - 1) + } + keyExtractor={(item: InboxRowViewModel) => item.inAppMessage.messageId} + viewabilityConfig={inboxSessionViewabilityConfig} + onViewableItemsChanged={inboxSessionItemsChanged} + onLayout={() => { + flatListRef.current?.recordInteraction(); + }} + /> + ); +}; -export default IterableInboxMessageList \ No newline at end of file +export default IterableInboxMessageList; diff --git a/src/IterableUtil.ts b/src/IterableUtil.ts index d9b31af30..f00bb9b78 100644 --- a/src/IterableUtil.ts +++ b/src/IterableUtil.ts @@ -1,11 +1,9 @@ -'use strict' - export default class IterableUtil { static readBoolean(dict: any, key: string): boolean { if (dict[key]) { - return dict[key] as boolean + return dict[key] as boolean; } else { - return false + return false; } } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 2d9e14ca6..000000000 --- a/src/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -'use strict' - -/** -* React Native module for Iterable. -* @module react-native-iterable-sdk -*/ - -import { Iterable, IterableCommerceItem } from './Iterable' -import IterableInAppManager from './IterableInAppManager' - -import { - IterableAction, - IterableActionContext, - IterableLogLevel, -} from './IterableAction' - -import { - IterableInAppShowResponse, - IterableInAppContent, - IterableInAppTriggerType, - IterableInAppTrigger, - IterableInAppContentType, - IterableEdgeInsets, - IterableHtmlInAppContent, - IterableInboxMetadata, - IterableInAppLocation, - IterableInAppCloseSource, - IterableInAppDeleteSource, -} from './IterableInAppClasses' - -import InboxRowViewModel from './InboxRowViewModel' -import IterableInboxCustomizations from './IterableInboxCustomizations' -import IterableInboxEmptyState from './IterableInboxEmptyState' -import IterableInboxMessageCell from './IterableInboxMessageCell' - -import IterableInAppMessage from './IterableInAppMessage' - -import useAppStateListener from './useAppStateListener' -import useDeviceOrientation from './useDeviceOrientation' -import InboxImpressionRowInfo from './InboxImpressionRowInfo' - -import IterableConfig from './IterableConfig' -import { IterableDataRegion } from './IterableDataRegion' - -export { - Iterable, - IterableCommerceItem, - IterableConfig, - IterableInAppManager, - IterableAction, - IterableActionContext, - IterableLogLevel, - IterableInAppShowResponse, - IterableInAppTriggerType, - IterableInAppTrigger, - IterableInAppContentType, - IterableEdgeInsets, - IterableHtmlInAppContent, - IterableInboxMetadata, - IterableInAppLocation, - IterableInAppMessage, - IterableInAppCloseSource, - IterableInAppDeleteSource, - IterableInboxEmptyState, - IterableInboxMessageCell, - useAppStateListener, - useDeviceOrientation, - IterableDataRegion -} -export type { - IterableInAppContent, - IterableInboxCustomizations, - InboxRowViewModel, - InboxImpressionRowInfo -} diff --git a/src/index.tsx b/src/index.tsx index 723441d12..4262fe12a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,22 +1,69 @@ -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'; - -const ReactNativeSdk = NativeModules.ReactNativeSdk - ? NativeModules.ReactNativeSdk - : new Proxy( - {}, - { - get() { - throw new Error(LINKING_ERROR); - }, - } - ); - -export function multiply(a: number, b: number): Promise { - return ReactNativeSdk.multiply(a, b); -} +/** + * React Native module for Iterable. + * @module react-native-iterable-sdk + */ + +import { Iterable, IterableCommerceItem } from './Iterable'; +import IterableInAppManager from './IterableInAppManager'; + +import { IterableAction, IterableActionContext, IterableLogLevel } from './IterableAction'; + +import { + IterableInAppShowResponse, + type IterableInAppContent, + IterableInAppTriggerType, + IterableInAppTrigger, + IterableInAppContentType, + IterableEdgeInsets, + IterableHtmlInAppContent, + IterableInboxMetadata, + IterableInAppLocation, + IterableInAppCloseSource, + IterableInAppDeleteSource, +} from './IterableInAppClasses'; + +import type { InboxRowViewModel } from './InboxRowViewModel'; +import IterableInboxCustomizations from './IterableInboxCustomizations'; +import IterableInboxEmptyState from './IterableInboxEmptyState'; +import IterableInboxMessageCell from './IterableInboxMessageCell'; + +import IterableInAppMessage from './IterableInAppMessage'; + +import useAppStateListener from './useAppStateListener'; +import useDeviceOrientation from './useDeviceOrientation'; +import InboxImpressionRowInfo from './InboxImpressionRowInfo'; + +import IterableConfig from './IterableConfig'; +import { IterableDataRegion } from './IterableDataRegion'; + +export { + Iterable, + IterableCommerceItem, + IterableConfig, + IterableInAppManager, + IterableAction, + IterableActionContext, + IterableLogLevel, + IterableInAppShowResponse, + IterableInAppTriggerType, + IterableInAppTrigger, + IterableInAppContentType, + IterableEdgeInsets, + IterableHtmlInAppContent, + IterableInboxMetadata, + IterableInAppLocation, + IterableInAppMessage, + IterableInAppCloseSource, + IterableInAppDeleteSource, + IterableInboxEmptyState, + IterableInboxMessageCell, + useAppStateListener, + useDeviceOrientation, + IterableDataRegion, +}; +export type { + IterableInAppContent, + IterableInboxCustomizations, + InboxRowViewModel, + InboxImpressionRowInfo, +}; diff --git a/src/index_OLD.tsx b/src/index_OLD.tsx new file mode 100644 index 000000000..723441d12 --- /dev/null +++ b/src/index_OLD.tsx @@ -0,0 +1,22 @@ +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'; + +const ReactNativeSdk = NativeModules.ReactNativeSdk + ? NativeModules.ReactNativeSdk + : new Proxy( + {}, + { + get() { + throw new Error(LINKING_ERROR); + }, + } + ); + +export function multiply(a: number, b: number): Promise { + return ReactNativeSdk.multiply(a, b); +} From bcec9d2403d849d17fb8dc07ba9c6fa3b615d85f Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:00:18 -0700 Subject: [PATCH 02/30] Fixed some linting issues --- src/Iterable.ts | 87 ++++++++++++++++++-------------- src/IterableInbox.tsx | 76 ++++++++++++++++++---------- src/IterableInboxMessageList.tsx | 29 +++++++---- src/IterableLogger.ts | 28 +++++----- src/IterableUtil.ts | 4 +- src/index.tsx | 6 ++- src/useDeviceOrientation.tsx | 22 ++++---- 7 files changed, 153 insertions(+), 99 deletions(-) diff --git a/src/Iterable.ts b/src/Iterable.ts index af03bdba3..18b16d3cd 100644 --- a/src/Iterable.ts +++ b/src/Iterable.ts @@ -1,4 +1,9 @@ -import { NativeModules, NativeEventEmitter, Linking, Platform } from 'react-native'; +import { + NativeModules, + NativeEventEmitter, + Linking, + Platform, +} from 'react-native'; import { IterableInAppLocation, @@ -17,7 +22,7 @@ const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); /** * Enum representing the source of IterableAction. */ -enum IterableActionSource { +export enum IterableActionSource { push = 0, appLink = 1, inApp = 2, @@ -31,7 +36,7 @@ enum AuthResponseCallback { /** * Enum representing what level of logs will Android and iOS project be printing on their consoles respectively. */ -enum IterableLogLevel { +export enum IterableLogLevel { debug = 1, info = 2, error = 3, @@ -41,7 +46,7 @@ enum IterableLogLevel { * IterableAction represents an action defined as a response to user events. * It is currently used in push notification actions (open push & action buttons). */ -class IterableAction { +export class IterableAction { type: string; data?: string; userInput?: string; @@ -57,7 +62,7 @@ class IterableAction { } } -class IterableActionContext { +export class IterableActionContext { action: IterableAction; source: IterableActionSource; @@ -73,7 +78,7 @@ class IterableActionContext { } } -class IterableAttributionInfo { +export class IterableAttributionInfo { campaignId: number; templateId: number; messageId: string; @@ -85,7 +90,7 @@ class IterableAttributionInfo { } } -class IterableCommerceItem { +export class IterableCommerceItem { id: string; name: string; price: number; @@ -107,7 +112,7 @@ class IterableCommerceItem { url?: string, imageUrl?: string, categories?: Array, - dataFields?: any | undefined, + dataFields?: any | undefined ) { this.id = id; this.name = name; @@ -122,7 +127,7 @@ class IterableCommerceItem { } } -enum EventName { +export enum EventName { handleUrlCalled = 'handleUrlCalled', handleCustomActionCalled = 'handleCustomActionCalled', handleInAppCalled = 'handleInAppCalled', @@ -132,7 +137,7 @@ enum EventName { handleAuthFailureCalled = 'handleAuthFailureCalled', } -class Iterable { +export class Iterable { static inAppManager = new IterableInAppManager(); static logger: IterableLogger; @@ -155,7 +160,7 @@ class Iterable { static initialize( apiKey: string, - config: IterableConfig = new IterableConfig(), + config: IterableConfig = new IterableConfig() ): Promise { Iterable.savedConfig = config; @@ -176,7 +181,7 @@ class Iterable { static initialize2( apiKey: string, config: IterableConfig = new IterableConfig(), - apiEndPoint: string, + apiEndPoint: string ): Promise { Iterable.savedConfig = config; @@ -187,7 +192,12 @@ class Iterable { this.setupEventHandlers(); const version = this.getVersionFromPackageJson(); - return RNIterableAPI.initialize2WithApiKey(apiKey, config.toDict(), version, apiEndPoint); + return RNIterableAPI.initialize2WithApiKey( + apiKey, + config.toDict(), + version, + apiEndPoint + ); } /** @@ -343,7 +353,7 @@ class Iterable { return new IterableAttributionInfo( dict.campaignId as number, dict.templateId as number, - dict.messageId as string, + dict.messageId as string ); } else { return undefined; @@ -385,7 +395,7 @@ class Iterable { templateId: number, messageId: string | undefined, appAlreadyRunning: boolean, - dataFields: any | undefined, + dataFields: any | undefined ) { Iterable.logger.log('trackPushOpenWithCampaignId'); @@ -394,7 +404,7 @@ class Iterable { templateId, messageId, appAlreadyRunning, - dataFields, + dataFields ); } @@ -441,7 +451,7 @@ class Iterable { static trackPurchase( total: number, items: Array, - dataFields: any | undefined, + dataFields: any | undefined ) { Iterable.logger.log('trackPurchase'); @@ -457,7 +467,10 @@ class Iterable { * @param {IterableInAppLocation} location the location of the in-app message (an IterableInAppLocation enum) */ - static trackInAppOpen(message: IterableInAppMessage, location: IterableInAppLocation) { + static trackInAppOpen( + message: IterableInAppMessage, + location: IterableInAppLocation + ) { Iterable.logger.log('trackInAppOpen'); RNIterableAPI.trackInAppOpen(message.messageId, location); @@ -477,7 +490,7 @@ class Iterable { static trackInAppClick( message: IterableInAppMessage, location: IterableInAppLocation, - clickedUrl: string, + clickedUrl: string ) { Iterable.logger.log('trackInAppClick'); @@ -499,11 +512,16 @@ class Iterable { message: IterableInAppMessage, location: IterableInAppLocation, source: IterableInAppCloseSource, - clickedUrl?: string | undefined, + clickedUrl?: string | undefined ) { Iterable.logger.log('trackInAppClose'); - RNIterableAPI.trackInAppClose(message.messageId, location, source, clickedUrl); + RNIterableAPI.trackInAppClose( + message.messageId, + location, + source, + clickedUrl + ); } /** @@ -520,7 +538,7 @@ class Iterable { static inAppConsume( message: IterableInAppMessage, location: IterableInAppLocation, - source: IterableInAppDeleteSource, + source: IterableInAppDeleteSource ) { Iterable.logger.log('inAppConsume'); @@ -616,7 +634,7 @@ class Iterable { unsubscribedMessageTypeIds: Array | undefined, subscribedMessageTypeIds: Array | undefined, campaignId: number, - templateId: number, + templateId: number ) { Iterable.logger.log('updateSubscriptions'); @@ -626,7 +644,7 @@ class Iterable { unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, - templateId, + templateId ); } @@ -680,14 +698,18 @@ 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 AuthResponse()) { - RNIterableAPI.passAlongAuthToken((promiseResult as AuthResponse).authToken); + RNIterableAPI.passAlongAuthToken( + (promiseResult as AuthResponse).authToken + ); setTimeout(() => { if (authResponseCallback === AuthResponseCallback.SUCCESS) { if ((promiseResult as AuthResponse).successCallback) { (promiseResult as AuthResponse).successCallback!(); } - } else if (authResponseCallback === AuthResponseCallback.FAILURE) { + } else if ( + authResponseCallback === AuthResponseCallback.FAILURE + ) { if ((promiseResult as AuthResponse).failureCallback) { (promiseResult as AuthResponse).failureCallback!(); } @@ -700,7 +722,7 @@ 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.' ); } }) @@ -737,13 +759,4 @@ class Iterable { } } -export { - Iterable, - IterableAction, - IterableActionContext, - IterableAttributionInfo, - IterableCommerceItem, - EventName, - IterableActionSource, - IterableLogLevel, -}; +export default Iterable; diff --git a/src/IterableInbox.tsx b/src/IterableInbox.tsx index c883a34f3..4b50f1362 100644 --- a/src/IterableInbox.tsx +++ b/src/IterableInbox.tsx @@ -1,31 +1,32 @@ -import React, { useState, useEffect } from 'react'; +import { useIsFocused } from '@react-navigation/native'; +import { useEffect, useState } from 'react'; import { - View, - Text, - StyleSheet, Animated, - NativeModules, NativeEventEmitter, + NativeModules, Platform, + StyleSheet, + Text, + View, } from 'react-native'; - import { SafeAreaView } from 'react-native-safe-area-context'; -import { IterableInAppDeleteSource, IterableInAppLocation } from './IterableInAppClasses'; +import { + IterableInAppDeleteSource, + IterableInAppLocation, +} from './IterableInAppClasses'; import { Iterable } from './Iterable'; -import IterableInboxEmptyState from './IterableInboxEmptyState'; import InboxImpressionRowInfo from './InboxImpressionRowInfo'; -import useDeviceOrientation from './useDeviceOrientation'; -import useAppStateListener from './useAppStateListener'; +import InboxRowViewModel from './InboxRowViewModel'; import IterableInboxCustomizations from './IterableInboxCustomizations'; -import IterableInboxMessageList from './IterableInboxMessageList'; -import IterableInboxMessageDisplay from './IterableInboxMessageDisplay'; import IterableInboxDataModel from './IterableInboxDataModel'; -import InboxRowViewModel from './InboxRowViewModel'; - -import { useIsFocused } from '@react-navigation/native'; +import IterableInboxEmptyState from './IterableInboxEmptyState'; +import IterableInboxMessageDisplay from './IterableInboxMessageDisplay'; +import IterableInboxMessageList from './IterableInboxMessageList'; +import useAppStateListener from './useAppStateListener'; +import useDeviceOrientation from './useDeviceOrientation'; const RNIterableAPI = NativeModules.RNIterableAPI; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); @@ -58,7 +59,8 @@ const IterableInbox = ({ const appState = useAppStateListener(); const isFocused = useIsFocused(); - const [selectedRowViewModelIdx, setSelectedRowViewModelIdx] = useState(0); + const [selectedRowViewModelIdx, setSelectedRowViewModelIdx] = + useState(0); const [rowViewModels, setRowViewModels] = useState([]); const [loading, setLoading] = useState(true); const [animatedValue] = useState(new Animated.Value(0)); @@ -108,7 +110,8 @@ const IterableInbox = ({ let { loadingScreen, container, headline, messageListContainer } = styles; - const navTitleHeight = headline.height + headline.paddingTop + headline.paddingBottom; + const navTitleHeight = + headline.height + headline.paddingTop + headline.paddingBottom; headline = { ...headline, height: Platform.OS === 'android' ? 70 : 60 }; headline = !isPortrait ? { ...headline, paddingLeft: 70 } : headline; @@ -188,7 +191,11 @@ const IterableInbox = ({ return inboxDataModel.getHtmlContentForMessageId(id); } - function handleMessageSelect(id: string, index: number, rowViewModels: InboxRowViewModel[]) { + function handleMessageSelect( + id: string, + index: number, + rowViewModels: InboxRowViewModel[] + ) { let newRowViewModels = rowViewModels.map((rowViewModel) => { return rowViewModel.inAppMessage.messageId === id ? { ...rowViewModel, read: true } @@ -198,13 +205,19 @@ const IterableInbox = ({ inboxDataModel.setMessageAsRead(id); setSelectedRowViewModelIdx(index); - Iterable.trackInAppOpen(rowViewModels[index].inAppMessage, IterableInAppLocation.inbox); + Iterable.trackInAppOpen( + rowViewModels[index].inAppMessage, + IterableInAppLocation.inbox + ); slideLeft(); } function deleteRow(messageId: string) { - inboxDataModel.deleteItemById(messageId, IterableInAppDeleteSource.inboxSwipe); + inboxDataModel.deleteItemById( + messageId, + IterableInAppDeleteSource.inboxSwipe + ); fetchInboxMessages(); } @@ -217,17 +230,24 @@ const IterableInbox = ({ setIsMessageDisplay(false); } - function updateVisibleMessageImpressions(messageImpressions: InboxImpressionRowInfo[]) { + function updateVisibleMessageImpressions( + messageImpressions: InboxImpressionRowInfo[] + ) { setVisibleMessageImpressions(messageImpressions); } - function showMessageDisplay(rowViewModelList: InboxRowViewModel[], index: number) { + function showMessageDisplay( + rowViewModelList: InboxRowViewModel[], + index: number + ) { const selectedRowViewModel = rowViewModelList[index]; return selectedRowViewModel ? ( returnToInbox(callback)} deleteRow={(messageId: string) => deleteRow(messageId)} contentWidth={width} @@ -241,7 +261,9 @@ const IterableInbox = ({ {showNavTitle ? ( - {customizations?.navTitle ? customizations?.navTitle : defaultInboxTitle} + {customizations?.navTitle + ? customizations?.navTitle + : defaultInboxTitle} ) : null} {rowViewModels.length ? ( @@ -254,9 +276,9 @@ const IterableInbox = ({ handleMessageSelect={(messageId: string, index: number) => handleMessageSelect(messageId, index, rowViewModels) } - updateVisibleMessageImpressions={(messageImpressions: InboxImpressionRowInfo[]) => - updateVisibleMessageImpressions(messageImpressions) - } + updateVisibleMessageImpressions={( + messageImpressions: InboxImpressionRowInfo[] + ) => updateVisibleMessageImpressions(messageImpressions)} contentWidth={width} isPortrait={isPortrait} /> diff --git a/src/IterableInboxMessageList.tsx b/src/IterableInboxMessageList.tsx index d4ebb3327..800d62b24 100644 --- a/src/IterableInboxMessageList.tsx +++ b/src/IterableInboxMessageList.tsx @@ -1,7 +1,7 @@ -import React, { useCallback, useRef, useState } from 'react'; -import { ViewabilityConfig, ViewToken, FlatList } from 'react-native'; +import { useCallback, useRef, useState } from 'react'; +import { type ViewabilityConfig, type ViewToken, FlatList } from 'react-native'; -import InboxImpressionRowInfo from './InboxImpressionRowInfo'; +import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; import IterableInboxMessageCell from './IterableInboxMessageCell'; import InboxRowViewModel from './InboxRowViewModel'; import IterableInboxCustomizations from './IterableInboxCustomizations'; @@ -9,6 +9,7 @@ import IterableInboxCustomizations from './IterableInboxCustomizations'; import IterableInAppMessage from './IterableInAppMessage'; import IterableInboxDataModel from './IterableInboxDataModel'; +// TODO: Comment type MessageListProps = { dataModel: IterableInboxDataModel; rowViewModels: InboxRowViewModel[]; @@ -35,7 +36,11 @@ const IterableInboxMessageList = ({ const [swiping, setSwiping] = useState(false); const flatListRef = useRef(null); - function renderRowViewModel(rowViewModel: InboxRowViewModel, index: number, last: boolean) { + function renderRowViewModel( + rowViewModel: InboxRowViewModel, + index: number, + last: boolean + ) { return ( ): Array { + function getRowInfosFromViewTokens( + viewTokens: Array + ): Array { return viewTokens.map(function (viewToken) { var inAppMessage = IterableInAppMessage.fromViewToken(viewToken); @@ -81,7 +88,7 @@ const IterableInboxMessageList = ({ updateVisibleMessageImpressions(rowInfos); }, - [], + [] ); return ( @@ -89,9 +96,13 @@ const IterableInboxMessageList = ({ ref={flatListRef} scrollEnabled={!swiping} data={rowViewModels} - renderItem={({ item, index }: { item: InboxRowViewModel; index: number }) => - renderRowViewModel(item, index, index === rowViewModels.length - 1) - } + renderItem={({ + item, + index, + }: { + item: InboxRowViewModel; + index: number; + }) => renderRowViewModel(item, index, index === rowViewModels.length - 1)} keyExtractor={(item: InboxRowViewModel) => item.inAppMessage.messageId} viewabilityConfig={inboxSessionViewabilityConfig} onViewableItemsChanged={inboxSessionItemsChanged} diff --git a/src/IterableLogger.ts b/src/IterableLogger.ts index e418e8745..a6954dee3 100644 --- a/src/IterableLogger.ts +++ b/src/IterableLogger.ts @@ -1,21 +1,21 @@ -import IterableConfig from "./IterableConfig" +import IterableConfig from './IterableConfig'; -class IterableLogger { - readonly config: IterableConfig +export class IterableLogger { + readonly config: IterableConfig; - constructor(config: IterableConfig) { - this.config = config - } + constructor(config: IterableConfig) { + this.config = config; + } - log(message: String) { - // default to `true` in the case of unit testing where `Iterable` is not initialized - // which is most likely in a debug environment anyways - var loggingEnabled = this.config.logReactNativeSdkCalls ?? true + log(message: String) { + // default to `true` in the case of unit testing where `Iterable` is not initialized + // which is most likely in a debug environment anyways + const loggingEnabled = this.config.logReactNativeSdkCalls ?? true; - if (loggingEnabled) { - console.log(message) - } + if (loggingEnabled) { + console.log(message); } + } } -export { IterableLogger } \ No newline at end of file +export default IterableLogger; diff --git a/src/IterableUtil.ts b/src/IterableUtil.ts index f00bb9b78..b23d954b2 100644 --- a/src/IterableUtil.ts +++ b/src/IterableUtil.ts @@ -1,4 +1,4 @@ -export default class IterableUtil { +export class IterableUtil { static readBoolean(dict: any, key: string): boolean { if (dict[key]) { return dict[key] as boolean; @@ -7,3 +7,5 @@ export default class IterableUtil { } } } + +export default IterableUtil; diff --git a/src/index.tsx b/src/index.tsx index 4262fe12a..b73001bce 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,11 @@ import { Iterable, IterableCommerceItem } from './Iterable'; import IterableInAppManager from './IterableInAppManager'; -import { IterableAction, IterableActionContext, IterableLogLevel } from './IterableAction'; +import { + IterableAction, + IterableActionContext, + IterableLogLevel, +} from './IterableAction'; import { IterableInAppShowResponse, diff --git a/src/useDeviceOrientation.tsx b/src/useDeviceOrientation.tsx index 1f453ffb2..1cfdd74aa 100644 --- a/src/useDeviceOrientation.tsx +++ b/src/useDeviceOrientation.tsx @@ -1,16 +1,18 @@ -import { useEffect, useState } from 'react' -import { useWindowDimensions } from 'react-native' +import { useEffect, useState } from 'react'; +import { useWindowDimensions } from 'react-native'; -function useDeviceOrientation() { - const { height, width } = useWindowDimensions() +export function useDeviceOrientation() { + const { height, width } = useWindowDimensions(); - const [isPortrait, setIsPortrait] = useState(height >= width) + const [isPortrait, setIsPortrait] = useState(height >= width); - useEffect(() => { - setIsPortrait(height >= width) - }, [width]) + useEffect(() => { + setIsPortrait(height >= width); + // TODO: why is height not included in the dependency array? + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [width]); - return { height, width, isPortrait } + return { height, width, isPortrait }; } -export default useDeviceOrientation \ No newline at end of file +export default useDeviceOrientation; From 658448a7424136bc47d4ee82c0e4b80684719cad Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:00:36 -0700 Subject: [PATCH 03/30] Changed format for updated react native --- src/useAppStateListener.ts | 45 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/useAppStateListener.ts b/src/useAppStateListener.ts index ad2f7511c..9a6f2f13f 100644 --- a/src/useAppStateListener.ts +++ b/src/useAppStateListener.ts @@ -1,29 +1,28 @@ -import { useEffect, useRef, useState } from 'react' -import { AppState } from 'react-native' +import { useEffect, useRef, useState } from 'react'; +import { AppState } from 'react-native'; -function useAppStateListener() { - const appStateEventName = "change" - const appState = useRef(AppState.currentState) - const [appStateVisibility, setAppStateVisibility] = useState(appState.current) +export function useAppStateListener() { + const appStateEventName = 'change'; + const appState = useRef(AppState.currentState); + const [appStateVisibility, setAppStateVisibility] = useState( + appState.current + ); - useEffect(() => { - AppState.addEventListener( - appStateEventName, - (nextAppState) => { - appState.current = nextAppState; - setAppStateVisibility(appState.current) - } - ) + useEffect(() => { + const listener = AppState.addEventListener( + appStateEventName, + (nextAppState) => { + appState.current = nextAppState; + setAppStateVisibility(appState.current); + } + ); - return () => { - AppState.removeEventListener( - appStateEventName, - () => { } - ) - } - }, []) + return () => { + listener.remove(); + }; + }, []); - return appStateVisibility + return appStateVisibility; } -export default useAppStateListener \ No newline at end of file +export default useAppStateListener; From 99e162976823c1520cd80504ac66348df011d065 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:04:47 -0700 Subject: [PATCH 04/30] Refactor IterableAction and IterableActionContext This commit refactors the `IterableAction` and `IterableActionContext` classes. It moves them from the `src` folder to the `ts` folder. Additionally, it removes the unused `use strict` directive and renames the `index` file. The changes aim to improve the code organization and maintainability. --- src/IterableAction.ts | 79 ++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/src/IterableAction.ts b/src/IterableAction.ts index d9b15c064..c00daadbe 100644 --- a/src/IterableAction.ts +++ b/src/IterableAction.ts @@ -1,50 +1,45 @@ -class IterableAction { - type: string - data?: string - userInput?: string - - constructor(type: string, data?: string, userInput?: string) { - this.type = type - this.data = data - this.userInput = userInput - } - - static fromDict(dict: any): IterableAction { - return new IterableAction(dict["type"], dict["data"], dict["userInput"]) - } +export class IterableAction { + type: string; + data?: string; + userInput?: string; + + constructor(type: string, data?: string, userInput?: string) { + this.type = type; + this.data = data; + this.userInput = userInput; } - class IterableActionContext { - action: IterableAction - source: IterableActionSource - - constructor(action: IterableAction, source: IterableActionSource) { - this.action = action - this.source = source - } - - static fromDict(dict: any): IterableActionContext { - const action = IterableAction.fromDict(dict["action"]) - const source = dict["source"] as IterableActionSource - return new IterableActionContext(action, source) - } + static fromDict(dict: any): IterableAction { + return new IterableAction(dict.type, dict.data, dict.userInput); } +} + +export class IterableActionContext { + action: IterableAction; + source: IterableActionSource; - enum IterableActionSource { - push = 0, - appLink = 1, - inApp = 2 + constructor(action: IterableAction, source: IterableActionSource) { + this.action = action; + this.source = source; } - enum IterableLogLevel { - debug = 1, - info = 2, - error = 3 + static fromDict(dict: any): IterableActionContext { + const action = IterableAction.fromDict(dict.action); + const source = dict.source as IterableActionSource; + return new IterableActionContext(action, source); } +} + +export enum IterableActionSource { + push = 0, + appLink = 1, + inApp = 2, +} + +export enum IterableLogLevel { + debug = 1, + info = 2, + error = 3, +} - export { - IterableAction, - IterableActionContext, - IterableActionSource, - IterableLogLevel - } \ No newline at end of file +export default IterableAction; From 16c23af209d5fd634f23ccfc6aabebca0865a7b6 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:04:55 -0700 Subject: [PATCH 05/30] Refactor imports in IterableInbox.tsx --- src/IterableInbox.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/IterableInbox.tsx b/src/IterableInbox.tsx index 4b50f1362..cdbc39493 100644 --- a/src/IterableInbox.tsx +++ b/src/IterableInbox.tsx @@ -16,10 +16,9 @@ import { IterableInAppLocation, } from './IterableInAppClasses'; +import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; +import type { InboxRowViewModel } from './InboxRowViewModel'; import { Iterable } from './Iterable'; - -import InboxImpressionRowInfo from './InboxImpressionRowInfo'; -import InboxRowViewModel from './InboxRowViewModel'; import IterableInboxCustomizations from './IterableInboxCustomizations'; import IterableInboxDataModel from './IterableInboxDataModel'; import IterableInboxEmptyState from './IterableInboxEmptyState'; From b89281db53d52300a4bf9e864f56e7f885466c2d Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:06:25 -0700 Subject: [PATCH 06/30] Refactor imports in IterableConfig.ts and added todos --- src/IterableConfig.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/IterableConfig.ts b/src/IterableConfig.ts index f003fd7b1..b268fe68a 100644 --- a/src/IterableConfig.ts +++ b/src/IterableConfig.ts @@ -1,4 +1,8 @@ -import { IterableAction, IterableActionContext, IterableLogLevel } from './IterableAction'; +import { + IterableAction, + IterableActionContext, + IterableLogLevel, +} from './IterableAction'; import { IterableInAppShowResponse } from './IterableInAppClasses'; @@ -6,6 +10,7 @@ import IterableInAppMessage from './IterableInAppMessage'; import { IterableDataRegion } from './IterableDataRegion'; +// TODO: Add description type AuthCallBack = () => void; /** @@ -13,7 +18,7 @@ type AuthCallBack = () => void; * An IterableConfig object is passed into the static initialize method on the Iterable class when initializing the SDK. */ -class IterableConfig { +export class IterableConfig { /** * The name of the Iterable push integration that will send push notifications to your app. * Defaults to your app's application ID or bundle ID for iOS. @@ -49,7 +54,10 @@ class IterableConfig { /** * A function expression used to handle `action://` URLs for in-app buttons and links. */ - customActionHandler?: (action: IterableAction, context: IterableActionContext) => boolean; + customActionHandler?: ( + action: IterableAction, + context: IterableActionContext + ) => boolean; /** * Implement this callback to override default in-app behavior. @@ -134,7 +142,8 @@ class IterableConfig { logLevel: this.logLevel, expiringAuthTokenRefreshPeriod: this.expiringAuthTokenRefreshPeriod, allowedProtocols: this.allowedProtocols, - androidSdkUseInMemoryStorageForInApps: this.androidSdkUseInMemoryStorageForInApps, + androidSdkUseInMemoryStorageForInApps: + this.androidSdkUseInMemoryStorageForInApps, useInMemoryStorageForInApps: this.useInMemoryStorageForInApps, dataRegion: this.dataRegion, encryptionEnforced: this.encryptionEnforced, @@ -142,6 +151,7 @@ class IterableConfig { } } +// TODO: Add comments and descriptions export class AuthResponse { authToken?: string = ''; successCallback?: AuthCallBack; From 6df54f5a70c9cd04364c521403960f371d825cd3 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:07:26 -0700 Subject: [PATCH 07/30] Refactor IterableDataRegion and export as default --- src/IterableDataRegion.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/IterableDataRegion.ts b/src/IterableDataRegion.ts index 6f4aed1ff..be7515fb9 100644 --- a/src/IterableDataRegion.ts +++ b/src/IterableDataRegion.ts @@ -1,8 +1,7 @@ -enum IterableDataRegion { - US = 0, - EU = 1 - } +// TODO: Add a description +export enum IterableDataRegion { + US = 0, + EU = 1, +} -export { - IterableDataRegion -} \ No newline at end of file +export default IterableDataRegion; From fad1c47e7baca43369f134095364cdb2d003bcfc Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:12:16 -0700 Subject: [PATCH 08/30] Refactor IterableInAppClasses and export as default --- src/IterableInAppClasses.ts | 70 ++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/IterableInAppClasses.ts b/src/IterableInAppClasses.ts index 4e02a8f53..83a30cbb8 100644 --- a/src/IterableInAppClasses.ts +++ b/src/IterableInAppClasses.ts @@ -1,23 +1,27 @@ /** - * `show` to show the in-app otherwise `skip` to skip. + * TODO: Break this file into multiple files */ -enum IterableInAppShowResponse { + +// TODO: Add description +export enum IterableInAppShowResponse { + /** Show in-app */ show = 0, + /** Skip showing in-app */ skip = 1, } -/** - * `immediate` will try to display the in-app automatically immediately - * `event` is used for Push to in-app - * `never` will not display the in-app automatically via the SDK - */ -enum IterableInAppTriggerType { +// TODO: Add description +export enum IterableInAppTriggerType { + /** Tries to display the in-app automatically immediately */ immediate = 0, + /** Used for Push to in-app */ event = 1, + /** Do not display the in-app automatically via the SDK */ never = 2, } -class IterableInAppTrigger { +// TODO: Add description +export class IterableInAppTrigger { type: IterableInAppTriggerType; constructor(type: IterableInAppTriggerType) { @@ -25,35 +29,42 @@ class IterableInAppTrigger { } static fromDict(dict: any): IterableInAppTrigger { - const type = dict.type as IterableInAppTriggerType | IterableInAppTriggerType.immediate; + const type = dict.type as + | IterableInAppTriggerType + | IterableInAppTriggerType.immediate; return new IterableInAppTrigger(type); } } -enum IterableInAppContentType { +// TODO: Add description +export enum IterableInAppContentType { html = 0, alert = 1, banner = 2, } -enum IterableInAppLocation { +// TODO: Add description +export enum IterableInAppLocation { inApp = 0, inbox = 1, } -enum IterableInAppCloseSource { +// TODO: Add description +export enum IterableInAppCloseSource { back = 0, link = 1, unknown = 100, } -enum IterableInAppDeleteSource { +// TODO: Add description +export enum IterableInAppDeleteSource { inboxSwipe = 0, deleteButton = 1, unknown = 100, } -class IterableEdgeInsets { +// TODO: Add description +export class IterableEdgeInsets { top: number; left: number; bottom: number; @@ -71,16 +82,18 @@ class IterableEdgeInsets { dict.top as number, dict.left as number, dict.bottom as number, - dict.right as number, + dict.right as number ); } } +// TODO: Add description export interface IterableInAppContent { type: IterableInAppContentType; } -class IterableHtmlInAppContent implements IterableInAppContent { +// TODO: Add description +export class IterableHtmlInAppContent implements IterableInAppContent { type: IterableInAppContentType = IterableInAppContentType.html; edgeInsets: IterableEdgeInsets; html: string; @@ -93,17 +106,21 @@ class IterableHtmlInAppContent implements IterableInAppContent { static fromDict(dict: any): IterableHtmlInAppContent { return new IterableHtmlInAppContent( IterableEdgeInsets.fromDict(dict.edgeInsets), - dict.html as string, + dict.html as string ); } } -class IterableInboxMetadata { +export class IterableInboxMetadata { title?: string; subtitle?: string; icon?: string; - constructor(title: string | undefined, subtitle: string | undefined, icon: string | undefined) { + constructor( + title: string | undefined, + subtitle: string | undefined, + icon: string | undefined + ) { this.title = title; this.subtitle = subtitle; this.icon = icon; @@ -113,16 +130,3 @@ class IterableInboxMetadata { return new IterableInboxMetadata(dict.title, dict.subtitle, dict.icon); } } - -export { - IterableInAppShowResponse, - IterableInAppTriggerType, - IterableInAppTrigger, - IterableInAppContentType, - IterableEdgeInsets, - IterableHtmlInAppContent, - IterableInboxMetadata, - IterableInAppLocation, - IterableInAppCloseSource, - IterableInAppDeleteSource, -}; From f2e05b9d25ba7cd8ff113397d415a0dda30c2ee0 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:14:19 -0700 Subject: [PATCH 09/30] Refactor IterableInAppManager and IterableInAppMessage classes --- src/IterableInAppManager.ts | 19 ++++++++++++------- src/IterableInAppMessage.ts | 16 +++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/IterableInAppManager.ts b/src/IterableInAppManager.ts index ef35e3aba..de2c0950b 100644 --- a/src/IterableInAppManager.ts +++ b/src/IterableInAppManager.ts @@ -1,21 +1,21 @@ import { NativeModules } from 'react-native'; +import { Iterable } from './Iterable'; import { IterableHtmlInAppContent, - IterableInAppLocation, IterableInAppDeleteSource, + IterableInAppLocation, } from './IterableInAppClasses'; -import { Iterable } from './Iterable'; - import IterableInAppMessage from './IterableInAppMessage'; +// TODO: Create a loader for this const RNIterableAPI = NativeModules.RNIterableAPI; /** * IterableInAppManager is set up as the inAppManager property of an Iterable instance. */ -class IterableInAppManager { +export class IterableInAppManager { /** * This method returns the current user's list of in-app messages stored in the local queue in the form of a promise. * Use `then` keyword to get the array of IterableInAppMessage objects. @@ -56,7 +56,10 @@ class IterableInAppManager { * @param {boolean} consume Whether or not the message should be consumed from the user's message queue after being shown. This should be defaulted to true. */ - showMessage(message: IterableInAppMessage, consume: boolean): Promise { + showMessage( + message: IterableInAppMessage, + consume: boolean + ): Promise { Iterable.logger.log('InAppManager.show'); return RNIterableAPI.showMessage(message.messageId, consume); @@ -73,7 +76,7 @@ class IterableInAppManager { removeMessage( message: IterableInAppMessage, location: IterableInAppLocation, - source: IterableInAppDeleteSource, + source: IterableInAppDeleteSource ): void { Iterable.logger.log('InAppManager.remove'); @@ -99,7 +102,9 @@ class IterableInAppManager { * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) */ - getHtmlContentForMessage(message: IterableInAppMessage): Promise { + getHtmlContentForMessage( + message: IterableInAppMessage + ): Promise { Iterable.logger.log('InAppManager.getHtmlContentForMessage'); return RNIterableAPI.getHtmlInAppContentForMessage(message.messageId); diff --git a/src/IterableInAppMessage.ts b/src/IterableInAppMessage.ts index a86ce98a6..fb1ebce20 100644 --- a/src/IterableInAppMessage.ts +++ b/src/IterableInAppMessage.ts @@ -1,3 +1,5 @@ +import { type ViewToken } from 'react-native'; + import IterableUtil from './IterableUtil'; import { @@ -6,12 +8,10 @@ import { IterableInboxMetadata, } from './IterableInAppClasses'; -import { ViewToken } from 'react-native'; - /** * Iterable in-app message */ -class IterableInAppMessage { +export class IterableInAppMessage { /** * the ID for the in-app message */ @@ -72,7 +72,7 @@ class IterableInAppMessage { inboxMetadata: IterableInboxMetadata | undefined, customPayload: any | undefined, read: boolean, - priorityLevel: number, + priorityLevel: number ) { this.campaignId = campaignId; this.messageId = messageId; @@ -99,12 +99,14 @@ class IterableInAppMessage { inAppMessage.inboxMetadata, inAppMessage.customPayload, inAppMessage.read, - inAppMessage.priorityLevel, + inAppMessage.priorityLevel ); } isSilentInbox(): boolean { - return this.saveToInbox && this.trigger.type == IterableInAppTriggerType.never; + return ( + this.saveToInbox && this.trigger.type == IterableInAppTriggerType.never + ); } static fromDict(dict: any): IterableInAppMessage { @@ -144,7 +146,7 @@ class IterableInAppMessage { inboxMetadata, customPayload, read, - priorityLevel, + priorityLevel ); } } From b1679e2569796ff18fbf87b817f5a1b7741fb019 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:17:35 -0700 Subject: [PATCH 10/30] Refactor IterableInboxCustomizations and add missing type description --- src/IterableInboxCustomizations.ts | 121 ++++++++++++++--------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/src/IterableInboxCustomizations.ts b/src/IterableInboxCustomizations.ts index 65593b3fa..74ee52c35 100644 --- a/src/IterableInboxCustomizations.ts +++ b/src/IterableInboxCustomizations.ts @@ -1,70 +1,69 @@ -type IterableInboxCustomizations = { - navTitle?: string, - noMessagesTitle?: string, - noMessagesBody?: string, +// TODO: Add description +export type IterableInboxCustomizations = { + navTitle?: string; + noMessagesTitle?: string; + noMessagesBody?: string; - unreadIndicatorContainer?: { - flexDirection?: string, - justifyContent?: string - }, + unreadIndicatorContainer?: { + flexDirection?: string; + justifyContent?: string; + }; - unreadIndicator?: { - width?: number, - height?: number, - borderRadius?: number, - backgroundColor?: string, - marginLeft?: number, - marginRight?: number, - marginTop?: number - }, + unreadIndicator?: { + width?: number; + height?: number; + borderRadius?: number; + backgroundColor?: string; + marginLeft?: number; + marginRight?: number; + marginTop?: number; + }; - unreadMessageThumbnailContainer?: { - paddingLeft?: number, - flexDirection?: string, - justifyContent?: string - }, + unreadMessageThumbnailContainer?: { + paddingLeft?: number; + flexDirection?: string; + justifyContent?: string; + }; - readMessageThumbnailContainer?: { - paddingLeft?: number, - flexDirection?: string, - justifyContent?: string - }, + readMessageThumbnailContainer?: { + paddingLeft?: number; + flexDirection?: string; + justifyContent?: string; + }; - messageContainer?: { - paddingLeft?: number, - width?: string, - flexDirection?: string, - justifyContent?: string - }, + messageContainer?: { + paddingLeft?: number; + width?: string; + flexDirection?: string; + justifyContent?: string; + }; - title?: { - fontSize?: number, - paddingBottom?: number - }, + title?: { + fontSize?: number; + paddingBottom?: number; + }; - body?: { - fontSize?: number, - color?: string, - width?: string, - flexWrap?: string, - paddingBottom?: number - }, + body?: { + fontSize?: number; + color?: string; + width?: string; + flexWrap?: string; + paddingBottom?: number; + }; - createdAt?: { - fontSize?: number, - color?: string - }, + createdAt?: { + fontSize?: number; + color?: string; + }; - messageRow?: { - flexDirection?: string, - backgroundColor?: string, - paddingTop?: number, - paddingBottom?: number, - height?: number, - borderStyle?: string, - borderColor?: string, - borderTopWidth?: number - } -} - -export default IterableInboxCustomizations \ No newline at end of file + messageRow?: { + flexDirection?: string; + backgroundColor?: string; + paddingTop?: number; + paddingBottom?: number; + height?: number; + borderStyle?: string; + borderColor?: string; + borderTopWidth?: number; + }; +}; From 54a35a34536f37f03b71db34de3402f92915474f Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:20:01 -0700 Subject: [PATCH 11/30] Refactor IterableInboxDataModel and improve code readability --- src/IterableInboxDataModel.ts | 54 +++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/IterableInboxDataModel.ts b/src/IterableInboxDataModel.ts index f00be6dd8..f9eb48ae4 100644 --- a/src/IterableInboxDataModel.ts +++ b/src/IterableInboxDataModel.ts @@ -1,29 +1,35 @@ import { NativeModules } from 'react-native'; import { - IterableInAppLocation, - IterableInAppDeleteSource, IterableHtmlInAppContent, + IterableInAppDeleteSource, + IterableInAppLocation, } from './IterableInAppClasses'; import { Iterable } from './Iterable'; -import InboxImpressionRowInfo from './InboxImpressionRowInfo'; -import InboxRowViewModel from './InboxRowViewModel'; +import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; +import type { InboxRowViewModel } from './InboxRowViewModel'; import IterableInAppMessage from './IterableInAppMessage'; const RNIterableAPI = NativeModules.RNIterableAPI; -class IterableInboxDataModel { +export class IterableInboxDataModel { filterFn?: (message: IterableInAppMessage) => boolean; - comparatorFn?: (message1: IterableInAppMessage, message2: IterableInAppMessage) => number; + comparatorFn?: ( + message1: IterableInAppMessage, + message2: IterableInAppMessage + ) => number; dateMapperFn?: (message: IterableInAppMessage) => string | undefined; constructor() {} 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; @@ -43,11 +49,15 @@ class IterableInboxDataModel { } getHtmlContentForMessageId(id: string): Promise { - Iterable.logger.log('IterableInboxDataModel.getHtmlContentForItem messageId: ' + id); + Iterable.logger.log( + 'IterableInboxDataModel.getHtmlContentForItem messageId: ' + id + ); - return RNIterableAPI.getHtmlInAppContentForMessage(id).then((content: any) => { - return IterableHtmlInAppContent.fromDict(content); - }); + return RNIterableAPI.getHtmlInAppContentForMessage(id).then( + (content: any) => { + return IterableHtmlInAppContent.fromDict(content); + } + ); } setMessageAsRead(id: string) { @@ -69,7 +79,7 @@ class IterableInboxDataModel { }, () => { return []; - }, + } ); } @@ -92,7 +102,7 @@ class IterableInboxDataModel { private static sortByMostRecent = ( message1: IterableInAppMessage, - message2: IterableInAppMessage, + message2: IterableInAppMessage ) => { let createdAt1 = message1.createdAt ?? new Date(0); let createdAt2 = message2.createdAt ?? new Date(0); @@ -121,11 +131,17 @@ class IterableInboxDataModel { return defaultDateString; } - private processMessages(messages: Array): Array { - return this.sortAndFilter(messages).map(IterableInboxDataModel.getInboxRowViewModelForMessage); + private processMessages( + messages: Array + ): Array { + return this.sortAndFilter(messages).map( + IterableInboxDataModel.getInboxRowViewModelForMessage + ); } - private sortAndFilter(messages: Array): Array { + private sortAndFilter( + messages: Array + ): Array { var sortedFilteredMessages = messages.slice(); if (this.filterFn != undefined) { @@ -141,7 +157,9 @@ class IterableInboxDataModel { return sortedFilteredMessages; } - private static getInboxRowViewModelForMessage(message: IterableInAppMessage): InboxRowViewModel { + private static getInboxRowViewModelForMessage( + message: IterableInAppMessage + ): InboxRowViewModel { return { title: message.inboxMetadata?.title ?? '', subtitle: message.inboxMetadata?.subtitle, From 725d72705d70629ebe56ab8f79ec365f391d8b83 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:20:21 -0700 Subject: [PATCH 12/30] Refactor IterableInboxDataModel and add comment --- src/IterableInboxDataModel.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/IterableInboxDataModel.ts b/src/IterableInboxDataModel.ts index f9eb48ae4..164793e9d 100644 --- a/src/IterableInboxDataModel.ts +++ b/src/IterableInboxDataModel.ts @@ -13,6 +13,7 @@ import IterableInAppMessage from './IterableInAppMessage'; const RNIterableAPI = NativeModules.RNIterableAPI; +// TODO: Comment export class IterableInboxDataModel { filterFn?: (message: IterableInAppMessage) => boolean; comparatorFn?: ( From f618d7c2829df8e3f67b921883b4f4cb71d71b90 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:21:47 -0700 Subject: [PATCH 13/30] Refactor IterableInboxEmptyState component and add missing type description --- src/IterableInboxEmptyState.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/IterableInboxEmptyState.tsx b/src/IterableInboxEmptyState.tsx index ab2568e86..d1c754764 100644 --- a/src/IterableInboxEmptyState.tsx +++ b/src/IterableInboxEmptyState.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; -import IterableInboxCustomizations from './IterableInboxCustomizations'; +import { type IterableInboxCustomizations } from './IterableInboxCustomizations'; -type emptyStateProps = { +// TODO: Comment +type EmptyStateProps = { customizations: IterableInboxCustomizations; tabBarHeight: number; tabBarPadding: number; @@ -13,14 +13,14 @@ type emptyStateProps = { isPortrait: boolean; }; -const IterableInboxEmptyState = ({ +export const IterableInboxEmptyState = ({ customizations, tabBarHeight, tabBarPadding, navTitleHeight, height, isPortrait, -}: emptyStateProps) => { +}: EmptyStateProps) => { const defaultTitle = 'No saved messages'; const defaultBody = 'Check again later!'; @@ -40,7 +40,9 @@ const IterableInboxEmptyState = ({ return ( - {emptyStateTitle ? emptyStateTitle : defaultTitle} + + {emptyStateTitle ? emptyStateTitle : defaultTitle} + {emptyStateBody ? emptyStateBody : defaultBody} ); From e5259f2814f5f408de7feaca86a33339c191e658 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:27:00 -0700 Subject: [PATCH 14/30] Refactor IterableInboxMessageCell component and improve code readability --- src/IterableInboxMessageCell.tsx | 73 +++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/src/IterableInboxMessageCell.tsx b/src/IterableInboxMessageCell.tsx index db2a48110..8d6e90384 100644 --- a/src/IterableInboxMessageCell.tsx +++ b/src/IterableInboxMessageCell.tsx @@ -1,31 +1,33 @@ -import React, { useRef } from 'react'; +import { useRef } from 'react'; import { - View, - Text, - Image, Animated, + Image, PanResponder, - ViewStyle, - TextStyle, StyleSheet, + Text, TouchableOpacity, + View, + type TextStyle, + type ViewStyle, } from 'react-native'; -import InboxRowViewModel from './InboxRowViewModel'; -import IterableInboxCustomizations from './IterableInboxCustomizations'; +import type { InboxRowViewModel } from './InboxRowViewModel'; +import type { IterableInboxCustomizations } from './IterableInboxCustomizations'; import IterableInboxDataModel from './IterableInboxDataModel'; +// TODO: Change to component function defaultMessageListLayout( last: boolean, dataModel: IterableInboxDataModel, rowViewModel: InboxRowViewModel, customizations: IterableInboxCustomizations, - isPortrait: boolean, + isPortrait: boolean ) { const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title ?? ''; const messageBody = rowViewModel.inAppMessage.inboxMetadata?.subtitle ?? ''; - const messageCreatedAt = dataModel.getFormattedDate(rowViewModel.inAppMessage) ?? ''; + const messageCreatedAt = + dataModel.getFormattedDate(rowViewModel.inAppMessage) ?? ''; const thumbnailURL = rowViewModel.imageUrl; let styles = StyleSheet.create({ @@ -110,13 +112,17 @@ function defaultMessageListLayout( messageRow, } = resolvedStyles; - unreadIndicator = !isPortrait ? { ...unreadIndicator, marginLeft: 40 } : unreadIndicator; + unreadIndicator = !isPortrait + ? { ...unreadIndicator, marginLeft: 40 } + : unreadIndicator; readMessageThumbnailContainer = !isPortrait ? { ...readMessageThumbnailContainer, paddingLeft: 65 } : readMessageThumbnailContainer; - messageContainer = !isPortrait ? { ...messageContainer, width: '90%' } : messageContainer; + messageContainer = !isPortrait + ? { ...messageContainer, width: '90%' } + : messageContainer; - function messageRowStyle(rowViewModel: InboxRowViewModel) { + function messageRowStyle(_rowViewModel: InboxRowViewModel) { return last ? { ...messageRow, borderBottomWidth: 1 } : messageRow; } @@ -133,22 +139,28 @@ function defaultMessageListLayout( } > {thumbnailURL ? ( - + ) : null} - {messageTitle as TextStyle} + {messageTitle} {messageBody} - {messageCreatedAt as TextStyle} + {messageCreatedAt} ); } +// TODO: Comment type MessageCellProps = { index: number; last: boolean; @@ -163,7 +175,7 @@ type MessageCellProps = { isPortrait: boolean; }; -const IterableInboxMessageCell = ({ +export const IterableInboxMessageCell = ({ index, last, dataModel, @@ -213,7 +225,9 @@ const IterableInboxMessageCell = ({ let { textContainer, deleteSlider, textStyle } = styles; - deleteSlider = isPortrait ? deleteSlider : { ...deleteSlider, paddingRight: 40 }; + deleteSlider = isPortrait + ? deleteSlider + : { ...deleteSlider, paddingRight: 40 }; const scrollThreshold = contentWidth / 15; const FORCING_DURATION = 350; @@ -247,12 +261,12 @@ const IterableInboxMessageCell = ({ const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponder: () => false, - onMoveShouldSetPanResponder: (event, gestureState) => { + onMoveShouldSetPanResponder: (_event, gestureState) => { const { dx, dy } = gestureState; // return true if user is swiping, return false if it's a single click return Math.abs(dx) !== 0 && Math.abs(dy) !== 0; }, - onMoveShouldSetPanResponderCapture: (event, gestureState) => { + onMoveShouldSetPanResponderCapture: (_event, gestureState) => { const { dx, dy } = gestureState; // return true if user is swiping, return false if it's a single click return Math.abs(dx) !== 0 && Math.abs(dy) !== 0; @@ -261,7 +275,7 @@ const IterableInboxMessageCell = ({ onPanResponderGrant: () => { position.setValue({ x: 0, y: 0 }); }, - onPanResponderMove: (event, gesture) => { + onPanResponderMove: (_event, gesture) => { if (gesture.dx <= -scrollThreshold) { //enables swipeing when threshold is reached swipingCheck(true); @@ -271,7 +285,7 @@ const IterableInboxMessageCell = ({ position.setValue({ x, y: 0 }); } }, - onPanResponderRelease: (event, gesture) => { + onPanResponderRelease: (_event, gesture) => { position.flattenOffset(); if (gesture.dx < 0) { userSwipedLeft(gesture); @@ -280,7 +294,7 @@ const IterableInboxMessageCell = ({ } swipingCheck(false); }, - }), + }) ).current; return ( @@ -288,7 +302,10 @@ const IterableInboxMessageCell = ({ DELETE - + { @@ -297,7 +314,13 @@ const IterableInboxMessageCell = ({ > {messageListItemLayout(last, rowViewModel) ? messageListItemLayout(last, rowViewModel)[0] - : defaultMessageListLayout(last, dataModel, rowViewModel, customizations, isPortrait)} + : defaultMessageListLayout( + last, + dataModel, + rowViewModel, + customizations, + isPortrait + )} From 92a244fb6602a9935a7353d810f047eae32787ee Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:35:08 -0700 Subject: [PATCH 15/30] add missing dependencies --- package.json | 7 +++ yarn.lock | 168 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 171 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c69d91e1e..aa860d14e 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@release-it/conventional-changelog": "^5.0.0", "@types/jest": "^29.5.5", "@types/react": "^18.2.44", + "@types/react-native-vector-icons": "^6.4.18", "commitlint": "^17.0.2", "del-cli": "^5.1.0", "eslint": "^8.51.0", @@ -184,5 +185,11 @@ "type": "module-legacy", "languages": "kotlin-swift", "version": "0.41.2" + }, + "dependencies": { + "@react-navigation/native": "^6.1.18", + "react-native-safe-area-context": "^4.11.0", + "react-native-vector-icons": "^10.2.0", + "react-native-webview": "^13.12.2" } } diff --git a/yarn.lock b/yarn.lock index 83f50ff54..c812d0a87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2006,9 +2006,11 @@ __metadata: "@commitlint/config-conventional": ^17.0.2 "@evilmartians/lefthook": ^1.5.0 "@react-native/eslint-config": ^0.73.1 + "@react-navigation/native": ^6.1.18 "@release-it/conventional-changelog": ^5.0.0 "@types/jest": ^29.5.5 "@types/react": ^18.2.44 + "@types/react-native-vector-icons": ^6.4.18 commitlint: ^17.0.2 del-cli: ^5.1.0 eslint: ^8.51.0 @@ -2019,6 +2021,9 @@ __metadata: react: 18.3.1 react-native: 0.75.3 react-native-builder-bob: ^0.30.2 + react-native-safe-area-context: ^4.11.0 + react-native-vector-icons: ^10.2.0 + react-native-webview: ^13.12.2 release-it: ^15.0.0 turbo: ^1.10.7 typescript: ^5.2.2 @@ -2989,6 +2994,46 @@ __metadata: languageName: node linkType: hard +"@react-navigation/core@npm:^6.4.17": + version: 6.4.17 + resolution: "@react-navigation/core@npm:6.4.17" + dependencies: + "@react-navigation/routers": ^6.1.9 + escape-string-regexp: ^4.0.0 + nanoid: ^3.1.23 + query-string: ^7.1.3 + react-is: ^16.13.0 + use-latest-callback: ^0.2.1 + peerDependencies: + react: "*" + checksum: 5e7315bb6ebff8e796eaccb0442d00696466750cc387e93f5edb5293d4ad3f409c1525ef76192894488e2d0979b762b236a1b0fbbb7500b2f065bf4745d509c0 + languageName: node + linkType: hard + +"@react-navigation/native@npm:^6.1.18": + version: 6.1.18 + resolution: "@react-navigation/native@npm:6.1.18" + dependencies: + "@react-navigation/core": ^6.4.17 + escape-string-regexp: ^4.0.0 + fast-deep-equal: ^3.1.3 + nanoid: ^3.1.23 + peerDependencies: + react: "*" + react-native: "*" + checksum: 82aeea67723f5dc41403e1c260f04942696f6cde95e30629c383521c3837d18d2d5c21bd78f0ade50beb81ac5edca2d7d38980dcd3a79e3acc86f45d0c09a4b8 + languageName: node + linkType: hard + +"@react-navigation/routers@npm:^6.1.9": + version: 6.1.9 + resolution: "@react-navigation/routers@npm:6.1.9" + dependencies: + nanoid: ^3.1.23 + checksum: 3a3392ce095d6a2bd2aad69856f513b35774f943a3dc73d8ffb75127de6773203e3264188d87058bdea4c0c9a7d43ed28d0cbf3a1f1cdc086df3ee255d8e1e27 + languageName: node + linkType: hard + "@release-it/conventional-changelog@npm:^5.0.0": version: 5.1.1 resolution: "@release-it/conventional-changelog@npm:5.1.1" @@ -3240,6 +3285,25 @@ __metadata: languageName: node linkType: hard +"@types/react-native-vector-icons@npm:^6.4.18": + version: 6.4.18 + resolution: "@types/react-native-vector-icons@npm:6.4.18" + dependencies: + "@types/react": "*" + "@types/react-native": ^0.70 + checksum: 1ef458cb5e7a37f41eb400e3153940b1b152e4df76a7c06c7a47c712dbfe46e14b9999f04dde1bd074f338f850e161c6c925174ddea33386b74f8112c940065b + languageName: node + linkType: hard + +"@types/react-native@npm:^0.70": + version: 0.70.19 + resolution: "@types/react-native@npm:0.70.19" + dependencies: + "@types/react": "*" + checksum: 79b504fa56340631079e7c20ea0d9412ec14147b76d0ce189f4403936f529ef1e6fd031383afab117846c5ae039123bcf3afc948bae4432269c6780282726f71 + languageName: node + linkType: hard + "@types/react@npm:^18.2.44": version: 18.3.10 resolution: "@types/react@npm:18.3.10" @@ -5191,6 +5255,13 @@ __metadata: languageName: node linkType: hard +"decode-uri-component@npm:^0.2.2": + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 95476a7d28f267292ce745eac3524a9079058bbb35767b76e3ee87d42e34cd0275d2eb19d9d08c3e167f97556e8a2872747f5e65cbebcac8b0c98d83e285f139 + languageName: node + linkType: hard + "decompress-response@npm:^6.0.0": version: 6.0.0 resolution: "decompress-response@npm:6.0.0" @@ -6338,6 +6409,13 @@ __metadata: languageName: node linkType: hard +"filter-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "filter-obj@npm:1.1.0" + checksum: cf2104a7c45ff48e7f505b78a3991c8f7f30f28bd8106ef582721f321f1c6277f7751aacd5d83026cb079d9d5091082f588d14a72e7c5d720ece79118fa61e10 + languageName: node + linkType: hard + "finalhandler@npm:1.1.2": version: 1.1.2 resolution: "finalhandler@npm:1.1.2" @@ -7341,7 +7419,7 @@ __metadata: languageName: node linkType: hard -"invariant@npm:^2.2.4": +"invariant@npm:2.2.4, invariant@npm:^2.2.4": version: 2.2.4 resolution: "invariant@npm:2.2.4" dependencies: @@ -9700,6 +9778,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.1.23": + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" + bin: + nanoid: bin/nanoid.cjs + checksum: d36c427e530713e4ac6567d488b489a36582ef89da1d6d4e3b87eded11eb10d7042a877958c6f104929809b2ab0bafa17652b076cdf84324aa75b30b722204f2 + languageName: node + linkType: hard + "natural-compare-lite@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare-lite@npm:1.4.0" @@ -10618,7 +10705,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.8.1": +"prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -10706,6 +10793,18 @@ __metadata: languageName: node linkType: hard +"query-string@npm:^7.1.3": + version: 7.1.3 + resolution: "query-string@npm:7.1.3" + dependencies: + decode-uri-component: ^0.2.2 + filter-obj: ^1.1.0 + split-on-first: ^1.0.0 + strict-uri-encode: ^2.0.0 + checksum: 91af02dcd9cc9227a052841d5c2eecb80a0d6489d05625df506a097ef1c59037cfb5e907f39b84643cbfd535c955abec3e553d0130a7b510120c37d06e0f4346 + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -10767,7 +10866,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1": +"react-is@npm:^16.13.0, react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -10820,6 +10919,44 @@ __metadata: languageName: node linkType: hard +"react-native-safe-area-context@npm:^4.11.0": + version: 4.11.0 + resolution: "react-native-safe-area-context@npm:4.11.0" + peerDependencies: + react: "*" + react-native: "*" + checksum: 6d6af265ecf1b9cb45e042a335f2c5da472ad41eb5d4fda8e31a1607b344726f0f4f9501ef3a3a731cec3784ba93485b44cbdafbf5f13055caa130c703bbd307 + languageName: node + linkType: hard + +"react-native-vector-icons@npm:^10.2.0": + version: 10.2.0 + resolution: "react-native-vector-icons@npm:10.2.0" + dependencies: + prop-types: ^15.7.2 + yargs: ^16.1.1 + bin: + fa-upgrade.sh: bin/fa-upgrade.sh + fa5-upgrade: bin/fa5-upgrade.sh + fa6-upgrade: bin/fa6-upgrade.sh + generate-icon: bin/generate-icon.js + checksum: fda930df4e63f12533268f5b339ebe4c77c691eae43503328466b3087ed868a06a4593fd246e75ac6b5ec955543eec35608c7922191bdcc3b3a94ed7f3575ef0 + languageName: node + linkType: hard + +"react-native-webview@npm:^13.12.2": + version: 13.12.2 + resolution: "react-native-webview@npm:13.12.2" + dependencies: + escape-string-regexp: ^4.0.0 + invariant: 2.2.4 + peerDependencies: + react: "*" + react-native: "*" + checksum: d84b2e2e75dc484eb40f8e285607b9686693f2d31e95802e2b358dc0820bcc294b438a77174eba157c31d95675c962c61c94885629af999416ad1355e9618fe0 + languageName: node + linkType: hard + "react-native@npm:0.75.3": version: 0.75.3 resolution: "react-native@npm:0.75.3" @@ -11828,6 +11965,13 @@ __metadata: languageName: node linkType: hard +"split-on-first@npm:^1.0.0": + version: 1.1.0 + resolution: "split-on-first@npm:1.1.0" + checksum: 16ff85b54ddcf17f9147210a4022529b343edbcbea4ce977c8f30e38408b8d6e0f25f92cd35b86a524d4797f455e29ab89eb8db787f3c10708e0b47ebf528d30 + languageName: node + linkType: hard + "split2@npm:^3.0.0, split2@npm:^3.2.2": version: 3.2.2 resolution: "split2@npm:3.2.2" @@ -11926,6 +12070,13 @@ __metadata: languageName: node linkType: hard +"strict-uri-encode@npm:^2.0.0": + version: 2.0.0 + resolution: "strict-uri-encode@npm:2.0.0" + checksum: eaac4cf978b6fbd480f1092cab8b233c9b949bcabfc9b598dd79a758f7243c28765ef7639c876fa72940dac687181b35486ea01ff7df3e65ce3848c64822c581 + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -12837,6 +12988,15 @@ __metadata: languageName: node linkType: hard +"use-latest-callback@npm:^0.2.1": + version: 0.2.1 + resolution: "use-latest-callback@npm:0.2.1" + peerDependencies: + react: ">=16.8" + checksum: da5718eda625738cc7dac8fb502d0f8f2039435eb71203565a72c32e0f5769e7b8ddac074e650066636e7f4b29b45524f751cb18a2b430856d98879bbb10d274 + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -13271,7 +13431,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^16.2.0": +"yargs@npm:^16.1.1, yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" dependencies: From df7676ba68fcf78100c59b2c270ae2e770224c98 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:35:19 -0700 Subject: [PATCH 16/30] Refactor IterableInboxMessageDisplay component and improve code readability --- src/IterableInboxMessageDisplay.tsx | 44 ++++++++++++++++------------- src/IterableInboxMessageList.tsx | 18 ++++++------ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/IterableInboxMessageDisplay.tsx b/src/IterableInboxMessageDisplay.tsx index 768239283..654a80113 100644 --- a/src/IterableInboxMessageDisplay.tsx +++ b/src/IterableInboxMessageDisplay.tsx @@ -1,28 +1,26 @@ -import React, { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { - Text, - View, + Linking, ScrollView, StyleSheet, - Linking, + Text, TouchableWithoutFeedback, + View, } from 'react-native'; -import { WebView } from 'react-native-webview'; import Icon from 'react-native-vector-icons/Ionicons'; +import { WebView } from 'react-native-webview'; +import { type InboxRowViewModel } from './InboxRowViewModel'; +import { Iterable, IterableActionSource } from './Iterable'; +import { IterableAction, IterableActionContext } from './IterableAction'; import { - IterableHtmlInAppContent, IterableEdgeInsets, - IterableInAppLocation, + IterableHtmlInAppContent, IterableInAppCloseSource, + IterableInAppLocation, } from './IterableInAppClasses'; -import { IterableAction, IterableActionContext } from './IterableAction'; - -import InboxRowViewModel from './InboxRowViewModel'; - -import { Iterable, IterableActionSource } from './Iterable'; - +// TODO: Comment type MessageDisplayProps = { rowViewModel: InboxRowViewModel; inAppContentPromise: Promise; @@ -32,7 +30,7 @@ type MessageDisplayProps = { isPortrait: boolean; }; -const IterableInboxMessageDisplay = ({ +export const IterableInboxMessageDisplay = ({ rowViewModel, inAppContentPromise, returnToInbox, @@ -42,7 +40,7 @@ const IterableInboxMessageDisplay = ({ }: MessageDisplayProps) => { const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title; const [inAppContent, setInAppContent] = useState( - new IterableHtmlInAppContent(new IterableEdgeInsets(0, 0, 0, 0), ''), + new IterableHtmlInAppContent(new IterableEdgeInsets(0, 0, 0, 0), '') ); const styles = StyleSheet.create({ @@ -160,12 +158,16 @@ const IterableInboxMessageDisplay = ({ let source = IterableActionSource.inApp; let 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 @@ -205,7 +207,7 @@ const IterableInboxMessageDisplay = ({ Iterable.trackInAppClose( rowViewModel.inAppMessage, IterableInAppLocation.inbox, - IterableInAppCloseSource.back, + IterableInAppCloseSource.back ); }} > @@ -217,7 +219,11 @@ const IterableInboxMessageDisplay = ({ - + {messageTitle} diff --git a/src/IterableInboxMessageList.tsx b/src/IterableInboxMessageList.tsx index 800d62b24..ea8e01493 100644 --- a/src/IterableInboxMessageList.tsx +++ b/src/IterableInboxMessageList.tsx @@ -2,12 +2,11 @@ import { useCallback, useRef, useState } from 'react'; import { type ViewabilityConfig, type ViewToken, FlatList } from 'react-native'; import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; -import IterableInboxMessageCell from './IterableInboxMessageCell'; -import InboxRowViewModel from './InboxRowViewModel'; -import IterableInboxCustomizations from './IterableInboxCustomizations'; - +import type { InboxRowViewModel } from './InboxRowViewModel'; import IterableInAppMessage from './IterableInAppMessage'; +import type { IterableInboxCustomizations } from './IterableInboxCustomizations'; import IterableInboxDataModel from './IterableInboxDataModel'; +import IterableInboxMessageCell from './IterableInboxMessageCell'; // TODO: Comment type MessageListProps = { @@ -22,7 +21,8 @@ type MessageListProps = { isPortrait: boolean; }; -const IterableInboxMessageList = ({ +// TODO: Comment +export const IterableInboxMessageList = ({ dataModel, rowViewModels, customizations, @@ -49,11 +49,11 @@ const IterableInboxMessageList = ({ dataModel={dataModel} rowViewModel={rowViewModel} customizations={customizations} - swipingCheck={(swiping: boolean) => setSwiping(swiping)} + swipingCheck={setSwiping} messageListItemLayout={messageListItemLayout} deleteRow={(messageId: string) => deleteRow(messageId)} - handleMessageSelect={(messageId: string, index: number) => - handleMessageSelect(messageId, index) + handleMessageSelect={(messageId: string, i: number) => + handleMessageSelect(messageId, i) } contentWidth={contentWidth} isPortrait={isPortrait} @@ -88,6 +88,8 @@ const IterableInboxMessageList = ({ updateVisibleMessageImpressions(rowInfos); }, + // TODO: Figure out if we need the missing dependencies + // eslint-disable-next-line react-hooks/exhaustive-deps [] ); From e2d69ed4f17d6249bcf1debc6a19ff12dc8b1bfc Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:39:34 -0700 Subject: [PATCH 17/30] Refactor IterableConfig.ts and add missing import Refactor IterableInbox.tsx and remove unused imports Add comment to IterableInboxMessageCell.tsx Add comment to IterableInboxMessageDisplay.tsx Add comments and descriptions to IterableLogger.ts Add a description to IterableUtil.ts Add comment to useAppStateListener.ts Add comment to useDeviceOrientation.tsx --- src/IterableConfig.ts | 5 +---- src/IterableInbox.tsx | 9 ++++----- src/IterableInboxMessageCell.tsx | 1 + src/IterableInboxMessageDisplay.tsx | 1 + src/IterableLogger.ts | 1 + src/IterableUtil.ts | 1 + src/useAppStateListener.ts | 1 + src/useDeviceOrientation.tsx | 1 + 8 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/IterableConfig.ts b/src/IterableConfig.ts index b268fe68a..17f13c7c6 100644 --- a/src/IterableConfig.ts +++ b/src/IterableConfig.ts @@ -3,13 +3,10 @@ import { IterableActionContext, IterableLogLevel, } from './IterableAction'; - +import { IterableDataRegion } from './IterableDataRegion'; import { IterableInAppShowResponse } from './IterableInAppClasses'; - import IterableInAppMessage from './IterableInAppMessage'; -import { IterableDataRegion } from './IterableDataRegion'; - // TODO: Add description type AuthCallBack = () => void; diff --git a/src/IterableInbox.tsx b/src/IterableInbox.tsx index cdbc39493..1b0bba3e5 100644 --- a/src/IterableInbox.tsx +++ b/src/IterableInbox.tsx @@ -11,15 +11,14 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; +import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; +import type { InboxRowViewModel } from './InboxRowViewModel'; +import { Iterable } from './Iterable'; import { IterableInAppDeleteSource, IterableInAppLocation, } from './IterableInAppClasses'; - -import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; -import type { InboxRowViewModel } from './InboxRowViewModel'; -import { Iterable } from './Iterable'; -import IterableInboxCustomizations from './IterableInboxCustomizations'; +import type IterableInboxCustomizations from './IterableInboxCustomizations'; import IterableInboxDataModel from './IterableInboxDataModel'; import IterableInboxEmptyState from './IterableInboxEmptyState'; import IterableInboxMessageDisplay from './IterableInboxMessageDisplay'; diff --git a/src/IterableInboxMessageCell.tsx b/src/IterableInboxMessageCell.tsx index 8d6e90384..3fdbc7548 100644 --- a/src/IterableInboxMessageCell.tsx +++ b/src/IterableInboxMessageCell.tsx @@ -175,6 +175,7 @@ type MessageCellProps = { isPortrait: boolean; }; +// TODO: Comment export const IterableInboxMessageCell = ({ index, last, diff --git a/src/IterableInboxMessageDisplay.tsx b/src/IterableInboxMessageDisplay.tsx index 654a80113..65bd4f504 100644 --- a/src/IterableInboxMessageDisplay.tsx +++ b/src/IterableInboxMessageDisplay.tsx @@ -30,6 +30,7 @@ type MessageDisplayProps = { isPortrait: boolean; }; +// TODO: Comment export const IterableInboxMessageDisplay = ({ rowViewModel, inAppContentPromise, diff --git a/src/IterableLogger.ts b/src/IterableLogger.ts index a6954dee3..9346166e3 100644 --- a/src/IterableLogger.ts +++ b/src/IterableLogger.ts @@ -1,5 +1,6 @@ import IterableConfig from './IterableConfig'; +// TODO: Add comments and descriptions export class IterableLogger { readonly config: IterableConfig; diff --git a/src/IterableUtil.ts b/src/IterableUtil.ts index b23d954b2..e2dc049bc 100644 --- a/src/IterableUtil.ts +++ b/src/IterableUtil.ts @@ -1,3 +1,4 @@ +// TODO: Add a description export class IterableUtil { static readBoolean(dict: any, key: string): boolean { if (dict[key]) { diff --git a/src/useAppStateListener.ts b/src/useAppStateListener.ts index 9a6f2f13f..353150d96 100644 --- a/src/useAppStateListener.ts +++ b/src/useAppStateListener.ts @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { AppState } from 'react-native'; +// TODO: Comment export function useAppStateListener() { const appStateEventName = 'change'; const appState = useRef(AppState.currentState); diff --git a/src/useDeviceOrientation.tsx b/src/useDeviceOrientation.tsx index 1cfdd74aa..0003bab86 100644 --- a/src/useDeviceOrientation.tsx +++ b/src/useDeviceOrientation.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useWindowDimensions } from 'react-native'; +// TODO: Comment export function useDeviceOrientation() { const { height, width } = useWindowDimensions(); From 0f74961b8e5f3a314e6c2bfaadde4b627b536eff Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:47:26 -0700 Subject: [PATCH 18/30] Refactor IterableConfig.ts and add missing import Refactor IterableInbox.tsx and improve code readability --- src/IterableConfig.ts | 8 ++++++++ src/IterableInbox.tsx | 36 +++++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/IterableConfig.ts b/src/IterableConfig.ts index 17f13c7c6..ae839fe92 100644 --- a/src/IterableConfig.ts +++ b/src/IterableConfig.ts @@ -132,9 +132,17 @@ export class IterableConfig { pushIntegrationName: this.pushIntegrationName, autoPushRegistration: this.autoPushRegistration, inAppDisplayInterval: this.inAppDisplayInterval, + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq urlHandlerPresent: this.urlHandler != undefined, + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq customActionHandlerPresent: this.customActionHandler != undefined, + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq inAppHandlerPresent: this.inAppHandler != undefined, + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq authHandlerPresent: this.authHandler != undefined, logLevel: this.logLevel, expiringAuthTokenRefreshPeriod: this.expiringAuthTokenRefreshPeriod, diff --git a/src/IterableInbox.tsx b/src/IterableInbox.tsx index 1b0bba3e5..58e6912a0 100644 --- a/src/IterableInbox.tsx +++ b/src/IterableInbox.tsx @@ -18,7 +18,7 @@ import { IterableInAppDeleteSource, IterableInAppLocation, } from './IterableInAppClasses'; -import type IterableInboxCustomizations from './IterableInboxCustomizations'; +import type { IterableInboxCustomizations } from './IterableInboxCustomizations'; import IterableInboxDataModel from './IterableInboxDataModel'; import IterableInboxEmptyState from './IterableInboxEmptyState'; import IterableInboxMessageDisplay from './IterableInboxMessageDisplay'; @@ -29,7 +29,8 @@ import useDeviceOrientation from './useDeviceOrientation'; const RNIterableAPI = NativeModules.RNIterableAPI; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); -type inboxProps = { +// TODO: Comment +type InboxProps = { returnToInboxTrigger?: boolean; messageListItemLayout?: Function; customizations?: IterableInboxCustomizations; @@ -39,17 +40,16 @@ type inboxProps = { showNavTitle?: boolean; }; -const IterableInbox = ({ +// TODO: Comment +export const IterableInbox = ({ returnToInboxTrigger = true, - messageListItemLayout = () => { - return null; - }, + messageListItemLayout = () => null, customizations = {} as IterableInboxCustomizations, tabBarHeight = 80, tabBarPadding = 20, safeAreaMode = true, showNavTitle = true, -}: inboxProps) => { +}: InboxProps) => { const defaultInboxTitle = 'Inbox'; const inboxDataModel = new IterableInboxDataModel(); @@ -123,6 +123,8 @@ const IterableInbox = ({ removeInboxChangedListener(); inboxDataModel.endSession(visibleMessageImpressions); }; + // TODO: figure out if missing dependency is a bug + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); //starts session when user is on inbox and app is active @@ -138,6 +140,8 @@ const IterableInbox = ({ inboxDataModel.endSession(visibleMessageImpressions); } } + // TODO: figure out if missing dependency is a bug + // eslint-disable-next-line react-hooks/exhaustive-deps }, [appState]); //starts session when user is on inbox @@ -150,11 +154,15 @@ const IterableInbox = ({ inboxDataModel.endSession(visibleMessageImpressions); } } + // TODO: figure out if missing dependency is a bug + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isFocused]); //updates the visible rows when visible messages changes useEffect(() => { inboxDataModel.updateVisibleRows(visibleMessageImpressions); + // TODO: figure out if missing dependency is a bug + // eslint-disable-next-line react-hooks/exhaustive-deps }, [visibleMessageImpressions]); //if return to inbox trigger is provided, runs the return to inbox animation whenever the trigger is toggled @@ -162,6 +170,8 @@ const IterableInbox = ({ if (isMessageDisplay) { returnToInbox(); } + // TODO: figure out if missing dependency is a bug + // eslint-disable-next-line react-hooks/exhaustive-deps }, [returnToInboxTrigger]); function addInboxChangedListener() { @@ -192,9 +202,9 @@ const IterableInbox = ({ function handleMessageSelect( id: string, index: number, - rowViewModels: InboxRowViewModel[] + models: InboxRowViewModel[] ) { - let newRowViewModels = rowViewModels.map((rowViewModel) => { + let newRowViewModels = models.map((rowViewModel) => { return rowViewModel.inAppMessage.messageId === id ? { ...rowViewModel, read: true } : rowViewModel; @@ -204,7 +214,9 @@ const IterableInbox = ({ setSelectedRowViewModelIdx(index); Iterable.trackInAppOpen( - rowViewModels[index].inAppMessage, + // TODO: Have a safety check for models[index].inAppMessage + // @ts-ignore + models[index].inAppMessage, IterableInAppLocation.inbox ); @@ -254,7 +266,7 @@ const IterableInbox = ({ ) : null; } - function showMessageList(loading: boolean) { + function showMessageList(_loading: boolean) { return ( {showNavTitle ? ( @@ -314,6 +326,8 @@ const IterableInbox = ({ const inboxAnimatedView = ( Date: Mon, 30 Sep 2024 15:52:45 -0700 Subject: [PATCH 19/30] Fixed linting for test files --- src/__tests__/Iterable.spec.ts | 551 ++++++++++++++++------------ src/__tests__/IterableInApp.spec.ts | 283 +++++++++----- src/__tests__/TestHelper.ts | 13 +- 3 files changed, 502 insertions(+), 345 deletions(-) diff --git a/src/__tests__/Iterable.spec.ts b/src/__tests__/Iterable.spec.ts index f82941c39..3eafe2d07 100644 --- a/src/__tests__/Iterable.spec.ts +++ b/src/__tests__/Iterable.spec.ts @@ -1,445 +1,511 @@ -import { MockRNIterableAPI } from '../__mocks__/MockRNIterableAPI' -import { MockLinking } from '../__mocks__/MockLinking' -import { TestHelper } from './TestHelper' -import { NativeEventEmitter } from 'react-native' +import { NativeEventEmitter } from 'react-native'; -// import from the same location that consumers import from -import { - Iterable, - IterableConfig, - IterableLogLevel -} from '../index' +import { MockRNIterableAPI } from '../__mocks__/MockRNIterableAPI'; +import { MockLinking } from '../__mocks__/MockLinking'; +import { TestHelper } from './TestHelper'; +// import from the same location that consumers import from +import { Iterable, IterableConfig, IterableLogLevel } from '../index'; import { IterableAttributionInfo, IterableCommerceItem, IterableActionContext, EventName, IterableAction, - IterableActionSource -} from '../Iterable' -import { IterableLogger } from '../IterableLogger' -import { IterableDataRegion } from '../IterableDataRegion' + IterableActionSource, +} from '../Iterable'; +import { IterableLogger } from '../IterableLogger'; +import { IterableDataRegion } from '../IterableDataRegion'; beforeEach(() => { - jest.clearAllMocks() - Iterable.logger = new IterableLogger(new IterableConfig()) -}) + jest.clearAllMocks(); + Iterable.logger = new IterableLogger(new IterableConfig()); +}); it('setEmail_getEmail_email_returnsEmail', async () => { - Iterable.logger.log('setEmail_getEmail_email_returnsEmail') - const result = 'user@example.com' + Iterable.logger.log('setEmail_getEmail_email_returnsEmail'); + const result = 'user@example.com'; // GIVEN an email - const email = 'user@example.com' + const email = 'user@example.com'; // WHEN Iterable.setEmail is called with the given email - Iterable.setEmail(email) + Iterable.setEmail(email); // THEN Iterable.getEmail returns the given email - return await Iterable.getEmail().then(email => { - expect(email).toBe(result) - }) -}) + return await Iterable.getEmail().then((mail) => { + expect(mail).toBe(result); + }); +}); test('setUserId_getUserId_userId_returnsUserId', async () => { - Iterable.logger.log('setUserId_getUserId_userId_returnsUserId') - const result = 'user1' + Iterable.logger.log('setUserId_getUserId_userId_returnsUserId'); + const result = 'user1'; // GIVEN an userId - const userId = 'user1' + const userId = 'user1'; // WHEN Iterable.setUserId is called with the given userId - Iterable.setUserId(userId) + Iterable.setUserId(userId); // THEN Iterable.getUserId returns the given userId - return await Iterable.getUserId().then(userId => { - expect(userId).toBe(result) - }) -}) + return await Iterable.getUserId().then((id) => { + expect(id).toBe(result); + }); +}); test('disableDeviceForCurrentUser_noParams_methodCalled', () => { - Iterable.logger.log('disableDeviceForCurrentUser_noParams_methodCalled') + Iterable.logger.log('disableDeviceForCurrentUser_noParams_methodCalled'); // GIVEN no parameters // WHEN Iterable.disableDeviceForCurrentUser is called - Iterable.disableDeviceForCurrentUser() + Iterable.disableDeviceForCurrentUser(); // THEN corresponding method is called on RNITerableAPI - expect(MockRNIterableAPI.disableDeviceForCurrentUser).toBeCalled() -}) + expect(MockRNIterableAPI.disableDeviceForCurrentUser).toBeCalled(); +}); test('getLastPushPayload_noParams_returnLastPushPayload', async () => { - Iterable.logger.log('getLastPushPayload_noParams_returnLastPushPayload') - const result = { var1: 'val1', var2: true } + Iterable.logger.log('getLastPushPayload_noParams_returnLastPushPayload'); + const result = { var1: 'val1', var2: true }; // GIVEN no parameters // WHEN the lastPushPayload is set - MockRNIterableAPI.lastPushPayload = { var1: 'val1', var2: true } + MockRNIterableAPI.lastPushPayload = { var1: 'val1', var2: true }; // THEN the lastPushPayload is returned when getLastPushPayload is called - return await Iterable.getLastPushPayload().then(payload => { - expect(payload).toEqual(result) - }) -}) + return await Iterable.getLastPushPayload().then((payload) => { + expect(payload).toEqual(result); + }); +}); test('trackPushOpenWithCampaignId_pushParams_methodCalled', () => { - Iterable.logger.log('getLastPushPayload_noParams_returnLastPushPayload') + Iterable.logger.log('getLastPushPayload_noParams_returnLastPushPayload'); // GIVEN the following parameters - const campaignId = 123 - const templateId = 234 - const messageId = 'someMessageId' - const appAlreadyRunning = false - const dataFields = { dataFieldKey: 'dataFieldValue' } + const campaignId = 123; + const templateId = 234; + const messageId = 'someMessageId'; + const appAlreadyRunning = false; + const dataFields = { dataFieldKey: 'dataFieldValue' }; // WHEN Iterable.trackPushOpenWithCampaignId is called - Iterable.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields) + Iterable.trackPushOpenWithCampaignId( + campaignId, + templateId, + messageId, + appAlreadyRunning, + dataFields + ); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackPushOpenWithCampaignId).toBeCalledWith(campaignId, templateId, messageId, appAlreadyRunning, dataFields) -}) + expect(MockRNIterableAPI.trackPushOpenWithCampaignId).toBeCalledWith( + campaignId, + templateId, + messageId, + appAlreadyRunning, + dataFields + ); +}); test('updateCart_items_methodCalled', () => { - Iterable.logger.log('updateCart_items_methodCalled') + Iterable.logger.log('updateCart_items_methodCalled'); // GIVEN list of items - const items = [new IterableCommerceItem('id1', 'Boba Tea', 18, 26)] + const items = [new IterableCommerceItem('id1', 'Boba Tea', 18, 26)]; // WHEN Iterable.updateCart is called - Iterable.updateCart(items) + Iterable.updateCart(items); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateCart).toBeCalledWith(items) -}) + expect(MockRNIterableAPI.updateCart).toBeCalledWith(items); +}); test('trackPurchase_params_methodCalled', () => { - Iterable.logger.log('trackPurchase_params_methodCalled') + Iterable.logger.log('trackPurchase_params_methodCalled'); // GIVEN the following parameters - const total = 10 - const items = [new IterableCommerceItem('id1', 'Boba Tea', 18, 26)] - const dataFields = { dataFieldKey: 'dataFieldValue' } + const total = 10; + const items = [new IterableCommerceItem('id1', 'Boba Tea', 18, 26)]; + const dataFields = { dataFieldKey: 'dataFieldValue' }; // WHEN Iterable.trackPurchase is called - Iterable.trackPurchase(total, items, dataFields) + Iterable.trackPurchase(total, items, dataFields); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackPurchase).toBeCalledWith(total, items, dataFields) -}) + expect(MockRNIterableAPI.trackPurchase).toBeCalledWith( + total, + items, + dataFields + ); +}); test('trackPurchase_paramsWithOptionalFields_methodCalled', () => { - Iterable.logger.log('trackPurchase_paramsWithOptionalFields_methodCalled') + Iterable.logger.log('trackPurchase_paramsWithOptionalFields_methodCalled'); // GIVEN the following parameters - const total = 5 - const items = [new IterableCommerceItem('id', 'swordfish', 64, 1, 'SKU', 'description', 'url', 'imageUrl', ['sword', 'shield'])] - const dataFields = { key: 'value' } + const total = 5; + const items = [ + new IterableCommerceItem( + 'id', + 'swordfish', + 64, + 1, + 'SKU', + 'description', + 'url', + 'imageUrl', + ['sword', 'shield'] + ), + ]; + const dataFields = { key: 'value' }; // WHEN Iterable.trackPurchase is called - Iterable.trackPurchase(total, items, dataFields) + Iterable.trackPurchase(total, items, dataFields); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackPurchase).toBeCalledWith(total, items, dataFields) -}) + expect(MockRNIterableAPI.trackPurchase).toBeCalledWith( + total, + items, + dataFields + ); +}); test('trackEvent_params_methodCalled', () => { - Iterable.logger.log('trackPurchase_paramsWithOptionalFields_methodCalled') + Iterable.logger.log('trackPurchase_paramsWithOptionalFields_methodCalled'); // GIVEN the following parameters - const name = 'EventName' - const dataFields = { DatafieldKey: 'DatafieldValue' } + const name = 'EventName'; + const dataFields = { DatafieldKey: 'DatafieldValue' }; // WHEN Iterable.trackEvent is called - Iterable.trackEvent(name, dataFields) + Iterable.trackEvent(name, dataFields); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackEvent).toBeCalledWith(name, dataFields) -}) + expect(MockRNIterableAPI.trackEvent).toBeCalledWith(name, dataFields); +}); test('setAttributionInfo_getAttributionInfo_attributionInfo_returnsAttributionInfo', async () => { - Iterable.logger.log('setAttributionInfo_getAttributionInfo_attributionInfo_returnsAttributionInfo') + Iterable.logger.log( + 'setAttributionInfo_getAttributionInfo_attributionInfo_returnsAttributionInfo' + ); // GIVEN attribution info - const campaignId = 1234 - const templateId = 5678 - const messageId = 'qwer' + const campaignId = 1234; + const templateId = 5678; + const messageId = 'qwer'; // WHEN Iterable.setAttributionInfo is called with the given attribution info - Iterable.setAttributionInfo(new IterableAttributionInfo(campaignId, templateId, messageId)) + Iterable.setAttributionInfo( + new IterableAttributionInfo(campaignId, templateId, messageId) + ); // THEN Iterable.getAttrbutionInfo returns the given attribution info - return await Iterable.getAttributionInfo().then(attributionInfo => { - expect(attributionInfo?.campaignId).toBe(campaignId) - expect(attributionInfo?.templateId).toBe(templateId) - expect(attributionInfo?.messageId).toBe(messageId) - }) -}) + return await Iterable.getAttributionInfo().then((attributionInfo) => { + expect(attributionInfo?.campaignId).toBe(campaignId); + expect(attributionInfo?.templateId).toBe(templateId); + expect(attributionInfo?.messageId).toBe(messageId); + }); +}); test('updateUser_params_methodCalled', () => { - Iterable.logger.log('updateUser_params_methodCalled') + Iterable.logger.log('updateUser_params_methodCalled'); // GIVEN the following parameters - const dataFields = { field: 'value1' } + const dataFields = { field: 'value1' }; // WHEN Iterable.updateUser is called - Iterable.updateUser(dataFields, false) + Iterable.updateUser(dataFields, false); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateUser).toBeCalledWith(dataFields, false) -}) + expect(MockRNIterableAPI.updateUser).toBeCalledWith(dataFields, false); +}); test('updateEmail_email_methodCalled', () => { - Iterable.logger.log('updateEmail_email_methodCalled') + Iterable.logger.log('updateEmail_email_methodCalled'); // GIVEN the new email - const newEmail = 'woo@newemail.com' + const newEmail = 'woo@newemail.com'; // WHEN Iterable.updateEmail is called - Iterable.updateEmail(newEmail) + Iterable.updateEmail(newEmail); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, undefined) -}) + expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, undefined); +}); test('updateEmail_emailAndToken_methodCalled', () => { - Iterable.logger.log('updateEmail_emailAndToken_methodCalled') + Iterable.logger.log('updateEmail_emailAndToken_methodCalled'); // GIVEN the new email and a token - const newEmail = 'woo@newemail.com' - const newToken = 'token2' + const newEmail = 'woo@newemail.com'; + const newToken = 'token2'; // WHEN Iterable.updateEmail is called - Iterable.updateEmail(newEmail, newToken) + Iterable.updateEmail(newEmail, newToken); // THEN corresponding function is called on RNITerableAPI - expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, newToken) -}) + expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, newToken); +}); test('iterableConfig_noParams_defaultValues', () => { - Iterable.logger.log('iterableConfig_noParams_defaultValues') + Iterable.logger.log('iterableConfig_noParams_defaultValues'); // GIVEN no parameters // WHEN config is initialized - const config = new IterableConfig() + const config = new IterableConfig(); // THEN config has default values - expect(config.pushIntegrationName).toBe(undefined) - expect(config.autoPushRegistration).toBe(true) - expect(config.checkForDeferredDeeplink).toBe(false) - expect(config.inAppDisplayInterval).toBe(30.0) - expect(config.urlHandler).toBe(undefined) - expect(config.customActionHandler).toBe(undefined) - expect(config.inAppHandler).toBe(undefined) - expect(config.authHandler).toBe(undefined) - expect(config.logLevel).toBe(IterableLogLevel.info) - expect(config.logReactNativeSdkCalls).toBe(true) - expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0) - expect(config.allowedProtocols).toEqual([]) - expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false) - expect(config.useInMemoryStorageForInApps).toBe(false) - expect(config.dataRegion).toBe(IterableDataRegion.US) - expect(config.encryptionEnforced).toBe(false) -}) + expect(config.pushIntegrationName).toBe(undefined); + expect(config.autoPushRegistration).toBe(true); + expect(config.checkForDeferredDeeplink).toBe(false); + expect(config.inAppDisplayInterval).toBe(30.0); + expect(config.urlHandler).toBe(undefined); + expect(config.customActionHandler).toBe(undefined); + expect(config.inAppHandler).toBe(undefined); + expect(config.authHandler).toBe(undefined); + expect(config.logLevel).toBe(IterableLogLevel.info); + expect(config.logReactNativeSdkCalls).toBe(true); + expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0); + expect(config.allowedProtocols).toEqual([]); + expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(config.useInMemoryStorageForInApps).toBe(false); + expect(config.dataRegion).toBe(IterableDataRegion.US); + expect(config.encryptionEnforced).toBe(false); +}); test('iterableConfig_noParams_defaultDictValues', () => { - Iterable.logger.log('iterableConfig_noParams_defaultDictValues') + Iterable.logger.log('iterableConfig_noParams_defaultDictValues'); // GIVEN no parameters // WHEN config is initialized and converted to a dictionary - const configDict = (new IterableConfig()).toDict() + const configDict = new IterableConfig().toDict(); // THEN config has default dictionary values - expect(configDict.pushIntegrationName).toBe(undefined) - expect(configDict.autoPushRegistration).toBe(true) - expect(configDict.inAppDisplayInterval).toBe(30.0) - expect(configDict.urlHandlerPresent).toBe(false) - expect(configDict.customActionHandlerPresent).toBe(false) - expect(configDict.inAppHandlerPresent).toBe(false) - expect(configDict.authHandlerPresent).toBe(false) - expect(configDict.logLevel).toBe(IterableLogLevel.info) - expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0) - expect(configDict.allowedProtocols).toEqual([]) - expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false) - expect(configDict.useInMemoryStorageForInApps).toBe(false) - expect(configDict.dataRegion).toBe(IterableDataRegion.US) - expect(configDict.encryptionEnforced).toBe(false) -}) + expect(configDict.pushIntegrationName).toBe(undefined); + expect(configDict.autoPushRegistration).toBe(true); + expect(configDict.inAppDisplayInterval).toBe(30.0); + expect(configDict.urlHandlerPresent).toBe(false); + expect(configDict.customActionHandlerPresent).toBe(false); + expect(configDict.inAppHandlerPresent).toBe(false); + expect(configDict.authHandlerPresent).toBe(false); + expect(configDict.logLevel).toBe(IterableLogLevel.info); + expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0); + expect(configDict.allowedProtocols).toEqual([]); + expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(configDict.useInMemoryStorageForInApps).toBe(false); + expect(configDict.dataRegion).toBe(IterableDataRegion.US); + expect(configDict.encryptionEnforced).toBe(false); +}); test('urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsFalse_openUrlCalled', async () => { - Iterable.logger.log('urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsFalse_openUrlCalled') + Iterable.logger.log( + 'urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsFalse_openUrlCalled' + ); // sets up event emitter - const nativeEmitter = new NativeEventEmitter() - nativeEmitter.removeAllListeners(EventName.handleUrlCalled) + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(EventName.handleUrlCalled); // sets up config file and urlHandler function // urlHandler set to return false - const config = new IterableConfig() - config.urlHandler = jest.fn((url: string, _: IterableActionContext) => { - return false - }) + const config = new IterableConfig(); + config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { + return false; + }); // initialize Iterable object - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Iterable.initialize('apiKey', config) + + Iterable.initialize('apiKey', config); // GIVEN canOpenUrl set to return a promise that resolves to true MockLinking.canOpenURL = jest.fn(async () => { - return await new Promise(resolve => { resolve(true) }) - }) - MockLinking.openURL.mockReset() - const expectedUrl = 'https://somewhere.com' - - const actionDict = { type: 'openUrl' } - const dict = { url: expectedUrl, context: { action: actionDict, source: 'inApp' } } + return await new Promise((resolve) => { + resolve(true); + }); + }); + MockLinking.openURL.mockReset(); + const expectedUrl = 'https://somewhere.com'; + + const actionDict = { type: 'openUrl' }; + const dict = { + url: expectedUrl, + context: { action: actionDict, source: 'inApp' }, + }; // WHEN handleUrlCalled event is emitted - nativeEmitter.emit(EventName.handleUrlCalled, dict) + nativeEmitter.emit(EventName.handleUrlCalled, dict); // THEN urlHandler and MockLinking is called with expected url return await TestHelper.delayed(0, () => { - expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context) - expect(MockLinking.openURL).toBeCalledWith(expectedUrl) - }) -}) + expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); + expect(MockLinking.openURL).toBeCalledWith(expectedUrl); + }); +}); test('urlHandler_canOpenUrlSetToFalseAndUrlHandlerReturnsFalse_openUrlNotCalled', async () => { - Iterable.logger.log('urlHandler_canOpenUrlSetToFalseAndUrlHandlerReturnsFalse_openUrlNotCalled') + Iterable.logger.log( + 'urlHandler_canOpenUrlSetToFalseAndUrlHandlerReturnsFalse_openUrlNotCalled' + ); // sets up event emitter - const nativeEmitter = new NativeEventEmitter() - nativeEmitter.removeAllListeners(EventName.handleUrlCalled) + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(EventName.handleUrlCalled); // sets up config file and urlHandler function // urlHandler set to return false - const config = new IterableConfig() - config.urlHandler = jest.fn((url: string, _: IterableActionContext) => { - return false - }) + const config = new IterableConfig(); + config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { + return false; + }); // initialize Iterable object - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Iterable.initialize('apiKey', config) + + Iterable.initialize('apiKey', config); // GIVEN canOpenUrl set to return a promise that resolves to false MockLinking.canOpenURL = jest.fn(async () => { - return await new Promise(resolve => { resolve(false) }) - }) - MockLinking.openURL.mockReset() - const expectedUrl = 'https://somewhere.com' - - const actionDict = { type: 'openUrl' } - const dict = { url: expectedUrl, context: { action: actionDict, source: 'inApp' } } + return await new Promise((resolve) => { + resolve(false); + }); + }); + MockLinking.openURL.mockReset(); + const expectedUrl = 'https://somewhere.com'; + + const actionDict = { type: 'openUrl' }; + const dict = { + url: expectedUrl, + context: { action: actionDict, source: 'inApp' }, + }; // WHEN handleUrlCalled event is emitted - nativeEmitter.emit(EventName.handleUrlCalled, dict) + nativeEmitter.emit(EventName.handleUrlCalled, dict); // THEN urlHandler is called and MockLinking.openURL is not called return await TestHelper.delayed(0, () => { - expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context) - expect(MockLinking.openURL).not.toBeCalled() - }) -}) + expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); + expect(MockLinking.openURL).not.toBeCalled(); + }); +}); test('urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsTrue_openUrlNotCalled', async () => { - Iterable.logger.log('urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsTrue_openUrlNotCalled') + Iterable.logger.log( + 'urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsTrue_openUrlNotCalled' + ); // sets up event emitter - const nativeEmitter = new NativeEventEmitter() - nativeEmitter.removeAllListeners(EventName.handleUrlCalled) + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(EventName.handleUrlCalled); // sets up config file and urlHandler function // urlHandler set to return true - const config = new IterableConfig() - config.urlHandler = jest.fn((url: string, _: IterableActionContext) => { - return true - }) + const config = new IterableConfig(); + config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { + return true; + }); // initialize Iterable object - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Iterable.initialize('apiKey', config) + + Iterable.initialize('apiKey', config); // GIVEN canOpenUrl set to return a promise that resolves to true MockLinking.canOpenURL = jest.fn(async () => { - return await new Promise(resolve => { resolve(true) }) - }) - MockLinking.openURL.mockReset() - const expectedUrl = 'https://somewhere.com' - - const actionDict = { type: 'openUrl' } - const dict = { url: expectedUrl, context: { action: actionDict, source: 'inApp' } } + return await new Promise((resolve) => { + resolve(true); + }); + }); + MockLinking.openURL.mockReset(); + const expectedUrl = 'https://somewhere.com'; + + const actionDict = { type: 'openUrl' }; + const dict = { + url: expectedUrl, + context: { action: actionDict, source: 'inApp' }, + }; // WHEN handleUrlCalled event is emitted - nativeEmitter.emit(EventName.handleUrlCalled, dict) + nativeEmitter.emit(EventName.handleUrlCalled, dict); // THEN urlHandler is called and MockLinking.openURL is not called return await TestHelper.delayed(0, () => { - expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context) - expect(MockLinking.openURL).not.toBeCalled() - }) -}) + expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); + expect(MockLinking.openURL).not.toBeCalled(); + }); +}); test('customActionHandler_actionNameAndActionData_customActionHandlerCalled', () => { - Iterable.logger.log('customActionHandler_actionNameAndActionData_customActionHandlerCalled') + Iterable.logger.log( + 'customActionHandler_actionNameAndActionData_customActionHandlerCalled' + ); // sets up event emitter - const nativeEmitter = new NativeEventEmitter() - nativeEmitter.removeAllListeners(EventName.handleCustomActionCalled) + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(EventName.handleCustomActionCalled); // sets up config file and customActionHandler function // customActionHandler set to return true - const config = new IterableConfig() - config.customActionHandler = jest.fn((action: IterableAction, context: IterableActionContext) => { - return true - }) + const config = new IterableConfig(); + config.customActionHandler = jest.fn( + (_action: IterableAction, _context: IterableActionContext) => { + return true; + } + ); // initialize Iterable object - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Iterable.initialize('apiKey', config) + + Iterable.initialize('apiKey', config); // GIVEN custom action name and custom action data - const actionName = 'zeeActionName' - const actionData = 'zeeActionData' - const actionDict = { type: actionName, data: actionData } - const actionSource = IterableActionSource.inApp - const dict = { action: actionDict, context: { action: actionDict, source: IterableActionSource.inApp } } + const actionName = 'zeeActionName'; + const actionData = 'zeeActionData'; + const actionDict = { type: actionName, data: actionData }; + const actionSource = IterableActionSource.inApp; + const dict = { + action: actionDict, + context: { action: actionDict, source: IterableActionSource.inApp }, + }; // WHEN handleCustomActionCalled event is emitted - nativeEmitter.emit(EventName.handleCustomActionCalled, dict) + nativeEmitter.emit(EventName.handleCustomActionCalled, dict); // THEN customActionHandler is called with expected action and expected context - const expectedAction = new IterableAction(actionName, actionData) - const expectedContext = new IterableActionContext(expectedAction, actionSource) - expect(config.customActionHandler).toBeCalledWith(expectedAction, expectedContext) -}) + const expectedAction = new IterableAction(actionName, actionData); + const expectedContext = new IterableActionContext( + expectedAction, + actionSource + ); + expect(config.customActionHandler).toBeCalledWith( + expectedAction, + expectedContext + ); +}); test('handleAppLink_link_methodCalled', () => { - Iterable.logger.log('handleAppLink_link_methodCalled') + Iterable.logger.log('handleAppLink_link_methodCalled'); // GIVEN a link - const link = 'https://somewhere.com/link/something' + const link = 'https://somewhere.com/link/something'; // WHEN Iterable.handleAppLink is called - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Iterable.handleAppLink(link) + + Iterable.handleAppLink(link); // THEN corresponding function is called on RNITerableAPI - expect(MockRNIterableAPI.handleAppLink).toBeCalledWith(link) -}) + expect(MockRNIterableAPI.handleAppLink).toBeCalledWith(link); +}); test('updateSubscriptions_params_methodCalled', () => { - Iterable.logger.log('update subscriptions is called') + Iterable.logger.log('update subscriptions is called'); // GIVEN the following parameters - const emailListIds = [1, 2, 3] - const unsubscribedChannelIds = [4, 5, 6] - const unsubscribedMessageTypeIds = [7, 8] - const subscribedMessageTypeIds = [9] - const campaignId = 10 - const templateId = 11 + const emailListIds = [1, 2, 3]; + const unsubscribedChannelIds = [4, 5, 6]; + const unsubscribedMessageTypeIds = [7, 8]; + const subscribedMessageTypeIds = [9]; + const campaignId = 10; + const templateId = 11; // WHEN Iterable.updateSubscriptions is called Iterable.updateSubscriptions( @@ -449,8 +515,15 @@ test('updateSubscriptions_params_methodCalled', () => { subscribedMessageTypeIds, campaignId, templateId - ) + ); // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateSubscriptions).toBeCalledWith(emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, templateId) -}) + expect(MockRNIterableAPI.updateSubscriptions).toBeCalledWith( + emailListIds, + unsubscribedChannelIds, + unsubscribedMessageTypeIds, + subscribedMessageTypeIds, + campaignId, + templateId + ); +}); diff --git a/src/__tests__/IterableInApp.spec.ts b/src/__tests__/IterableInApp.spec.ts index 1600a9805..a054d9653 100644 --- a/src/__tests__/IterableInApp.spec.ts +++ b/src/__tests__/IterableInApp.spec.ts @@ -1,15 +1,10 @@ -import { MockRNIterableAPI } from '../__mocks__/MockRNIterableAPI' -import { NativeEventEmitter } from 'react-native' +import { NativeEventEmitter } from 'react-native'; -import { - Iterable, - EventName -} from '../Iterable' - -import IterableConfig from '../IterableConfig' - -import IterableInAppMessage from '../IterableInAppMessage' +import { MockRNIterableAPI } from '../__mocks__/MockRNIterableAPI'; +import { Iterable, EventName } from '../Iterable'; +import IterableConfig from '../IterableConfig'; +import IterableInAppMessage from '../IterableInAppMessage'; import { IterableInAppLocation, IterableInAppTrigger, @@ -17,184 +12,272 @@ import { IterableInboxMetadata, IterableInAppCloseSource, IterableInAppShowResponse, - IterableInAppDeleteSource -} from '../IterableInAppClasses' -import { IterableLogger } from '../IterableLogger' + IterableInAppDeleteSource, +} from '../IterableInAppClasses'; +import { IterableLogger } from '../IterableLogger'; beforeEach(() => { - jest.clearAllMocks() - Iterable.logger = new IterableLogger(new IterableConfig()) -}) + jest.clearAllMocks(); + Iterable.logger = new IterableLogger(new IterableConfig()); +}); test('trackInAppOpen_params_methodCalledWithParams', () => { // GIVEN an in-app message and a location - const msg: IterableInAppMessage = new IterableInAppMessage('someMessageId', 123, new IterableInAppTrigger(IterableInAppTriggerType.event), new Date(1234), new Date(123123), true, new IterableInboxMetadata('title', 'subtitle', 'iconURL'), { 'CustomPayloadKey': 'CustomPayloadValue' }, false, 300.5) - const location: IterableInAppLocation = IterableInAppLocation.inApp + const msg: IterableInAppMessage = new IterableInAppMessage( + 'someMessageId', + 123, + new IterableInAppTrigger(IterableInAppTriggerType.event), + new Date(1234), + new Date(123123), + true, + new IterableInboxMetadata('title', 'subtitle', 'iconURL'), + { CustomPayloadKey: 'CustomPayloadValue' }, + false, + 300.5 + ); + const location: IterableInAppLocation = IterableInAppLocation.inApp; // WHEN Iterable.trackInAppOpen is called - Iterable.trackInAppOpen(msg, location) + Iterable.trackInAppOpen(msg, location); // THEN corresponding method is called on MockIterableAPI with appropriate parameters - expect(MockRNIterableAPI.trackInAppOpen).toBeCalledWith(msg.messageId, location) -}) + expect(MockRNIterableAPI.trackInAppOpen).toBeCalledWith( + msg.messageId, + location + ); +}); test('trackInAppClick_params_methodCalledWithParams', () => { // GIVEN an in-app message, a location, and a url - const msg: IterableInAppMessage = new IterableInAppMessage('someMessageId', 123, new IterableInAppTrigger(IterableInAppTriggerType.event), new Date(1234), new Date(123123), true, new IterableInboxMetadata('title', 'subtitle', 'iconURL'), { 'CustomPayloadKey': 'CustomPayloadValue' }, false, 300.5) - const location: IterableInAppLocation = IterableInAppLocation.inApp - const url: string = 'URLClicked' + const msg: IterableInAppMessage = new IterableInAppMessage( + 'someMessageId', + 123, + new IterableInAppTrigger(IterableInAppTriggerType.event), + new Date(1234), + new Date(123123), + true, + new IterableInboxMetadata('title', 'subtitle', 'iconURL'), + { CustomPayloadKey: 'CustomPayloadValue' }, + false, + 300.5 + ); + const location: IterableInAppLocation = IterableInAppLocation.inApp; + const url: string = 'URLClicked'; // WHEN Iterable.trackInAppClick is called - Iterable.trackInAppClick(msg, location, url) + Iterable.trackInAppClick(msg, location, url); // THEN corresponding method is called on MockIterableAPI with appropriate parameters - expect(MockRNIterableAPI.trackInAppClick).toBeCalledWith(msg.messageId, location, url) -}) + expect(MockRNIterableAPI.trackInAppClick).toBeCalledWith( + msg.messageId, + location, + url + ); +}); test('trackInAppClose_params_methodCalledWithParams', () => { // GIVEN an in-app messsage, a location, a close source, and a url - const msg: IterableInAppMessage = new IterableInAppMessage('someMessageId', 123, new IterableInAppTrigger(IterableInAppTriggerType.event), new Date(1234), new Date(123123), true, new IterableInboxMetadata('title', 'subtitle', 'iconURL'), { 'CustomPayloadKey': 'CustomPayloadValue' }, false, 300.5) - const location: IterableInAppLocation = IterableInAppLocation.inbox - const source: IterableInAppCloseSource = IterableInAppCloseSource.link - const url: string = 'ClickedURL' + const msg: IterableInAppMessage = new IterableInAppMessage( + 'someMessageId', + 123, + new IterableInAppTrigger(IterableInAppTriggerType.event), + new Date(1234), + new Date(123123), + true, + new IterableInboxMetadata('title', 'subtitle', 'iconURL'), + { CustomPayloadKey: 'CustomPayloadValue' }, + false, + 300.5 + ); + const location: IterableInAppLocation = IterableInAppLocation.inbox; + const source: IterableInAppCloseSource = IterableInAppCloseSource.link; + const url: string = 'ClickedURL'; // WHEN Iterable.trackInAppClose is called - Iterable.trackInAppClose(msg, location, source, url) + Iterable.trackInAppClose(msg, location, source, url); // THEN corresponding method is called on MockIterableAPI with appropriate parameters - expect(MockRNIterableAPI.trackInAppClose).toBeCalledWith(msg.messageId, location, source, url) -}) + expect(MockRNIterableAPI.trackInAppClose).toBeCalledWith( + msg.messageId, + location, + source, + url + ); +}); test('inAppConsume_params_methodCalledWithParams', () => { // GIVEN an in-app messsage, a location, and a delete source - const msg = new IterableInAppMessage('asdf', 1234, new IterableInAppTrigger(IterableInAppTriggerType.never), undefined, undefined, false, undefined, undefined, false, 300.5) - const location: IterableInAppLocation = IterableInAppLocation.inApp - const source: IterableInAppDeleteSource = IterableInAppDeleteSource.unknown + const msg = new IterableInAppMessage( + 'asdf', + 1234, + new IterableInAppTrigger(IterableInAppTriggerType.never), + undefined, + undefined, + false, + undefined, + undefined, + false, + 300.5 + ); + const location: IterableInAppLocation = IterableInAppLocation.inApp; + const source: IterableInAppDeleteSource = IterableInAppDeleteSource.unknown; // WHEN Iterable.inAppConsume is called - Iterable.inAppConsume(msg, location, source) + Iterable.inAppConsume(msg, location, source); // THEN corresponding method is called on MockIterableAPI with appropriate parameters - expect(MockRNIterableAPI.inAppConsume).toBeCalledWith(msg.messageId, location, source) -}) + expect(MockRNIterableAPI.inAppConsume).toBeCalledWith( + msg.messageId, + location, + source + ); +}); test('inAppHandler_messageAndEventEmitted_methodCalledWithMessage', () => { // sets up event emitter - const nativeEmitter = new NativeEventEmitter() - nativeEmitter.removeAllListeners(EventName.handleInAppCalled) + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(EventName.handleInAppCalled); // sets up config file and inAppHandler function - const config = new IterableConfig() - config.inAppHandler = jest.fn((message: IterableInAppMessage) => { - return IterableInAppShowResponse.show - }) + const config = new IterableConfig(); + config.inAppHandler = jest.fn((_message: IterableInAppMessage) => { + return IterableInAppShowResponse.show; + }); // initialize Iterable object - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Iterable.initialize('apiKey', config) + + Iterable.initialize('apiKey', config); // GIVEN an in-app message const messageDict = { messageId: 'message1', campaignId: 1234, trigger: { type: IterableInAppTriggerType.immediate }, - priorityLevel: 300.5 - } - const expectedMessage = new IterableInAppMessage('message1', 1234, new IterableInAppTrigger(IterableInAppTriggerType.immediate), undefined, undefined, false, undefined, undefined, false, 300.5) + priorityLevel: 300.5, + }; + const expectedMessage = new IterableInAppMessage( + 'message1', + 1234, + new IterableInAppTrigger(IterableInAppTriggerType.immediate), + undefined, + undefined, + false, + undefined, + undefined, + false, + 300.5 + ); // WHEN handleInAppCalled event is emitted - nativeEmitter.emit(EventName.handleInAppCalled, messageDict) + nativeEmitter.emit(EventName.handleInAppCalled, messageDict); // THEN inAppHandler and MockRNIterableAPI.setInAppShowResponse is called with message - expect(config.inAppHandler) - expect(config.inAppHandler).toBeCalledWith(expectedMessage) - expect(MockRNIterableAPI.setInAppShowResponse).toBeCalledWith(IterableInAppShowResponse.show) -}) + expect(config.inAppHandler).toBeCalledWith(expectedMessage); + expect(MockRNIterableAPI.setInAppShowResponse).toBeCalledWith( + IterableInAppShowResponse.show + ); +}); test('getMessages_noParams_returnsMessages', async () => { // GIVEN a list of in-app messages representing the local queue - const messageDicts = [{ - messageId: 'message1', - campaignId: 1234, - trigger: { type: IterableInAppTriggerType.immediate } - }, { - messageId: 'message2', - campaignId: 2345, - trigger: { type: IterableInAppTriggerType.never } - }] - const messages = messageDicts.map(message => IterableInAppMessage.fromDict(message)) + const messageDicts = [ + { + messageId: 'message1', + campaignId: 1234, + trigger: { type: IterableInAppTriggerType.immediate }, + }, + { + messageId: 'message2', + campaignId: 2345, + trigger: { type: IterableInAppTriggerType.never }, + }, + ]; + const messages = messageDicts.map((message) => + IterableInAppMessage.fromDict(message) + ); // WHEN the simulated local queue is set to the in-app messages - MockRNIterableAPI.setMessages(messages) + MockRNIterableAPI.setMessages(messages); // THEN Iterable,inAppManager.getMessages returns the list of in-app messages - return await Iterable.inAppManager.getMessages().then(messagesObtained => { - expect(messagesObtained).toEqual(messages) - }) -}) + return await Iterable.inAppManager.getMessages().then((messagesObtained) => { + expect(messagesObtained).toEqual(messages); + }); +}); test('showMessage_messageAndConsume_returnsClickedUrl', async () => { // GIVEN an in-app message and a clicked url const messageDict = { messageId: 'message1', campaignId: 1234, - trigger: { type: IterableInAppTriggerType.immediate } - } - const message: IterableInAppMessage = IterableInAppMessage.fromDict(messageDict) - const consume: boolean = true - const clickedUrl: string = 'testUrl' + trigger: { type: IterableInAppTriggerType.immediate }, + }; + const message: IterableInAppMessage = + IterableInAppMessage.fromDict(messageDict); + const consume: boolean = true; + const clickedUrl: string = 'testUrl'; // WHEN the simulated clicked url is set to the clicked url - MockRNIterableAPI.setClickedUrl(clickedUrl) + MockRNIterableAPI.setClickedUrl(clickedUrl); // THEN Iterable,inAppManager.showMessage returns the simulated clicked url - return await Iterable.inAppManager.showMessage(message, consume).then(url => { - expect(url).toEqual(clickedUrl) - }) -}) + return await Iterable.inAppManager + .showMessage(message, consume) + .then((url) => { + expect(url).toEqual(clickedUrl); + }); +}); test('removeMessage_params_methodCalledWithParams', () => { // GIVEN an in-app message const messageDict = { messageId: 'message1', campaignId: 1234, - trigger: { type: IterableInAppTriggerType.immediate } - } - const message = IterableInAppMessage.fromDict(messageDict) - const location: IterableInAppLocation = IterableInAppLocation.inApp - const source: IterableInAppDeleteSource = IterableInAppDeleteSource.deleteButton + trigger: { type: IterableInAppTriggerType.immediate }, + }; + const message = IterableInAppMessage.fromDict(messageDict); + const location: IterableInAppLocation = IterableInAppLocation.inApp; + const source: IterableInAppDeleteSource = + IterableInAppDeleteSource.deleteButton; // WHEN Iterable.inAppManager.removeMessage is called - Iterable.inAppManager.removeMessage(message, location, source) + Iterable.inAppManager.removeMessage(message, location, source); // THEN corresponding method is called on MockIterableAPI with appropriate parameters - expect(MockRNIterableAPI.removeMessage).toBeCalledWith(message.messageId, location, source) -}) + expect(MockRNIterableAPI.removeMessage).toBeCalledWith( + message.messageId, + location, + source + ); +}); test('setReadForMessage_params_methodCalledWithParams', () => { // GIVEN an in-app message const messageDict = { messageId: 'message1', campaignId: 1234, - trigger: { type: IterableInAppTriggerType.immediate } - } - const message = IterableInAppMessage.fromDict(messageDict) - const read: boolean = true + trigger: { type: IterableInAppTriggerType.immediate }, + }; + const message = IterableInAppMessage.fromDict(messageDict); + const read: boolean = true; // WHEN Iterable.inAppManager.setReadForMessage is called - Iterable.inAppManager.setReadForMessage(message, read) + Iterable.inAppManager.setReadForMessage(message, read); // THEN corresponding method is called on MockRNIterableAPI with appropriate parameters - expect(MockRNIterableAPI.setReadForMessage).toBeCalledWith(message.messageId, read) -}) + expect(MockRNIterableAPI.setReadForMessage).toBeCalledWith( + message.messageId, + read + ); +}); test('setAutoDisplayPaused_params_methodCalledWithParams', () => { // GIVEN paused flag - const paused: boolean = true + const paused: boolean = true; // WHEN Iterable.inAppManager.setAutoDisplayPaused is called - Iterable.inAppManager.setAutoDisplayPaused(paused) + Iterable.inAppManager.setAutoDisplayPaused(paused); // THEN corresponding method is called on MockRNIterableAPI with appropriate parameters - expect(MockRNIterableAPI.setAutoDisplayPaused).toBeCalledWith(paused) -}) + expect(MockRNIterableAPI.setAutoDisplayPaused).toBeCalledWith(paused); +}); diff --git a/src/__tests__/TestHelper.ts b/src/__tests__/TestHelper.ts index ae43cb0a2..f56010282 100644 --- a/src/__tests__/TestHelper.ts +++ b/src/__tests__/TestHelper.ts @@ -1,9 +1,10 @@ -// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class TestHelper { - static async delayed (delay: number, fn: () => void): Promise { - return await new Promise(resolve => setTimeout(() => { - fn() - resolve() - }, delay)) + static async delayed(delay: number, fn: () => void): Promise { + return await new Promise((resolve) => + setTimeout(() => { + fn(); + resolve(); + }, delay) + ); } } From adc6b929d8d64b8e6a788166f8fd9eba080b98b4 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 30 Sep 2024 15:55:12 -0700 Subject: [PATCH 20/30] fixed index file --- src/index.tsx | 77 +++++++++++++-------------------------------------- 1 file changed, 19 insertions(+), 58 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index b73001bce..3490edcd7 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,72 +2,33 @@ * React Native module for Iterable. * @module react-native-iterable-sdk */ - -import { Iterable, IterableCommerceItem } from './Iterable'; -import IterableInAppManager from './IterableInAppManager'; - -import { +export type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; +export type { InboxRowViewModel } from './InboxRowViewModel'; +export { Iterable, IterableCommerceItem } from './Iterable'; +export { IterableAction, IterableActionContext, IterableLogLevel, } from './IterableAction'; - -import { - IterableInAppShowResponse, - type IterableInAppContent, - IterableInAppTriggerType, - IterableInAppTrigger, - IterableInAppContentType, +export { IterableConfig } from './IterableConfig'; +export { IterableDataRegion } from './IterableDataRegion'; +export { IterableEdgeInsets, IterableHtmlInAppContent, - IterableInboxMetadata, - IterableInAppLocation, IterableInAppCloseSource, + IterableInAppContentType, IterableInAppDeleteSource, -} from './IterableInAppClasses'; - -import type { InboxRowViewModel } from './InboxRowViewModel'; -import IterableInboxCustomizations from './IterableInboxCustomizations'; -import IterableInboxEmptyState from './IterableInboxEmptyState'; -import IterableInboxMessageCell from './IterableInboxMessageCell'; - -import IterableInAppMessage from './IterableInAppMessage'; - -import useAppStateListener from './useAppStateListener'; -import useDeviceOrientation from './useDeviceOrientation'; -import InboxImpressionRowInfo from './InboxImpressionRowInfo'; - -import IterableConfig from './IterableConfig'; -import { IterableDataRegion } from './IterableDataRegion'; - -export { - Iterable, - IterableCommerceItem, - IterableConfig, - IterableInAppManager, - IterableAction, - IterableActionContext, - IterableLogLevel, + IterableInAppLocation, IterableInAppShowResponse, - IterableInAppTriggerType, IterableInAppTrigger, - IterableInAppContentType, - IterableEdgeInsets, - IterableHtmlInAppContent, + IterableInAppTriggerType, IterableInboxMetadata, - IterableInAppLocation, - IterableInAppMessage, - IterableInAppCloseSource, - IterableInAppDeleteSource, - IterableInboxEmptyState, - IterableInboxMessageCell, - useAppStateListener, - useDeviceOrientation, - IterableDataRegion, -}; -export type { - IterableInAppContent, - IterableInboxCustomizations, - InboxRowViewModel, - InboxImpressionRowInfo, -}; + type IterableInAppContent, +} from './IterableInAppClasses'; +export { IterableInAppManager } from './IterableInAppManager'; +export { IterableInAppMessage } from './IterableInAppMessage'; +export type { IterableInboxCustomizations } from './IterableInboxCustomizations'; +export { IterableInboxEmptyState } from './IterableInboxEmptyState'; +export { IterableInboxMessageCell } from './IterableInboxMessageCell'; +export { useAppStateListener } from './useAppStateListener'; +export { useDeviceOrientation } from './useDeviceOrientation'; From 2ea5f808ef73d8993db01b8e8d6e79fd25a1c640 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 1 Oct 2024 12:32:02 -0700 Subject: [PATCH 21/30] Added last of linting fixes --- src/Iterable.ts | 6 ++ src/IterableInAppMessage.ts | 2 + src/IterableInboxDataModel.ts | 6 +- src/__mocks__/MockLinking.ts | 9 +- src/__mocks__/MockRNIterableAPI.ts | 134 +++++++++++++++-------------- src/__mocks__/jest.setup.ts | 16 ++-- 6 files changed, 94 insertions(+), 79 deletions(-) diff --git a/src/Iterable.ts b/src/Iterable.ts index 18b16d3cd..7a1d2e436 100644 --- a/src/Iterable.ts +++ b/src/Iterable.ts @@ -1,3 +1,7 @@ +/** + * TODO: Split into seperate files + */ + import { NativeModules, NativeEventEmitter, @@ -738,6 +742,8 @@ export class Iterable { } function callUrlHandler(url: any, context: IterableActionContext) { + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq if (Iterable.savedConfig.urlHandler!(url, context) == false) { Linking.canOpenURL(url) .then((canOpen) => { diff --git a/src/IterableInAppMessage.ts b/src/IterableInAppMessage.ts index fb1ebce20..816b250f7 100644 --- a/src/IterableInAppMessage.ts +++ b/src/IterableInAppMessage.ts @@ -105,6 +105,8 @@ export class IterableInAppMessage { isSilentInbox(): boolean { return ( + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq this.saveToInbox && this.trigger.type == IterableInAppTriggerType.never ); } diff --git a/src/IterableInboxDataModel.ts b/src/IterableInboxDataModel.ts index 164793e9d..c197e479f 100644 --- a/src/IterableInboxDataModel.ts +++ b/src/IterableInboxDataModel.ts @@ -122,7 +122,7 @@ export class IterableInboxDataModel { let createdAt; if (typeof message.createdAt === 'string') { - createdAt = new Date(parseInt(message.createdAt)); + createdAt = new Date(parseInt(message.createdAt, 10)); } else { createdAt = new Date(message.createdAt); } @@ -145,10 +145,14 @@ export class IterableInboxDataModel { ): Array { var sortedFilteredMessages = messages.slice(); + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq if (this.filterFn != undefined) { sortedFilteredMessages = sortedFilteredMessages.filter(this.filterFn); } + // TODO: Figure out if this is purposeful + // eslint-disable-next-line eqeqeq if (this.comparatorFn != undefined) { sortedFilteredMessages.sort(this.comparatorFn); } else { diff --git a/src/__mocks__/MockLinking.ts b/src/__mocks__/MockLinking.ts index 77ea9733d..568cce756 100644 --- a/src/__mocks__/MockLinking.ts +++ b/src/__mocks__/MockLinking.ts @@ -1,8 +1,7 @@ -// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class MockLinking { - static canOpenURL = jest.fn() - static openURL = jest.fn() + static canOpenURL = jest.fn(); + static openURL = jest.fn(); - static addEventListener = jest.fn() - static removeEventListener = jest.fn() + static addEventListener = jest.fn(); + static removeEventListener = jest.fn(); } diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index 39e6a18e9..03c3edac6 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -1,111 +1,115 @@ -import { IterableAttributionInfo } from '../Iterable' -import IterableInAppMessage from '../IterableInAppMessage' +import { IterableAttributionInfo } from '../Iterable'; +import IterableInAppMessage from '../IterableInAppMessage'; -// eslint-disable-next-line @typescript-eslint/no-extraneous-class export class MockRNIterableAPI { - static email?: string - static userId?: string - static token?: string - static lastPushPayload?: any - static attributionInfo?: IterableAttributionInfo - static messages?: IterableInAppMessage[] - static clickedUrl?: string - - static async getEmail (): Promise { - return await new Promise((resolve, reject) => { - resolve(MockRNIterableAPI.email) - }) + static email?: string; + static userId?: string; + static token?: string; + static lastPushPayload?: any; + static attributionInfo?: IterableAttributionInfo; + static messages?: IterableInAppMessage[]; + static clickedUrl?: string; + + static async getEmail(): Promise { + return await new Promise((resolve) => { + resolve(MockRNIterableAPI.email); + }); } - static setEmail (email: string, authToken?: string | undefined): void { - MockRNIterableAPI.email = email - MockRNIterableAPI.token = authToken + static setEmail(email: string, authToken?: string | undefined): void { + MockRNIterableAPI.email = email; + MockRNIterableAPI.token = authToken; } - static async getUserId (): Promise { - return await new Promise((resolve, reject) => { - resolve(MockRNIterableAPI.userId) - }) + static async getUserId(): Promise { + return await new Promise((resolve) => { + resolve(MockRNIterableAPI.userId); + }); } - static setUserId (userId: string, authToken?: string | undefined): void { - MockRNIterableAPI.userId = userId - MockRNIterableAPI.token = authToken + static setUserId(userId: string, authToken?: string | undefined): void { + MockRNIterableAPI.userId = userId; + MockRNIterableAPI.token = authToken; } - static disableDeviceForCurrentUser = jest.fn() + static disableDeviceForCurrentUser = jest.fn(); - static trackPushOpenWithCampaignId = jest.fn() + static trackPushOpenWithCampaignId = jest.fn(); - static updateCart = jest.fn() + static updateCart = jest.fn(); - static trackPurchase = jest.fn() + static trackPurchase = jest.fn(); - static trackInAppOpen = jest.fn() + static trackInAppOpen = jest.fn(); - static trackInAppClick = jest.fn() + static trackInAppClick = jest.fn(); - static trackInAppClose = jest.fn() + static trackInAppClose = jest.fn(); - static trackEvent = jest.fn() + static trackEvent = jest.fn(); - static async getLastPushPayload (): Promise { - return await new Promise((resolve, reject) => { - resolve(MockRNIterableAPI.lastPushPayload) - }) + static async getLastPushPayload(): Promise { + return await new Promise((resolve) => { + resolve(MockRNIterableAPI.lastPushPayload); + }); } - static async getAttributionInfo (): Promise { - return await new Promise((resolve, reject) => { - resolve(MockRNIterableAPI.attributionInfo) - }) + static async getAttributionInfo(): Promise< + IterableAttributionInfo | undefined + > { + return await new Promise((resolve) => { + resolve(MockRNIterableAPI.attributionInfo); + }); } - static setAttributionInfo (attributionInfo?: IterableAttributionInfo): void { - MockRNIterableAPI.attributionInfo = attributionInfo + static setAttributionInfo(attributionInfo?: IterableAttributionInfo): void { + MockRNIterableAPI.attributionInfo = attributionInfo; } - static initializeWithApiKey = jest.fn() + static initializeWithApiKey = jest.fn(); - static setInAppShowResponse = jest.fn() + static setInAppShowResponse = jest.fn(); - static async getInAppMessages (): Promise { - return await new Promise((resolve, reject) => { - resolve(MockRNIterableAPI.messages) - }) + static async getInAppMessages(): Promise { + return await new Promise((resolve) => { + resolve(MockRNIterableAPI.messages); + }); } - static setAutoDisplayPaused = jest.fn() + static setAutoDisplayPaused = jest.fn(); - static async showMessage (message: IterableInAppMessage, consume: boolean): Promise { - return await new Promise((resolve, reject) => { - resolve(MockRNIterableAPI.clickedUrl) - }) + static async showMessage( + _message: IterableInAppMessage, + _consume: boolean + ): Promise { + return await new Promise((resolve) => { + resolve(MockRNIterableAPI.clickedUrl); + }); } - static removeMessage = jest.fn() + static removeMessage = jest.fn(); - static setReadForMessage = jest.fn() + static setReadForMessage = jest.fn(); - static inAppConsume = jest.fn() + static inAppConsume = jest.fn(); - static updateUser = jest.fn() + static updateUser = jest.fn(); - static updateEmail = jest.fn() + static updateEmail = jest.fn(); - static handleAppLink = jest.fn() + static handleAppLink = jest.fn(); - static updateSubscriptions = jest.fn() + static updateSubscriptions = jest.fn(); // set messages function is to set the messages static property // this is for testing purposes only - static setMessages (messages: IterableInAppMessage[]): void { - MockRNIterableAPI.messages = messages + static setMessages(messages: IterableInAppMessage[]): void { + MockRNIterableAPI.messages = messages; } // setClickedUrl function is to set the messages static property // this is for testing purposes only - static setClickedUrl (clickedUrl: string): void { - MockRNIterableAPI.clickedUrl = clickedUrl + static setClickedUrl(clickedUrl: string): void { + MockRNIterableAPI.clickedUrl = clickedUrl; } } diff --git a/src/__mocks__/jest.setup.ts b/src/__mocks__/jest.setup.ts index 816bdf9e8..5be4d351c 100644 --- a/src/__mocks__/jest.setup.ts +++ b/src/__mocks__/jest.setup.ts @@ -1,8 +1,8 @@ -import { MockRNIterableAPI } from './MockRNIterableAPI' -import { MockLinking } from './MockLinking' +import { MockRNIterableAPI } from './MockRNIterableAPI'; +import { MockLinking } from './MockLinking'; -import * as ReactNative from 'react-native' -jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter.js') +import * as ReactNative from 'react-native'; +jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter.js'); jest.doMock('react-native', () => { // Extend ReactNative @@ -11,10 +11,10 @@ jest.doMock('react-native', () => { // Mock RNIterableAPI NativeModules: { ...ReactNative.NativeModules, - RNIterableAPI: MockRNIterableAPI + RNIterableAPI: MockRNIterableAPI, }, - Linking: MockLinking + Linking: MockLinking, }, ReactNative - ) -}) + ); +}); From 3a92b7ff633cbbd79bd6833c00b9e877ad48e0be Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 1 Oct 2024 12:41:37 -0700 Subject: [PATCH 22/30] Added peer dependencies --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index aa860d14e..b1a0a6352 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,11 @@ }, "peerDependencies": { "react": "*", - "react-native": "*" + "react-native": "*", + "@react-navigation/native": "^6.0.0", + "react-native-safe-area-context": "^4.0.0", + "react-native-vector-icons": "^10.0.0", + "react-native-webview": "^13.0.0" }, "workspaces": [ "example" From 350fe72be463e010c7d57b365dcfd3cdc54c76b0 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 1 Oct 2024 12:49:44 -0700 Subject: [PATCH 23/30] Refactor variable declarations to use "let" instead of "var" --- src/Iterable.ts | 2 +- src/IterableInboxDataModel.ts | 4 ++-- src/IterableInboxMessageList.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Iterable.ts b/src/Iterable.ts index 7a1d2e436..a318b1198 100644 --- a/src/Iterable.ts +++ b/src/Iterable.ts @@ -694,7 +694,7 @@ export class Iterable { } if (Iterable.savedConfig.authHandler) { - var authResponseCallback: AuthResponseCallback; + let authResponseCallback: AuthResponseCallback; RNEventEmitter.addListener(EventName.handleAuthCalled, () => { Iterable.savedConfig.authHandler!() .then((promiseResult) => { diff --git a/src/IterableInboxDataModel.ts b/src/IterableInboxDataModel.ts index c197e479f..62ec110b8 100644 --- a/src/IterableInboxDataModel.ts +++ b/src/IterableInboxDataModel.ts @@ -127,7 +127,7 @@ export class IterableInboxDataModel { createdAt = new Date(message.createdAt); } - var defaultDateString = `${createdAt.toLocaleDateString()} at ${createdAt.toLocaleTimeString()}`; + const defaultDateString = `${createdAt.toLocaleDateString()} at ${createdAt.toLocaleTimeString()}`; return defaultDateString; } @@ -143,7 +143,7 @@ export class IterableInboxDataModel { private sortAndFilter( messages: Array ): Array { - var sortedFilteredMessages = messages.slice(); + let sortedFilteredMessages = messages.slice(); // TODO: Figure out if this is purposeful // eslint-disable-next-line eqeqeq diff --git a/src/IterableInboxMessageList.tsx b/src/IterableInboxMessageList.tsx index ea8e01493..5352be2b2 100644 --- a/src/IterableInboxMessageList.tsx +++ b/src/IterableInboxMessageList.tsx @@ -65,7 +65,7 @@ export const IterableInboxMessageList = ({ viewTokens: Array ): Array { return viewTokens.map(function (viewToken) { - var inAppMessage = IterableInAppMessage.fromViewToken(viewToken); + const inAppMessage = IterableInAppMessage.fromViewToken(viewToken); const impression = { messageId: inAppMessage.messageId, From 1b00d33d5c7607590ac6897c2cc5d1990211ad95 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 8 Oct 2024 16:41:48 -0700 Subject: [PATCH 24/30] It's making me do this --- SampleApp/javascript/android/gradlew.bat | 200 +++++++++++------------ SampleApp/typescript/android/gradlew.bat | 200 +++++++++++------------ 2 files changed, 200 insertions(+), 200 deletions(-) diff --git a/SampleApp/javascript/android/gradlew.bat b/SampleApp/javascript/android/gradlew.bat index 15e1ee37a..9991c5032 100644 --- a/SampleApp/javascript/android/gradlew.bat +++ b/SampleApp/javascript/android/gradlew.bat @@ -1,100 +1,100 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/SampleApp/typescript/android/gradlew.bat b/SampleApp/typescript/android/gradlew.bat index 15e1ee37a..9991c5032 100644 --- a/SampleApp/typescript/android/gradlew.bat +++ b/SampleApp/typescript/android/gradlew.bat @@ -1,100 +1,100 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From f378df4c29c568b8dac6370d26816006d851c999 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 22 Oct 2024 15:43:16 -0700 Subject: [PATCH 25/30] fix package and peer dependency structure --- package.json | 18 ++++++++---------- yarn.lock | 24 ++++++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index b1a0a6352..06c74277d 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@commitlint/config-conventional": "^17.0.2", "@evilmartians/lefthook": "^1.5.0", "@react-native/eslint-config": "^0.73.1", + "@react-navigation/native": "^6.1.18", "@release-it/conventional-changelog": "^5.0.0", "@types/jest": "^29.5.5", "@types/react": "^18.2.44", @@ -81,6 +82,9 @@ "react": "18.3.1", "react-native": "0.75.3", "react-native-builder-bob": "^0.30.2", + "react-native-safe-area-context": "^4.11.1", + "react-native-vector-icons": "^10.2.0", + "react-native-webview": "^13.12.3", "release-it": "^15.0.0", "turbo": "^1.10.7", "typescript": "^5.2.2" @@ -89,12 +93,12 @@ "@types/react": "^18.2.44" }, "peerDependencies": { + "@react-navigation/native": "*", "react": "*", "react-native": "*", - "@react-navigation/native": "^6.0.0", - "react-native-safe-area-context": "^4.0.0", - "react-native-vector-icons": "^10.0.0", - "react-native-webview": "^13.0.0" + "react-native-safe-area-context": "*", + "react-native-vector-icons": "*", + "react-native-webview": "*" }, "workspaces": [ "example" @@ -189,11 +193,5 @@ "type": "module-legacy", "languages": "kotlin-swift", "version": "0.41.2" - }, - "dependencies": { - "@react-navigation/native": "^6.1.18", - "react-native-safe-area-context": "^4.11.0", - "react-native-vector-icons": "^10.2.0", - "react-native-webview": "^13.12.2" } } diff --git a/yarn.lock b/yarn.lock index c812d0a87..47a3423c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2021,15 +2021,19 @@ __metadata: react: 18.3.1 react-native: 0.75.3 react-native-builder-bob: ^0.30.2 - react-native-safe-area-context: ^4.11.0 + react-native-safe-area-context: ^4.11.1 react-native-vector-icons: ^10.2.0 - react-native-webview: ^13.12.2 + react-native-webview: ^13.12.3 release-it: ^15.0.0 turbo: ^1.10.7 typescript: ^5.2.2 peerDependencies: + "@react-navigation/native": "*" react: "*" react-native: "*" + react-native-safe-area-context: "*" + react-native-vector-icons: "*" + react-native-webview: "*" languageName: unknown linkType: soft @@ -10919,13 +10923,13 @@ __metadata: languageName: node linkType: hard -"react-native-safe-area-context@npm:^4.11.0": - version: 4.11.0 - resolution: "react-native-safe-area-context@npm:4.11.0" +"react-native-safe-area-context@npm:^4.11.1": + version: 4.11.1 + resolution: "react-native-safe-area-context@npm:4.11.1" peerDependencies: react: "*" react-native: "*" - checksum: 6d6af265ecf1b9cb45e042a335f2c5da472ad41eb5d4fda8e31a1607b344726f0f4f9501ef3a3a731cec3784ba93485b44cbdafbf5f13055caa130c703bbd307 + checksum: fddb4c72c8ec404602317a3d52c555a5f4173dcb94d8403c8368fb8a556ad41741f87419e052fa26e55a80732abb144f16bb6475d808dff392da5480ff7ec813 languageName: node linkType: hard @@ -10944,16 +10948,16 @@ __metadata: languageName: node linkType: hard -"react-native-webview@npm:^13.12.2": - version: 13.12.2 - resolution: "react-native-webview@npm:13.12.2" +"react-native-webview@npm:^13.12.3": + version: 13.12.3 + resolution: "react-native-webview@npm:13.12.3" dependencies: escape-string-regexp: ^4.0.0 invariant: 2.2.4 peerDependencies: react: "*" react-native: "*" - checksum: d84b2e2e75dc484eb40f8e285607b9686693f2d31e95802e2b358dc0820bcc294b438a77174eba157c31d95675c962c61c94885629af999416ad1355e9618fe0 + checksum: 94d824fedc6b5c0402926b99a96f71f4752416e4f038be827552015c0bf6d595d71ba656079618402662aec5e2c539018b0bff2906b6f95261f70893e5dd5d80 languageName: node linkType: hard From cf6aeef632d83bdc03c92d415dec733e418689f7 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 22 Oct 2024 15:50:26 -0700 Subject: [PATCH 26/30] Add exclusions for SampleApp, example and integration-testing in tsconfig. These will be removed as changes are made. --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index f9287edfb..4f306cc29 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,5 +25,6 @@ "strict": true, "target": "ESNext", "verbatimModuleSyntax": true - } + }, + "exclude": ["SampleApp/", "integration-testing/", "example/"] } From af6f4ae421b04e4cd31d217f70e8e5f0e0425948 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 22 Oct 2024 16:00:53 -0700 Subject: [PATCH 27/30] Refactor Jest configuration, update ESLint ignore list, and clean up IterablePushPlatform enum export --- jest.config.js | 4 ++-- package.json | 4 +++- src/IterablePushPlatform.ts | 13 +++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/jest.config.js b/jest.config.js index df5ed2418..092ec62b5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,5 +2,5 @@ module.exports = { preset: 'react-native', setupFiles: ['/ts/__mocks__/jest.setup.ts'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - testMatch: ['/ts/__tests__/**/*.(test|spec).[jt]s?(x)'] -} + testMatch: ['/ts/__tests__/**/*.(test|spec).[jt]s?(x)'], +}; diff --git a/package.json b/package.json index 06c74277d..03703b118 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,9 @@ }, "eslintIgnore": [ "node_modules/", - "lib/" + "lib/", + "integration-testing/", + "SampleApp/" ], "prettier": { "quoteProps": "consistent", diff --git a/src/IterablePushPlatform.ts b/src/IterablePushPlatform.ts index a3335e3f0..33fe63fef 100644 --- a/src/IterablePushPlatform.ts +++ b/src/IterablePushPlatform.ts @@ -1,10 +1,7 @@ - enum IterablePushPlatform { - sandbox = 0, - production = 1, - auto = 2 - } + sandbox = 0, + production = 1, + auto = 2, +} -export { - IterablePushPlatform -} \ No newline at end of file +export { IterablePushPlatform }; From 6d7f414a760e1474a02a105c74c03d8a87c54764 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 22 Oct 2024 16:04:50 -0700 Subject: [PATCH 28/30] Remove obsolete index_OLD.tsx file --- src/index_OLD.tsx | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/index_OLD.tsx diff --git a/src/index_OLD.tsx b/src/index_OLD.tsx deleted file mode 100644 index 723441d12..000000000 --- a/src/index_OLD.tsx +++ /dev/null @@ -1,22 +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'; - -const ReactNativeSdk = NativeModules.ReactNativeSdk - ? NativeModules.ReactNativeSdk - : new Proxy( - {}, - { - get() { - throw new Error(LINKING_ERROR); - }, - } - ); - -export function multiply(a: number, b: number): Promise { - return ReactNativeSdk.multiply(a, b); -} From 88792df2cd4fa71dc9dbe8a9ec3a5ed3e92410a0 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 14 Nov 2024 14:44:20 -0800 Subject: [PATCH 29/30] Refactor imports to use named imports for consistency and clarity, and to help with tree shaking --- src/Iterable.ts | 6 +++--- src/IterableAction.ts | 2 -- src/IterableConfig.ts | 4 +--- src/IterableDataRegion.ts | 2 -- src/IterableInAppManager.ts | 4 +--- src/IterableInAppMessage.ts | 4 +--- src/IterableInbox.tsx | 14 ++++++-------- src/IterableInboxDataModel.ts | 4 +--- src/IterableInboxEmptyState.tsx | 2 -- src/IterableInboxMessageCell.tsx | 4 +--- src/IterableInboxMessageDisplay.tsx | 2 -- src/IterableInboxMessageList.tsx | 8 +++----- src/IterableLogger.ts | 4 +--- src/IterableUtil.ts | 2 -- src/__mocks__/MockRNIterableAPI.ts | 2 +- src/__tests__/IterableInApp.spec.ts | 4 ++-- src/useAppStateListener.ts | 2 -- src/useDeviceOrientation.tsx | 2 -- 18 files changed, 21 insertions(+), 51 deletions(-) diff --git a/src/Iterable.ts b/src/Iterable.ts index a318b1198..50fc97c0a 100644 --- a/src/Iterable.ts +++ b/src/Iterable.ts @@ -15,9 +15,9 @@ import { IterableInAppDeleteSource, } from './IterableInAppClasses'; -import IterableInAppManager from './IterableInAppManager'; -import IterableInAppMessage from './IterableInAppMessage'; -import IterableConfig, { AuthResponse } from './IterableConfig'; +import { IterableInAppManager } from './IterableInAppManager'; +import { IterableInAppMessage } from './IterableInAppMessage'; +import { AuthResponse, IterableConfig } from './IterableConfig'; import { IterableLogger } from './IterableLogger'; const RNIterableAPI = NativeModules.RNIterableAPI; diff --git a/src/IterableAction.ts b/src/IterableAction.ts index c00daadbe..e68995344 100644 --- a/src/IterableAction.ts +++ b/src/IterableAction.ts @@ -41,5 +41,3 @@ export enum IterableLogLevel { info = 2, error = 3, } - -export default IterableAction; diff --git a/src/IterableConfig.ts b/src/IterableConfig.ts index 087ccb93d..ce17b5c7f 100644 --- a/src/IterableConfig.ts +++ b/src/IterableConfig.ts @@ -5,7 +5,7 @@ import { } from './IterableAction'; import { IterableDataRegion } from './IterableDataRegion'; import { IterableInAppShowResponse } from './IterableInAppClasses'; -import IterableInAppMessage from './IterableInAppMessage'; +import { IterableInAppMessage } from './IterableInAppMessage'; import { IterablePushPlatform } from './IterablePushPlatform'; // TODO: Add description @@ -171,5 +171,3 @@ export class AuthResponse { successCallback?: AuthCallBack; failureCallback?: AuthCallBack; } - -export default IterableConfig; diff --git a/src/IterableDataRegion.ts b/src/IterableDataRegion.ts index be7515fb9..c42e3002a 100644 --- a/src/IterableDataRegion.ts +++ b/src/IterableDataRegion.ts @@ -3,5 +3,3 @@ export enum IterableDataRegion { US = 0, EU = 1, } - -export default IterableDataRegion; diff --git a/src/IterableInAppManager.ts b/src/IterableInAppManager.ts index de2c0950b..f7370b78c 100644 --- a/src/IterableInAppManager.ts +++ b/src/IterableInAppManager.ts @@ -6,7 +6,7 @@ import { IterableInAppDeleteSource, IterableInAppLocation, } from './IterableInAppClasses'; -import IterableInAppMessage from './IterableInAppMessage'; +import { IterableInAppMessage } from './IterableInAppMessage'; // TODO: Create a loader for this const RNIterableAPI = NativeModules.RNIterableAPI; @@ -123,5 +123,3 @@ export class IterableInAppManager { RNIterableAPI.setAutoDisplayPaused(paused); } } - -export default IterableInAppManager; diff --git a/src/IterableInAppMessage.ts b/src/IterableInAppMessage.ts index 816b250f7..fb3030319 100644 --- a/src/IterableInAppMessage.ts +++ b/src/IterableInAppMessage.ts @@ -1,6 +1,6 @@ import { type ViewToken } from 'react-native'; -import IterableUtil from './IterableUtil'; +import { IterableUtil } from './IterableUtil'; import { IterableInAppTrigger, @@ -152,5 +152,3 @@ export class IterableInAppMessage { ); } } - -export default IterableInAppMessage; diff --git a/src/IterableInbox.tsx b/src/IterableInbox.tsx index 58e6912a0..06aca667e 100644 --- a/src/IterableInbox.tsx +++ b/src/IterableInbox.tsx @@ -19,12 +19,12 @@ import { IterableInAppLocation, } from './IterableInAppClasses'; import type { IterableInboxCustomizations } from './IterableInboxCustomizations'; -import IterableInboxDataModel from './IterableInboxDataModel'; -import IterableInboxEmptyState from './IterableInboxEmptyState'; -import IterableInboxMessageDisplay from './IterableInboxMessageDisplay'; -import IterableInboxMessageList from './IterableInboxMessageList'; -import useAppStateListener from './useAppStateListener'; -import useDeviceOrientation from './useDeviceOrientation'; +import { IterableInboxDataModel } from './IterableInboxDataModel'; +import { IterableInboxEmptyState } from './IterableInboxEmptyState'; +import { IterableInboxMessageDisplay } from './IterableInboxMessageDisplay'; +import { IterableInboxMessageList } from './IterableInboxMessageList'; +import { useAppStateListener } from './useAppStateListener'; +import { useDeviceOrientation } from './useDeviceOrientation'; const RNIterableAPI = NativeModules.RNIterableAPI; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); @@ -354,5 +354,3 @@ export const IterableInbox = ({ {inboxAnimatedView} ); }; - -export default IterableInbox; diff --git a/src/IterableInboxDataModel.ts b/src/IterableInboxDataModel.ts index 62ec110b8..29dce8e1a 100644 --- a/src/IterableInboxDataModel.ts +++ b/src/IterableInboxDataModel.ts @@ -9,7 +9,7 @@ import { Iterable } from './Iterable'; import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; import type { InboxRowViewModel } from './InboxRowViewModel'; -import IterableInAppMessage from './IterableInAppMessage'; +import { IterableInAppMessage } from './IterableInAppMessage'; const RNIterableAPI = NativeModules.RNIterableAPI; @@ -175,5 +175,3 @@ export class IterableInboxDataModel { }; } } - -export default IterableInboxDataModel; diff --git a/src/IterableInboxEmptyState.tsx b/src/IterableInboxEmptyState.tsx index d1c754764..6e6c885ed 100644 --- a/src/IterableInboxEmptyState.tsx +++ b/src/IterableInboxEmptyState.tsx @@ -68,5 +68,3 @@ const styles = StyleSheet.create({ color: 'grey', }, }); - -export default IterableInboxEmptyState; diff --git a/src/IterableInboxMessageCell.tsx b/src/IterableInboxMessageCell.tsx index 3fdbc7548..d2489b2db 100644 --- a/src/IterableInboxMessageCell.tsx +++ b/src/IterableInboxMessageCell.tsx @@ -14,7 +14,7 @@ import { import type { InboxRowViewModel } from './InboxRowViewModel'; import type { IterableInboxCustomizations } from './IterableInboxCustomizations'; -import IterableInboxDataModel from './IterableInboxDataModel'; +import { IterableInboxDataModel } from './IterableInboxDataModel'; // TODO: Change to component function defaultMessageListLayout( @@ -327,5 +327,3 @@ export const IterableInboxMessageCell = ({ ); }; - -export default IterableInboxMessageCell; diff --git a/src/IterableInboxMessageDisplay.tsx b/src/IterableInboxMessageDisplay.tsx index 65bd4f504..ca137b502 100644 --- a/src/IterableInboxMessageDisplay.tsx +++ b/src/IterableInboxMessageDisplay.tsx @@ -242,5 +242,3 @@ export const IterableInboxMessageDisplay = ({ ); }; - -export default IterableInboxMessageDisplay; diff --git a/src/IterableInboxMessageList.tsx b/src/IterableInboxMessageList.tsx index 5352be2b2..2993268df 100644 --- a/src/IterableInboxMessageList.tsx +++ b/src/IterableInboxMessageList.tsx @@ -3,10 +3,10 @@ import { type ViewabilityConfig, type ViewToken, FlatList } from 'react-native'; import type { InboxImpressionRowInfo } from './InboxImpressionRowInfo'; import type { InboxRowViewModel } from './InboxRowViewModel'; -import IterableInAppMessage from './IterableInAppMessage'; +import { IterableInAppMessage } from './IterableInAppMessage'; import type { IterableInboxCustomizations } from './IterableInboxCustomizations'; -import IterableInboxDataModel from './IterableInboxDataModel'; -import IterableInboxMessageCell from './IterableInboxMessageCell'; +import { IterableInboxDataModel } from './IterableInboxDataModel'; +import { IterableInboxMessageCell } from './IterableInboxMessageCell'; // TODO: Comment type MessageListProps = { @@ -114,5 +114,3 @@ export const IterableInboxMessageList = ({ /> ); }; - -export default IterableInboxMessageList; diff --git a/src/IterableLogger.ts b/src/IterableLogger.ts index 9346166e3..bc02410b0 100644 --- a/src/IterableLogger.ts +++ b/src/IterableLogger.ts @@ -1,4 +1,4 @@ -import IterableConfig from './IterableConfig'; +import { IterableConfig } from './IterableConfig'; // TODO: Add comments and descriptions export class IterableLogger { @@ -18,5 +18,3 @@ export class IterableLogger { } } } - -export default IterableLogger; diff --git a/src/IterableUtil.ts b/src/IterableUtil.ts index e2dc049bc..e160ce607 100644 --- a/src/IterableUtil.ts +++ b/src/IterableUtil.ts @@ -8,5 +8,3 @@ export class IterableUtil { } } } - -export default IterableUtil; diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index 03c3edac6..564cb26eb 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -1,5 +1,5 @@ import { IterableAttributionInfo } from '../Iterable'; -import IterableInAppMessage from '../IterableInAppMessage'; +import { IterableInAppMessage } from '../IterableInAppMessage'; export class MockRNIterableAPI { static email?: string; diff --git a/src/__tests__/IterableInApp.spec.ts b/src/__tests__/IterableInApp.spec.ts index a054d9653..894e0a8c2 100644 --- a/src/__tests__/IterableInApp.spec.ts +++ b/src/__tests__/IterableInApp.spec.ts @@ -3,8 +3,8 @@ import { NativeEventEmitter } from 'react-native'; import { MockRNIterableAPI } from '../__mocks__/MockRNIterableAPI'; import { Iterable, EventName } from '../Iterable'; -import IterableConfig from '../IterableConfig'; -import IterableInAppMessage from '../IterableInAppMessage'; +import { IterableConfig } from '../IterableConfig'; +import { IterableInAppMessage } from '../IterableInAppMessage'; import { IterableInAppLocation, IterableInAppTrigger, diff --git a/src/useAppStateListener.ts b/src/useAppStateListener.ts index 353150d96..1f6d432ee 100644 --- a/src/useAppStateListener.ts +++ b/src/useAppStateListener.ts @@ -25,5 +25,3 @@ export function useAppStateListener() { return appStateVisibility; } - -export default useAppStateListener; diff --git a/src/useDeviceOrientation.tsx b/src/useDeviceOrientation.tsx index 0003bab86..bbf2dfedf 100644 --- a/src/useDeviceOrientation.tsx +++ b/src/useDeviceOrientation.tsx @@ -15,5 +15,3 @@ export function useDeviceOrientation() { return { height, width, isPortrait }; } - -export default useDeviceOrientation; From 6eda0969864b00e22f5b714db36d49ceb4b4f81d Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 14 Nov 2024 14:47:20 -0800 Subject: [PATCH 30/30] Updated export structure for IterablePushPlatform --- src/IterablePushPlatform.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/IterablePushPlatform.ts b/src/IterablePushPlatform.ts index 33fe63fef..0375c3768 100644 --- a/src/IterablePushPlatform.ts +++ b/src/IterablePushPlatform.ts @@ -1,7 +1,5 @@ -enum IterablePushPlatform { +export enum IterablePushPlatform { sandbox = 0, production = 1, auto = 2, } - -export { IterablePushPlatform };