diff --git a/.eslintignore b/.eslintignore index 1dfe5c44..21158ead 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ +docs/ integration-testing/ lib/ node_modules/ \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 97dc99b3..e0f808c2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,7 +12,7 @@ module.exports = { overrides: [ { files: ['*.ts', '*.tsx'], - plugins: ['@typescript-eslint/eslint-plugin'], + plugins: ['@typescript-eslint/eslint-plugin', 'eslint-plugin-tsdoc'], extends: [ 'plugin:@typescript-eslint/recommended', // We need more verbose typing to enable the below rule, but we should @@ -33,6 +33,7 @@ module.exports = { 'error', { allow: ['/package\\.json$'] }, ], + 'tsdoc/syntax': 'warn', }, }, { diff --git a/.gitignore b/.gitignore index efd5c4e5..04d62dd4 100644 --- a/.gitignore +++ b/.gitignore @@ -84,4 +84,5 @@ android/generated # Iterable .env .xcode.env.local -coverage/ \ No newline at end of file +coverage/ +docs/ \ No newline at end of file diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 3908db4c..e5674f8e 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -28,7 +28,7 @@ interface IterableAppProps { config: IterableConfig | null; /** * Call to initialize the Iterable SDK - * @param navigation The navigation prop from the screen + * @param navigation - The navigation prop from the screen * @returns Promise Whether the initialization was successful */ initialize: (navigation: Navigation) => void; @@ -47,7 +47,7 @@ interface IterableAppProps { loginInProgress?: boolean; /** Logs the user out */ logout: () => void; - /** TODO: Ask @evantk91 or @Ayyanchira what this is for */ + /** Should the iterable inbox return to the list of messages? */ returnToInboxTrigger: boolean; /** Sets the API key for the user */ setApiKey: (value: string) => void; @@ -55,7 +55,7 @@ interface IterableAppProps { setIsInboxTab: (value: boolean) => void; /** Sets whether the login is in progress */ setLoginInProgress: (value: boolean) => void; - /** TODO: Ask @evantk91 or @Ayyanchira what this is for */ + /** Sets whether the iterable inbox should return to the list of messages */ setReturnToInboxTrigger: (value: boolean) => void; /** Sets the user ID for the user */ setUserId: (value: string) => void; diff --git a/package.json b/package.json index 880d202c..afe23f89 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "test:coverage": "jest --coverage", "typecheck": "tsc", "lint": "eslint \"**/*.{js,ts,tsx}\"", + "docs": "typedoc", "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "bob build", "release": "release-it" @@ -83,6 +84,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-jest": "^28.9.0", "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-tsdoc": "^0.3.0", "jest": "^29.7.0", "prettier": "^3.0.3", "react": "18.3.1", @@ -93,6 +95,9 @@ "react-native-webview": "^13.12.3", "release-it": "^15.0.0", "turbo": "^1.10.7", + "typedoc": "^0.26.11", + "typedoc-plugin-coverage": "^3.3.0", + "typedoc-plugin-mermaid": "^1.12.0", "typescript": "^5.2.2" }, "resolutions": { diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 0227893b..2254ae89 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -25,27 +25,71 @@ import { IterableLogger } from './IterableLogger'; const RNIterableAPI = NativeModules.RNIterableAPI; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); +/** + * @hideconstructor + * The main class for the Iterable React Native SDK. + * + * The majority of the high-level functionality can be accomplished through the + * static methods of this class. EG: initializing the SDK, logging in a user, + * tracking purchases, etc. + * + * @example + * // Initialize the SDK + * Iterable.initialize(YOUR_API_KEY, new IterableConfig()); + * + * // Log in a user + * Iterable.setEmail('my.email@company.com'); + * // OR + * Iterable.setUserId('myUserId'); + */ export class Iterable { + /** + * Manager for in app messages + */ static inAppManager = new IterableInAppManager(); + /** + * Logger for the Iterable SDK + * Log level is set with {@link IterableLogLevel} + */ static logger: IterableLogger; + /** + * Current configuration of the Iterable SDK + */ static savedConfig: IterableConfig; /** - * This static method is used to initialize the React Native SDK in your app's Javascript or Typescript code. + * Initializes the Iterable 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. + * Pass in an `IterableConfig` object with the various customization properties setup. + * + * **WARNING**: Never use server-side API keys with Iterable's mobile SDKs. + * Since API keys are, by necessity, distributed with the mobile apps that + * contain them, bad actors can potentially access them. For this reason, + * Iterable provides mobile API keys, which have minimal access. * - * Note: Use Iterable.initialize and NOT Iterable.initialize2, as Iterable.initialize2 is only used internally. + * @param apiKey - The [*mobile* API + * key](https://support.iterable.com/hc/en-us/articles/360043464871-API-Keys) + * for your application + * @param config - Configuration object for the SDK * - * @param {string} apiKey mobile API key provided with the application - * @param {IterableConfig} config config object with various properties + * @example + * Initializing the app could look like this: + * ```typescript + * // Create a new IterableConfig object + * const config = new IterableConfig(); + * + * // Set various properties on the config object + * config.logLevel = IterableLogLevel.debug; + * + * // Initialize the SDK with the API key and config object + * Iterable.initialize(API_KEY, config); + * ``` */ - static initialize( apiKey: string, config: IterableConfig = new IterableConfig() @@ -65,6 +109,8 @@ export class Iterable { /** * DO NOT CALL THIS METHOD. * This method is used internally to connect to staging environment. + * + * @internal */ static initialize2( apiKey: string, @@ -89,39 +135,54 @@ export class Iterable { } /** - * This static method associates the current user with the passed in email parameter. + * Associate 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.setEmail or Iterable.setUserId. + * Note: specify a user by calling `Iterable.setEmail` or + * `Iterable.setUserId`, but **NOT** both * - * User profile creation: + * @remarks + * 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`. * - * 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. + * ## User profile creation: * - * Registering device token: + * 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. * - * If IterableConfig.autoPushRegisteration is set to true, calling setEmail automatically registers the device for push - * notifications and sends the deviceId and token to Iterable. + * ## Registering device token: * - * Optional JWT token parameter: + * If `IterableConfig.autoPushRegisteration` is set to true, calling + * `setEmail` automatically registers the device for push notifications and + * sends the deviceId and token to Iterable. * - * 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. + * ## Optional JWT token parameter: * - * Signing out a user from the SDK: + * 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. * - * 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. + * ## Signing out a user from the SDK: * - * Note: specify a user by calling Iterable.setEmail or Iterable.setUserId, but NOT both. + * 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. * - * @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 + * @param email - Email address to associate with + * the current user + * @param authToken - Valid, pre-fetched JWT the SDK + * can use to authenticate API requests, optional - If null/undefined, no JWT + * related action will be taken + * + * @example + * ```typescript + * Iterable.setEmail('my.user.name@gmail.com'); + * ``` */ - static setEmail(email?: string | null, authToken?: string | null) { Iterable.logger.log('setEmail: ' + email); @@ -129,12 +190,15 @@ export class Iterable { } /** - * 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. + * Get the email associated with the current user. * - * parameters: none + * @example + * ```typescript + * Iterable.getEmail().then((email) => { + * // Do something with the email + * }); + * ``` */ - static getEmail(): Promise { Iterable.logger.log('getEmail'); @@ -142,39 +206,48 @@ export class Iterable { } /** - * This static method associates the current user with the passed in userId parameter. + * Associate 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.setEmail or Iterable.setUserId. + * Note: specify a user by calling `Iterable.setEmail` or + * `Iterable.setUserId`, but **NOT** both. * - * User profile creation: + * @remarks + * 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`. * - * 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. + * ## User profile creation: * - * Registering device token: + * 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 IterableConfig.autoPushRegisteration is set to true, calling setUserId automatically registers the device for push - * notifications and sends the deviceId and token to Iterable. + * ## Registering device token: * - * Optional JWT token parameter: + * If `IterableConfig.autoPushRegisteration` is set to `true`, calling + * setUserI`d automatically registers the device for push notifications and + * sends the `deviceId` and token to Iterable. * - * 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. + * ## Optional JWT token parameter: * - * Signing out a user from the SDK: + * 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. * - * 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. + * ## Signing out a user from the SDK: * - * Note: specify a user by calling Iterable.setEmail or Iterable.setUserId, but NOT both. + * 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. * - * 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 + * @param userId - User ID to associate with the current user + * @param 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); @@ -182,12 +255,15 @@ export class Iterable { } /** - * 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. + * Get the `userId` associated with the current user. * - * parameters: none + * @example + * ```typescript + * Iterable.getUserId().then((userId) => { + * // Do something with the userId + * }); + * ``` */ - static getUserId(): Promise { Iterable.logger.log('getUserId'); @@ -195,11 +271,13 @@ export class Iterable { } /** - * This static method disables the device's token for the current user. + * Disable the device's token for the current user. This will disable push notifications for the current user. * - * parameters: none + * @example + * ```typescript + * Iterable.disableDeviceForCurrentUser(); + * ``` */ - static disableDeviceForCurrentUser() { Iterable.logger.log('disableDeviceForCurrentUser'); @@ -207,14 +285,16 @@ export class Iterable { } /** - * This static method returns the payload of the last push notification with which the user + * Get 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 + * @example + * ```typescript + * Iterable.getLastPushPayload().then((payload) => { + * // Do something with the payload + * }); + * ``` */ - static getLastPushPayload(): Promise { Iterable.logger.log('getLastPushPayload'); @@ -222,17 +302,27 @@ export class Iterable { } /** - * This static method returns the attribution information stored. + * Get the stored attribution information -- possibly based on a recent deep link click. + * * 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. - * Use the keyword `then` to get the result of the promise. + * @see {@link IterableAttributionInfo} * - * parameters: none + * @example + * ```typescript + * Iterable.getAttributionInfo().then((attributionInfo) => { + * Iterable.updateSubscriptions( + * null, + * [33015, 33016, 33018], + * null, + * null, + * attributionInfo.campaignId, + * attributionInfo.templateId + * ); + * }); + * ``` */ - static getAttributionInfo(): Promise { Iterable.logger.log('getAttributionInfo'); @@ -252,17 +342,29 @@ export class Iterable { } /** - * This static method manually sets the current attribution information stored. + * Manually set the current stored attribution information so that it can later be used when tracking events. + * * 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. + * + * @see {@link IterableAttributionInfo} * * For deep link clicks, Iterable sets attribution information automatically. * However, use this method to set it manually if ever necessary. * - * @param {attributionInfo} IterableAttributionInfo object storing current attribution info + * @param attributionInfo - Object storing current attribution info + * + * @example + * ```typescript + * const CAMPAIGN_ID = 1234; + * const TEMPLATE_ID = 5678; + * const MESSAGE_ID = 9012; + * + * const attributionInfo = new IterableAttributionInfo(CAMPAIGN_ID, TEMPLATE_ID, MESSAGE_ID); + * + * Iterable.setAttributionInfo(attributionInfo); + * ``` */ - static setAttributionInfo(attributionInfo?: IterableAttributionInfo) { Iterable.logger.log('setAttributionInfo'); @@ -270,16 +372,34 @@ export class Iterable { } /** - * 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 - * @param {boolean} appAlreadyRunning whether or not the app was already running when the push notification arrived - * @param {unknown | undefined} dataFields information to store with the push open event + * Create a `pushOpen` event on the current user's Iterable profile, populating + * it with data provided to the method call. + * + * **NOTE**: Iterable's SDK automatically tracks push notification opens. + * However, it's also possible to manually track these events by calling this + * method. + * + * @param campaignId - The ID of the campaign to associate with the push open + * @param templateId - The ID of the template to associate with the push open + * @param messageId - The ID of the message to associate with the push open + * @param appAlreadyRunning - Whether or not the app was already running when + * the push notification arrived + * @param dataFields - Information to store with the push open event + * + * @example + * ```typescript + * const CAMPAIGN_ID = 12345; + * const TEMPLATE_ID = 67890; + * const MESSAGE_ID = '0fc6657517c64014868ea2d15f23082b'; + * const APP_ALREADY_RUNNING = false; + * const DATA_FIELDS = { + * "discount": 0.99, + * "product": "cappuccino", + * }; + * + * Iterable.trackPushOpen(CAMPAIGN_ID, TEMPLATE_ID, MESSAGE_ID, APP_ALREADY_RUNNING, DATA_FIELDS); + * ``` */ - static trackPushOpenWithCampaignId( campaignId: number, templateId: number, @@ -299,13 +419,31 @@ export class Iterable { } /** - * This static method updates the items saved in the shopping cart (or equivalent). + * Update 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 {IterableCommerceItem[]} items the items added to the shopping cart + * @see {@link IterableCommerceItem} + * + * @param items - The items added to the shopping cart + * + * @example + * ```typescript + * const item = new IterableCommerceItem( + * "TOY1", + * "Red Racecar", + * 4.99, + * 1, + * "RR123", + * "A small, red racecar.", + * "https://www.example.com/toys/racecar", + * "https://www.example.com/toys/racecar/images/car.png", + * ["Toy", "Inexpensive"], + * ); + * + * Iterable.updateCart([item]); + * ``` */ - static updateCart(items: IterableCommerceItem[]) { Iterable.logger.log('updateCart'); @@ -313,11 +451,15 @@ export class Iterable { } /** - * This static method launches the application from the background for Android devices. + * Launch the application from the background in Android devices. * - * parameters: none + * @group Android Only + * + * @example + * ```typescript + * Iterable.wakeApp(); + * ``` */ - static wakeApp() { if (Platform.OS === 'android') { Iterable.logger.log('Attempting to wake the app'); @@ -327,17 +469,29 @@ export 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. + * Create a purchase event on the current user's Iterable profile. + * + * Represent each item in the purchase event with an {@link IterableCommerceItem} object. * - * Note: total is a parameter that is passed in. Iterable does not sum the price fields of the various items in the purchase event. + * @see {@link IterableCommerceItem} * - * @param {number} total the total cost of the purchase - * @param {IterableCommerceItem[]} items the items included in the purchase - * @param {any | undefined} dataFields descriptive data to store on the purchase event + * **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 total - The total cost of the purchase + * @param items - The items included in the purchase + * @param dataFields - Descriptive data to store on the purchase event + * + * @example + * ```typescript + * const items = [ + * new IterableCommerceItem('item1', 'Item 1', 10.0, 1), + * new IterableCommerceItem('item2', 'Item 2', 20.0, 2), + * ]; + * const dataFields = { 'key1': 'value1', }; + * + * Iterable.trackPurchase(30.0, items, dataFields); + * ``` */ - static trackPurchase( total: number, items: IterableCommerceItem[], @@ -349,14 +503,24 @@ export class Iterable { } /** - * This static method creates an inAppOpen event for the specified message on the current user's profile + * Create 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. * - * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) - * @param {IterableInAppLocation} location the location of the in-app message (an IterableInAppLocation enum) + * @param message - The in-app message (an {@link IterableInAppMessage} object) + * @param location - The location of the in-app message (an IterableInAppLocation enum) + * + * @example + * ```typescript + * const message = new IterableInAppMessage(1234, 4567, IterableInAppTrigger.auto, new Date(), new Date(), false, undefined, undefined, false, 0); + * Iterable.trackInAppOpen(message, IterableInAppLocation.inApp); + * ``` + * + * @remarks + * Iterable's SDK automatically tracks in-app message opens when you use the + * SDK's default rendering. However, it's also possible to manually track + * these events by calling this method. */ - static trackInAppOpen( message: IterableInAppMessage, location: IterableInAppLocation @@ -367,16 +531,26 @@ export class Iterable { } /** - * This static method creates an inAppClick event for the specified message on the current user's profile + * Create 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 - * from inAppOpen events. + * from `inAppOpen` events. + * + * @param message - The in-app message. + * @param location - The location of the in-app message. + * @param clickedUrl - The URL clicked by the user. * - * @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 + * @example + * ```typescript + * const message = new IterableInAppMessage(1234, 4567, IterableInAppTrigger.auto, new Date(), new Date(), false, undefined, undefined, false, 0); + * Iterable.trackInAppClick(message, IterableInAppLocation.inApp, 'https://www.example.com'); + * ``` + * + * @remarks + * Iterable's SDK automatically tracks in-app message clicks when you use the + * SDK's default rendering. However, you can also manually track these events + * by calling this method. */ - static trackInAppClick( message: IterableInAppMessage, location: IterableInAppLocation, @@ -388,16 +562,27 @@ export class Iterable { } /** - * 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. + * Create 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 message - The in-app message. + * @param location - The location of the in-app message. Useful for determining if the messages is in a mobile inbox. + * @param source - The way the in-app was closed. + * @param clickedUrl - The URL clicked by the user. + * + * @example + * ```typescript + * const message = new IterableInAppMessage(1234, 4567, IterableInAppTrigger.auto, new Date(), new Date(), false, undefined, undefined, false, 0); + * Iterable.trackInAppClose(message, IterableInAppLocation.inApp, IterableInAppCloseSource.back, 'https://www.example.com'); + * ``` * - * @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 + * @remarks + * Iterable's SDK automatically tracks in-app message close events when you + * use the SDK's default rendering. However, it's also possible to manually + * track these events by calling this method. */ - static trackInAppClose( message: IterableInAppMessage, location: IterableInAppLocation, @@ -415,16 +600,41 @@ export class Iterable { } /** - * This static method removes the specifed message from the current user's message queue. - * 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) + * Remove the specified message from the current user's message queue. + * + * This creates an in-app delete event for the specified message on the current user's profile + * unless otherwise specified (specifying a source of {@link IterableInAppDeleteSource.unknown} prevents + * an `inAppDelete` event from being created). + * + * @param message - The in-app message (an {@link IterableInAppMessage} object) + * @param location - The location of the in-app message (an {@link IterableInAppLocation} enum) + * @param source - How the in-app message was deleted (an {@link IterableInAppDeleteSource} enum) + * + * @example + * ```typescript + * const message = new IterableInAppMessage( + * 1234, + * 4567, + * IterableInAppTrigger.auto, + * new Date(), + * new Date(), + * false, + * undefined, + * undefined, + * false, + * 0, + * ); + * + * Iterable.inAppConsume(message, IterableInAppLocation.inApp, IterableInAppDeleteSource.delete); + * ``` + * + * @remarks + * After a user has read an in-app message, you can _consume_ it so that it's no + * longer in their queue of messages. When you use the SDK's default rendering + * for in-app messages, it handles this automatically. However, you can also + * use this method to do it manually (for example, after rendering an in-app + * message in a custom way). */ - static inAppConsume( message: IterableInAppMessage, location: IterableInAppLocation, @@ -436,12 +646,24 @@ export class Iterable { } /** - * This static method creates a custom event to the current user's Iterable profile. + * Create 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 {unknown | undefined} dataFields descriptive data to store on the custom event + * @param name - The event name of the custom event + * @param dataFields - Descriptive data to store on the custom event + * + * @example + * ```typescript + * Iterable.trackEvent("completedOnboarding", + * { + * "includedProfilePhoto": true, + * "favoriteColor": "red", + * "favoriteFlavor": "cinnamon", + * } + * ); + * ``` */ static trackEvent(name: string, dataFields?: unknown) { Iterable.logger.log('trackEvent'); @@ -450,18 +672,43 @@ export class Iterable { } /** - * This static method saves data to the current user's Iterable profile. + * Save data to the current user's Iterable profile. * - * If mergeNestedObjects is set to true, top-level objects in the passed in dataFields parameter + * 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 + * 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 {unknown | undefined} dataFields data fields to store in user profile - * @param {boolean} mergeNestedObjects flag indicating whether to merge top-level objects + * @param dataFields - Data fields to store in user profile + * @param mergeNestedObjects - Whether to merge top-level objects included in + * dataFields with analogous, existing objects on the user profile (if `true`) + * or overwrite them (if `false`). + * + * @example + * This call adds the `firstName` field and `favorites` object to the current + * user's Iterable profile. Since `mergeNestedObjects` is `false`, this call will + * overwrite the existing favorites object (if there is one), replacing it + * with the value in the call (otherwise, it would have merged the two + * `favorites` objects). + * + * ```typescript + * Iterable.updateUser( + * { + * "firstName": "Joe", + * "favorites": { + * "color": "red", + * "flavor": "cinnamon" + * } + * }, + * false + * ); + * ``` + * + * @remarks + * **IMPORTANT**: `mergeNestedObjects` only works for data that is stored up to one level deep within an object (for example, `{mySettings:{mobile:true}}`). Note that `mergeNestedObjects` applies to objects, not arrays. */ static updateUser( dataFields: unknown | undefined, @@ -473,18 +720,22 @@ export class Iterable { } /** - * This static method changes the value of the email field on the current user's Iterable profile. + * Change 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 + * 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. * * 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 + * @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 + * + * @example + * ```typescript + * Iterable.updateEmail('my.new.email@gmail.com', 'myAuthToken'); + * ``` */ - static updateEmail(email: string, authToken?: string) { Iterable.logger.log('updateEmail'); @@ -492,13 +743,82 @@ export class Iterable { } /** - * 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 + * tsdoc/syntax needs to be disabled as it conflicts with the mermaid syntax. + * unfortunately, disabling it inline does not appear to work. + * eslint-disable tsdoc/syntax + */ + /** + * Handle a universal link. + * + * `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 + * This can be used to handle deep links, universal links, and other URLs that + * the app should handle. + * + * @see [Handling Deep Links in the Iterable React Native SDK](https://support.iterable.com/hc/en-us/articles/360046134911-Deep-Links-and-Custom-Actions-with-Iterable-s-React-Native-SDK#configuring-your-app-to-support-deep-links) + * + * @remarks + * When you call `Iterable.handleAppLink,` you'll pass a URL—the tracking URL + * generated by Iterable for the link you included in your message content. + * `handleAppLink` will fetch the original URL (the one you added to your + * message) from `Iterable` and hand it to `IterableConfig.urlHandler`, where you + * can analyze it and decide what to do (for example, you might navigate the + * user to a particular screen in your app that has content related to the + * link). + * + * @mermaid The flow goes like this: + * graph TD; + * A[Linking event listener] --> B[Iterable.handleAppLink] --> C[IterableConfig.urlHandler]; + * + * + * @param link - The tracking URL generated by Iterable for the link you included in your message content. + * + * @example + * Basic usage is as follows: + * ```typescript + * Iterable.handleAppLink('https://www.example.com').then((handled) => { + * console.log('Link handled: ' + handled); + * }); + * ``` + * + * For deep linking, your code would look something like this: + * ```tsx + * import { Linking } from 'react-native'; + * + * const MyApp = () => { + * // To handle deep links clicked when the app is not running, + * // implement `Linking.getInitialURL`: + * Linking.getInitialURL().then((url) => { + * if (url) { + * Iterable.handleAppLink(url); + * } + * }); + * + * // To handle deep links clicked when the app is already open + * // (even if it's in the background), implement + * // `Linking.addEventListener('url', callback)` + * Linking.addEventListener('url', (event) => { + * if (event.url) { + * Iterable.handleAppLink(event.url); + * } + * }); + * + * // This, in turn, will call your `urlHandler` function on the + * // `IterableConfig` object you used to initialize the SDK. + * const config = new IterableConfig(); + * config.urlHandler = (url) => { + * const isCoffeeUrl = url.search(/coffee/i) !== -1; + * if (isCoffeeUrl) { + * // Navigate to the coffee screen + * } + * return isCoffeeUrl; + * }; + * + * return ( /* Your app code here * / ); + * } + * ``` */ - static handleAppLink(link: string): Promise { Iterable.logger.log('handleAppLink'); @@ -506,21 +826,39 @@ export class Iterable { } /** - * This static method updates the current user's subscribed email lists, unsubscribed channel IDs, + * Update 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 + * 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 {number[] | undefined} emailListIds the list of email lists (by ID) to which the user should be subscribed - * @param {number[] | undefined} unsubscribedChannelIds the list of message channels (by ID) to which the user should be unsubscribed - * @param {number[] | undefined} unsubscribedMessageTypeIds the list of message types (by ID) to which the user should be unsubscribed (for opt-out message types) - * @param {number[] | undefined} subscribedMessageTypeIds the list of message types (by ID) to which the user should be subscribed (for opt-in message types) - * @param {number} campaignId the campaign ID to associate with events generated by this request, use -1 if unknown or not applicable - * @param {number} templateId the template ID to associate with events generated by this request, use -1 if unknown or not applicable + * @param emailListIds - The list of email lists (by ID) to which the user should be subscribed + * @param unsubscribedChannelIds - The list of message channels (by ID) to which the user should be unsubscribed + * @param unsubscribedMessageTypeIds - The list of message types (by ID) to which the user should be unsubscribed (for opt-out message types) + * @param subscribedMessageTypeIds - The list of message types (by ID) to which the user should be subscribed (for opt-in message types) + * @param campaignId - The campaign ID to associate with events generated by this request, use `-1` if unknown or not applicable + * @param templateId - The template ID to associate with events generated by this request, use `-1` if unknown or not applicable + * + * @example + * ```typescript + * const emailListIds = [1234, 5678]; + * const unsubscribedChannelIds = [1234, 5678]; + * const unsubscribedMessageTypeIds = [1234, 5678]; + * const subscribedMessageTypeIds = [1234, 5678]; + * const campaignId = 1234; + * const templateId = 5678; + * + * Iterable.updateSubscriptions( + * emailListIds, + * unsubscribedChannelIds, + * unsubscribedMessageTypeIds, + * subscribedMessageTypeIds, + * campaignId, + * templateId, + * ); + * ``` */ - static updateSubscriptions( emailListIds: number[] | undefined, unsubscribedChannelIds: number[] | undefined, @@ -541,7 +879,26 @@ export class Iterable { ); } - // PRIVATE + /** + * Sets up event handlers for various Iterable events. + * + * This method performs the following actions: + * - Removes all existing listeners to avoid duplicate listeners. + * - Adds listeners for URL handling, custom actions, in-app messages, and authentication. + * + * Event Handlers: + * - `handleUrlCalled`: Invokes the URL handler if configured, with a delay on Android to allow the activity to wake up. + * - `handleCustomActionCalled`: Invokes the custom action handler if configured. + * - `handleInAppCalled`: Invokes the in-app handler if configured and sets the in-app show response. + * - `handleAuthCalled`: Invokes the authentication handler if configured and handles the promise result. + * - `handleAuthSuccessCalled`: Sets the authentication response callback to success. + * - `handleAuthFailureCalled`: Sets the authentication response callback to failure. + * + * Helper Functions: + * - `callUrlHandler`: Calls the URL handler and attempts to open the URL if the handler returns false. + * + * @internal + */ private static setupEventHandlers() { //Remove all listeners to avoid duplicate listeners RNEventEmitter.removeAllListeners(IterableEventName.handleUrlCalled); @@ -667,6 +1024,13 @@ export class Iterable { } } + /** + * Retrieves the version number from the package.json file. + * + * @returns {string} The version number as specified in the package.json file. + * + * @internal + */ private static getVersionFromPackageJson(): string { const json = require('../../../package.json'); const version = json.version as string; diff --git a/src/core/classes/IterableAction.ts b/src/core/classes/IterableAction.ts index 5bac640f..dcb3650a 100644 --- a/src/core/classes/IterableAction.ts +++ b/src/core/classes/IterableAction.ts @@ -1,18 +1,39 @@ /** * IterableAction represents an action defined as a response to user events. + * * It is currently used in push notification actions (open push & action buttons). */ export class IterableAction { + /** The type of iterable action. */ type: string; + /** + * Determines the action. EG: "open_url", "open_in_app", "deep_link", "join" etc. + */ data?: string; + /** + * Additional info related to the action. + */ userInput?: string; + /** + * Creates an instance of IterableAction. + * + * @param type - The type of the action. + * @param data - Optional data associated with the action. + * @param userInput - Optional user input related to the action. + */ constructor(type: string, data?: string, userInput?: string) { this.type = type; this.data = data; this.userInput = userInput; } + /** + * Creates an instance of `IterableAction` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableAction` instance. + * @returns A new instance of `IterableAction` initialized with the provided dictionary properties. + */ static fromDict(dict: IterableAction): IterableAction { return new IterableAction(dict.type, dict.data, dict.userInput); } diff --git a/src/core/classes/IterableActionContext.ts b/src/core/classes/IterableActionContext.ts index 8eec4ed1..064025d1 100644 --- a/src/core/classes/IterableActionContext.ts +++ b/src/core/classes/IterableActionContext.ts @@ -2,17 +2,31 @@ import type { IterableActionSource } from '../enums'; import { IterableAction } from './IterableAction'; /** - * TODO: Add description + * Information related to an Iterable action. */ export class IterableActionContext { + /** + * The action associated with the context. + */ action: IterableAction; + /** + * The origin of the action. In other words, where was the action triggered? + */ source: IterableActionSource; + /** + * Creates an instance of IterableActionContext. + */ constructor(action: IterableAction, source: IterableActionSource) { this.action = action; this.source = source; } + /** + * Creates an instance of `IterableActionContext` from a dictionary object. + * + * @returns A new instance of `IterableActionContext` with the provided properties. + */ static fromDict(dict: IterableActionContext): IterableActionContext { const action = IterableAction.fromDict(dict.action); const source = dict.source; diff --git a/src/core/classes/IterableAttributionInfo.ts b/src/core/classes/IterableAttributionInfo.ts index 5b8a1929..46f8d913 100644 --- a/src/core/classes/IterableAttributionInfo.ts +++ b/src/core/classes/IterableAttributionInfo.ts @@ -1,11 +1,28 @@ /** - * TODO: Add description + * Represents attribution information for an Iterable campaign. */ export class IterableAttributionInfo { + /** + * The ID of the campaign. + */ campaignId: number; + + /** + * The ID of the template used in the campaign. + */ templateId: number; + + /** + * The ID of the message. + */ messageId: string; + /** + * Creates an instance of IterableAttributionInfo. + * @param campaignId - The ID of the campaign. + * @param templateId - The ID of the template used in the campaign. + * @param messageId - The ID of the message. + */ constructor(campaignId: number, templateId: number, messageId: string) { this.campaignId = campaignId; this.templateId = templateId; diff --git a/src/core/classes/IterableAuthResponse.ts b/src/core/classes/IterableAuthResponse.ts index 716b52b7..d071d4d2 100644 --- a/src/core/classes/IterableAuthResponse.ts +++ b/src/core/classes/IterableAuthResponse.ts @@ -1,8 +1,13 @@ -// TODO: Add comments and descriptions // REVIEW: This seems to currently be used as a type instead of a class, so it // might be better to make it a type +/** + * The result of an authentication request to Iterable. + */ export class IterableAuthResponse { + /** JWT Token */ authToken?: string = ''; + /** Callback when the authentication to Iterable succeeds */ successCallback?: () => void; + /** Callback when the authentication to Iterable fails */ failureCallback?: () => void; } diff --git a/src/core/classes/IterableCommerceItem.ts b/src/core/classes/IterableCommerceItem.ts index 3beffad7..c738a465 100644 --- a/src/core/classes/IterableCommerceItem.ts +++ b/src/core/classes/IterableCommerceItem.ts @@ -1,18 +1,101 @@ /** - * TODO: Add description + * Represents an item for purchase. + * + * This is used in methods like `trackPurchase` to track purchases made by + * users. It is also used in the `updateCart` method to update the shopping cart. + * + * @example + * ```typescript + * const commerceItem = new IterableCommerceItem( + * '12345', + * 'Example Item', + * 9.99, + * 1, + * 'SKU123', + * 'An example item for demonstration purposes.', + * 'https://example.com/item', + * 'https://example.com/item.jpg', + * ['Example', 'Demo'], + * { customField: 'value' }, + * ); + * + * IterableAPI.updateCart([commerceItem]); + * ``` */ export class IterableCommerceItem { + /** + * The unique identifier for the item. + */ id: string; + /** + * The name of the item. + */ name: string; + /** + * The price of the item. + */ price: number; + /** + * The quantity of the item. + */ quantity: number; + /** + * The stock keeping unit (SKU) of the item. + */ sku?: string; + /** + * The description of the item. + */ description?: string; + /** + * The URL of the item. + */ url?: string; + /** + * The image URL of the item. + */ imageUrl?: string; + /** + * The categories the item belongs to. + */ categories?: string[]; + /** + * Additional data fields for the item. + */ dataFields?: unknown; + /** + * Creates an instance of IterableCommerceItem. + * + * @param id - The unique identifier for the item. + * @param name - The name of the item. + * @param price - The price of the item. + * @param quantity - The quantity of the item. + * @param sku - The stock keeping unit (SKU) of the item. + * @param description - The description of the item. + * @param url - The URL of the item. + * @param imageUrl - The image URL of the item. + * @param categories - The categories the item belongs to. + * @param dataFields - Additional data fields for the item. + * + * @returns A new instance of IterableCommerceItem. + * + * @example + * ```typescript + * const commerceItem = new IterableCommerceItem( + * '12345', + * 'Example Item', + * 9.99, + * 1, + * 'SKU123', + * 'An example item for demonstration purposes.', + * 'https://example.com/item', + * 'https://example.com/item.jpg', + * ['Example', 'Demo'], + * { customField: 'value' }, + * ); + * ``` + */ constructor( id: string, name: string, diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 0b3fa0f3..079a7cff 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -10,43 +10,116 @@ import type { IterableAuthResponse } from './IterableAuthResponse'; /** * 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. + * + * An IterableConfig object is passed into the static initialize method on the + * Iterable class when initializing the SDK. + * */ 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. * * 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. + * has a custom name. To view your existing integrations, navigate to Settings \> Mobile Apps. */ pushIntegrationName?: string; /** - * 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. + * 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; /** - * When set to true, it will check for deferred deep links on first time app launch after installation from the App Store. + * @deprecated + * 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. - * between each. Defaults to 30 seconds. + * Number of seconds between each in-app message when displaying multiple in-app messages in sequence. + * + * Defaults to 30 seconds. */ inAppDisplayInterval = 30.0; /** * A callback function used to handle deep link URLs and in-app message button and link URLs. + * + * @param url - The URL to be processed (likely from a click). + * @param context - The context in which the URL action is being performed. + * Describes the source of the URL (push, in-app, or universal link) and + * information about any associated custom actions. + * + * @remarks + * Use this method to determine whether or not the app can handle the clicked + * URL. If it can, the method should navigate the user to the right content in + * the app and return `true`. Otherwise, it should return `false` to have the web + * browser open the URL. + * + * @example + * This example searches for URLs that contain product/, followed by more text. Upon finding this sequence of text, the code displays the appropriate screen and returns `true`. When it's not found, the app returns `false`. + * + * ```typescript + * const config = new IterableConfig(); + * + * config.urlHandler = (url, context) => { + * if (url.match(/product\/([^\/]+)/i)) { + * this.navigate(match[1]); + * return true; // handled + * } + * return false; // not handled + * }; + * + * Iterable.initialize('', config); + * ``` + * + * @returns A boolean indicating whether the URL was successfully handled. */ urlHandler?: (url: string, context: IterableActionContext) => boolean; /** - * A function expression used to handle `action://` URLs for in-app buttons and links. + * A function expression used to handle `action://` URLs for in-app buttons + * and links. + * + * Use this method to determine whether or not the app can handle the clicked + * custom action URL. If it can, it should handle the action and return `true`. + * Otherwise, it should return `false`. + * + * @param action - The custom action that was triggered. + * @param context - The context in which the action was triggered. In other + * words, information about where the action was invoked. + * + * @remarks + * A custom action URL has format `action://customActionName`: an `action://` + * prefix, followed by a custom action name. Decide with your marketing team + * on a set of known custom actions your app should support. + * + * **WARNING**: Earlier versions of the SDK used the `itbl:// prefix` for custom + * action URLs. The SDK still supports these custom actions, but they are + * deprecated and will not be supported forever. Migrate to `action://` as it's + * possible to do so. + * + * @example + * This example responds to the `action://achievedPremierStatus` custom action URL by updating the app's styles and return `true`. Since this is the only custom action handled by the method, it returns `false` for anything else. + * ```typescript + * const config = new IterableConfig(); + * config.customActionHandler = (action, context) => { + * if (action.type == "achievedPremierStatus") { + * // For this action, update the app's styles + * this.updateAppStyles("premier"); + * return true; + * } + * return false; + * } + * Iterable.initialize('', config); + * ``` + * + * @returns A boolean indicating whether the action was handled. */ customActionHandler?: ( action: IterableAction, @@ -55,29 +128,93 @@ export class IterableConfig { /** * 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. + * @see [In-App Messages with Iterable's React Native SDK](https://support.iterable.com/hc/en-us/articles/360045714172-In-App-Messages-with-Iterable-s-React-Native-SDK) + * + * @remarks + * Implement this property to override default in-app message behavior. For + * example, you might use the custom payload associated with an incoming + * message to choose whether or not to display it, or you might inspect the + * priorityLevel property to determine the message's priority. + * + * @example + * ```typescript + * config.inAppHandler = (message: IterableInAppMessage) => { + * return message?.read ? IterableInAppShowResponse.skip : IterableInAppShowResponse.show; + * } + * ``` + * + * @param message - The in-app message to be processed. + * @returns A response indicating how the in-app message should be shown. */ 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 - * JWT-enabled API key. + * 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 + * [JWT-enabled API + * key](https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#:~:text=app%20uses%20a-,JWT%2Denabled%20API%20key,-.). + * + * @remarks + * To make requests to Iterable's API using a JWT-enabled API key, you should + * first fetch a valid JWT for your app's current user from your own server, + * which must generate it. The `authHandler` provides this JWT to Iterable's + * React Native SDK, which can then append the JWT to subsequent API requests. + * The SDK automatically calls `authHandler` at various times: + * - When your app sets the user's email or user ID. + * - When your app updates the user's email. + * - Before the current JWT expires (at a configurable interval set by + * `expiringAuthTokenRefreshPeriod`) + * - When your app receives a 401 response from Iterable's API with a + * `InvalidJwtPayload` error code. However, if the SDK receives a second + * consecutive 401 with an `InvalidJwtPayload` error when it makes a request + * with the new token, it won't call the `authHandler` again until you call + * `setEmail` or `setUserId` without passing in an auth token. + * + * **TIP**: The `setEmail` and `setUserId` mobile SDK methods accept an + * optional, prefetched auth token. If you encounter SDK errors related to + * auth token requests, try using this parameter. + * + * For more information about JWT-enabled API keys, read [JWT-Enabled API Keys](https://support.iterable.com/hc/articles/360050801231). + * + * @example + * This example demonstrates how an app that uses a JWT-enabled API key might initialize the SDK. + * + * ```typescript + * const config = new IterableConfig(); + * + * config.authHandler = () => { + * // ... Fetch a JWT from your server, or locate one you've already retrieved + * return new Promise(function (resolve, reject) { + * // Resolve the promise with a valid auth token for the current user + * resolve("") + * }); + * }; + * config.autoPushRegistration = false; + * + * Iterable.initialize('', config); + * ``` + * + * @returns A promise that resolves to an `IterableAuthResponse`, a `string`, + * or `undefined`. */ 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 whether the React Native SDK should print function calls to console + * 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 */ @@ -85,18 +222,35 @@ export class IterableConfig { /** * The number of seconds before the current JWT's expiration that the SDK should call the - * authHandler to get an updated JWT. + * `authHandler` to get an updated JWT. */ expiringAuthTokenRefreshPeriod = 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. + * 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. + * + * @example + * To allow the SDK to handle `http`, `tel`, and `custom` links, use code similar to this: + * + * ```typescript + * const config = new IterableConfig(); + * config.allowedProtocols = ["http", "tel", "custom"]; + * ``` + * + * @remarks + * **IMPORTANT**: Iterable's React Native SDK handles https, action, itbl, and + * iterable links, regardless of the contents of this array. However, you must + * explicitly declare any other types of URL protocols you'd like the SDK to + * handle (otherwise, the SDK won't open them in the web browser or as deep + * links). */ - allowedProtocols: Array = []; + allowedProtocols: string[] = []; /** + * @deprecated * DEPRECATED - please use `useInMemoryStorageForInApps` as a replacement for this config option. * * NOTE: until this option is removed, it will still function with `useInMemoryStorageForInApps` by @@ -109,7 +263,15 @@ export class IterableConfig { /** * This specifies the `useInMemoryStorageForInApps` config option downstream to the native SDK layers. + * + * It determines whether Android and iOS apps should store in-app messages in memory, rather than in an unencrypted local file (defaults to `false`). + * * Please read the respective `IterableConfig` files for specific details on this config option. + * + * @remarks + * For more information about this option, and the deprecated-but-related + * `androidSdkUseInMemoryStorageForInApps` available in version 1.3.7 of + * Iterable's React Native SDK, read the [release notes for version 1.3.9](https://github.com/Iterable/react-native-sdk/releases/tag/1.3.9). */ useInMemoryStorageForInApps = false; @@ -131,23 +293,45 @@ export class IterableConfig { */ encryptionEnforced = false; + /** + * Converts the IterableConfig instance to a dictionary object. + * + * @returns An object representing the configuration. + */ toDict() { return { pushIntegrationName: this.pushIntegrationName, autoPushRegistration: this.autoPushRegistration, inAppDisplayInterval: this.inAppDisplayInterval, - // TODO: Figure out if this is purposeful + /** + * A boolean indicating if a URL handler is present. + * + * TODO: Figure out if this is purposeful + */ // eslint-disable-next-line eqeqeq urlHandlerPresent: this.urlHandler != undefined, - // TODO: Figure out if this is purposeful + /** + * A boolean indicating if a custom action handler is present. + * + * TODO: Figure out if this is purposeful + */ // eslint-disable-next-line eqeqeq customActionHandlerPresent: this.customActionHandler != undefined, - // TODO: Figure out if this is purposeful + /** + * A boolean indicating if an in-app handler is present. + * + * TODO: Figure out if this is purposeful + */ // eslint-disable-next-line eqeqeq inAppHandlerPresent: this.inAppHandler != undefined, - // TODO: Figure out if this is purposeful + /** + * A boolean indicating if an authentication handler is present. + * + * TODO: Figure out if this is purposeful + */ // eslint-disable-next-line eqeqeq authHandlerPresent: this.authHandler != undefined, + /** The log level for the SDK. */ logLevel: this.logLevel, expiringAuthTokenRefreshPeriod: this.expiringAuthTokenRefreshPeriod, allowedProtocols: this.allowedProtocols, diff --git a/src/core/classes/IterableEdgeInsets.ts b/src/core/classes/IterableEdgeInsets.ts index f7651772..df939295 100644 --- a/src/core/classes/IterableEdgeInsets.ts +++ b/src/core/classes/IterableEdgeInsets.ts @@ -1,12 +1,22 @@ import type { IterableEdgeInsetDetails } from '../types'; -// TODO: Add description -export class IterableEdgeInsets { +/** + * Space around the html content + */ +export class IterableEdgeInsets implements IterableEdgeInsetDetails { top: number; left: number; bottom: number; right: number; + /** + * Creates an instance of IterableEdgeInsets. + * + * @param top - The top edge inset. + * @param left - The left edge inset. + * @param bottom - The bottom edge inset. + * @param right - The right edge inset. + */ constructor(top: number, left: number, bottom: number, right: number) { this.top = top; this.left = left; @@ -14,6 +24,16 @@ export class IterableEdgeInsets { this.right = right; } + /** + * Creates an instance of `IterableEdgeInsets` from a dictionary object. + * + * @param dict - An object containing the edge inset details with properties: + * - `top`: The top edge inset. + * - `left`: The left edge inset. + * - `bottom`: The bottom edge inset. + * - `right`: The right edge inset. + * @returns A new instance of `IterableEdgeInsets` initialized with the provided edge inset values. + */ static fromDict(dict: IterableEdgeInsetDetails): IterableEdgeInsets { return new IterableEdgeInsets(dict.top, dict.left, dict.bottom, dict.right); } diff --git a/src/core/classes/IterableLogger.ts b/src/core/classes/IterableLogger.ts index a27ebb06..3d985488 100644 --- a/src/core/classes/IterableLogger.ts +++ b/src/core/classes/IterableLogger.ts @@ -1,13 +1,44 @@ import { IterableConfig } from './IterableConfig'; -// TODO: Add comments and descriptions +/** + * A logger class for the Iterable SDK. + * + * This class is responsible for logging messages based on the configuration provided. + * + * @remarks + * The logging behavior is controlled by the `logReactNativeSdkCalls` property + * in {@link IterableConfig}. + * If this property is not set, logging defaults to `true`, which is useful in unit testing or debug environments. + * + * @example + * ```typescript + * const config = new IterableConfig(); + * config.logReactNativeSdkCalls = true; + * const logger = new IterableLogger(config); + * logger.log('This is a log message.'); + * ``` + */ export class IterableLogger { + /** + * The configuration settings for the Iterable SDK. + * This property is read-only and is initialized with an instance of `IterableConfig`. + */ readonly config: IterableConfig; + /** + * Creates an instance of IterableLogger. + * + * @param config - The configuration object for IterableLogger. + */ constructor(config: IterableConfig) { this.config = config; } + /** + * Logs a message to the console if logging is enabled. + * + * @param message - The message to be logged. + */ 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 diff --git a/src/core/classes/IterableUtil.ts b/src/core/classes/IterableUtil.ts index 50d01fe0..6c2e39d0 100644 --- a/src/core/classes/IterableUtil.ts +++ b/src/core/classes/IterableUtil.ts @@ -1,6 +1,16 @@ -// TODO: Add a description -// TODO: Change to a util function instead of a class +/** + * Utility class. + * + * TODO: Change to a util function instead of a class + */ export class IterableUtil { + /** + * Reads a boolean value from a dictionary. + * + * @param dict - The dictionary to read from. + * @param key - The key whose associated value is to be returned. + * @returns The boolean value associated with the specified key, or `false` if the key does not exist or the value is not a boolean. + */ static readBoolean(dict: Record, key: string): boolean { if (dict[key]) { return dict[key] as boolean; diff --git a/src/core/enums/IterableActionSource.ts b/src/core/enums/IterableActionSource.ts index 2cecfcf0..3692e636 100644 --- a/src/core/enums/IterableActionSource.ts +++ b/src/core/enums/IterableActionSource.ts @@ -2,7 +2,10 @@ * Enum representing the source of IterableAction. */ export enum IterableActionSource { + /** The action source was a push message */ push = 0, + /** The action source was a link in the app */ appLink = 1, + /** The action source was an in-app message */ inApp = 2, } diff --git a/src/core/enums/IterableAuthResponseResult.ts b/src/core/enums/IterableAuthResponseResult.ts index 03fb6a30..47bed458 100644 --- a/src/core/enums/IterableAuthResponseResult.ts +++ b/src/core/enums/IterableAuthResponseResult.ts @@ -1,5 +1,9 @@ -// TODO: Add description +/** + * The result of an attempt to authenticate with Iterable. + */ export enum IterableAuthResponseResult { + /** The authentication was successful */ SUCCESS, + /** The authentication failed */ FAILURE, } diff --git a/src/core/enums/IterableDataRegion.ts b/src/core/enums/IterableDataRegion.ts index c42e3002..6f93ed22 100644 --- a/src/core/enums/IterableDataRegion.ts +++ b/src/core/enums/IterableDataRegion.ts @@ -1,5 +1,13 @@ -// TODO: Add a description +/** + * The data region on which your Iterable project is hosted. + */ export enum IterableDataRegion { + /** Iterable projects hosted in the US. */ US = 0, + /** + * Iterable projects hosted in the EU. + * + * This configures the SDK to interact with Iterable's EDC-based endpoints. + */ EU = 1, } diff --git a/src/core/enums/IterableEventName.ts b/src/core/enums/IterableEventName.ts index 648032f0..4a44cbb4 100644 --- a/src/core/enums/IterableEventName.ts +++ b/src/core/enums/IterableEventName.ts @@ -1,10 +1,22 @@ -// TODO: Add description +/** + * Events that can be emitted by the Iterable SDK + */ export enum IterableEventName { + /** Event that fires when a URL is clicked */ handleUrlCalled = 'handleUrlCalled', + /** Event that fires when a custom action is called */ handleCustomActionCalled = 'handleCustomActionCalled', + /** + * TODO: Rename at some point + * Event that fires when an in-app message is shown + */ handleInAppCalled = 'handleInAppCalled', + /** Event that fires when a user attempts to authenticate */ handleAuthCalled = 'handleAuthCalled', + /** Event that fires when the Iterable inbox is updated */ receivedIterableInboxChanged = 'receivedIterableInboxChanged', + /** Event that fires when authentication with Iterable succeeds */ handleAuthSuccessCalled = 'handleAuthSuccessCalled', + /** Event that fires when authentication with Iterable fails */ handleAuthFailureCalled = 'handleAuthFailureCalled', } diff --git a/src/core/enums/IterableLogLevel.ts b/src/core/enums/IterableLogLevel.ts index 11675183..04c13ec7 100644 --- a/src/core/enums/IterableLogLevel.ts +++ b/src/core/enums/IterableLogLevel.ts @@ -1,8 +1,14 @@ /** - * Enum representing what level of logs will Android and iOS project be printing on their consoles respectively. + * Enum representing the level of logs will Android and iOS projects be using. + * + * @see [Android Log Levels](https://source.android.com/docs/core/tests/debug/understanding-logging) + * @see [iOS Log Levels](https://apple.github.io/swift-log/docs/current/Logging/Structs/Logger/Level.html#/s:7Logging6LoggerV5LevelO4infoyA2EmF) */ export enum IterableLogLevel { + /** Appropriate for messages that contain information normally of use only when debugging a program. */ debug = 1, + /** Appropriate for informational messages. */ info = 2, + /** Appropriate for error conditions. */ error = 3, } diff --git a/src/core/enums/IterablePushPlatform.ts b/src/core/enums/IterablePushPlatform.ts index fbe88b87..f7ceb55e 100644 --- a/src/core/enums/IterablePushPlatform.ts +++ b/src/core/enums/IterablePushPlatform.ts @@ -1,6 +1,11 @@ -// TODO: add decription +/** + * The push platform to use for push notifications. + */ export enum IterablePushPlatform { + /** Use sandbox. */ sandbox = 0, + /** Use production. */ production = 1, + /** Automatically determine the push platform to use. */ auto = 2, } diff --git a/src/core/hooks/useAppStateListener.ts b/src/core/hooks/useAppStateListener.ts index 1f6d432e..3ae70b44 100644 --- a/src/core/hooks/useAppStateListener.ts +++ b/src/core/hooks/useAppStateListener.ts @@ -1,7 +1,18 @@ import { useEffect, useRef, useState } from 'react'; import { AppState } from 'react-native'; -// TODO: Comment +/** + * A hook that listens to the app state changes and returns the current app + * state. + * + * @returns {string} The current app state. + * + * @example + * ```typescript + * const appState = useAppStateListener(); + * console.log(appState); // 'active', 'background', etc. + * ``` + */ export function useAppStateListener() { const appStateEventName = 'change'; const appState = useRef(AppState.currentState); diff --git a/src/core/hooks/useDeviceOrientation.tsx b/src/core/hooks/useDeviceOrientation.tsx index bbf2dfed..f6d6ade4 100644 --- a/src/core/hooks/useDeviceOrientation.tsx +++ b/src/core/hooks/useDeviceOrientation.tsx @@ -1,8 +1,34 @@ import { useEffect, useState } from 'react'; import { useWindowDimensions } from 'react-native'; -// TODO: Comment -export function useDeviceOrientation() { +/** + * Represents the device orientation. + */ +export interface IterableDeviceOrientation { + /** The height of the device screen */ + height: number; + /** The width of the device screen */ + width: number; + /** Indicates if the device is in portrait mode */ + isPortrait: boolean; +} + +/** + * Custom hook to get the current device orientation. + * + * This hook returns the height, width, and a boolean indicating if the device is in portrait mode. + * It listens to changes in the window dimensions and updates the orientation accordingly. + * + * @returns {IterableDeviceOrientation} An object containing the height, width, and a boolean `isPortrait` indicating if the device is in portrait mode. + * + * @example + * const { height, width, isPortrait } = useDeviceOrientation(); + * + * @remarks + * The `useEffect` hook only includes `width` in its dependency array. This is because the height and width are typically updated together, + * and including only `width` prevents unnecessary re-renders. + */ +export function useDeviceOrientation(): IterableDeviceOrientation { const { height, width } = useWindowDimensions(); const [isPortrait, setIsPortrait] = useState(height >= width); diff --git a/src/core/types/IterableEdgeInsetDetails.ts b/src/core/types/IterableEdgeInsetDetails.ts index 5915eedd..799ba3ec 100644 --- a/src/core/types/IterableEdgeInsetDetails.ts +++ b/src/core/types/IterableEdgeInsetDetails.ts @@ -1,6 +1,13 @@ +/** + * Space around the html content. + */ export interface IterableEdgeInsetDetails { + /** Space at the top of the html content. */ top: number; + /** Space to the left of the html content. */ left: number; + /** Space at the bottom of the html content. */ bottom: number; + /** Space to the right of the html content. */ right: number; } diff --git a/src/inApp/classes/IterableHtmlInAppContent.ts b/src/inApp/classes/IterableHtmlInAppContent.ts index ed9c7309..45843861 100644 --- a/src/inApp/classes/IterableHtmlInAppContent.ts +++ b/src/inApp/classes/IterableHtmlInAppContent.ts @@ -6,17 +6,34 @@ import type { IterableInAppContent, } from '../types'; -// TODO: Add description +/** + * Information about the display of an HTML in-app message. + */ export class IterableHtmlInAppContent implements IterableInAppContent { + /** The type of in-app content. */ type: IterableInAppContentType = IterableInAppContentType.html; + /** The space around the in-app content. */ edgeInsets: IterableEdgeInsets; + /** The raw HTML content of the in-app message. */ html: string; + /** + * Constructs an `IterableHtmlInAppContent` instance with the provided `edgeInsets` and `html`. + * + * @param edgeInsets The space around the in-app content. + * @param html The raw HTML content of the in-app message. + */ constructor(edgeInsets: IterableEdgeInsets, html: string) { this.edgeInsets = edgeInsets; this.html = html; } + /** + * Creates a new `IterableHtmlInAppContent` instance from a raw dictionary representation. + * + * @param dict The raw dictionary representation of the HTML in-app content. + * @returns A new `IterableHtmlInAppContent` instance with the values from the provided dictionary. + */ static fromDict(dict: IterableHtmlInAppContentRaw): IterableHtmlInAppContent { return new IterableHtmlInAppContent( IterableEdgeInsets.fromDict(dict.edgeInsets), diff --git a/src/inApp/classes/IterableInAppManager.ts b/src/inApp/classes/IterableInAppManager.ts index 7ec2482f..0fc7d090 100644 --- a/src/inApp/classes/IterableInAppManager.ts +++ b/src/inApp/classes/IterableInAppManager.ts @@ -12,50 +12,77 @@ import { IterableInAppMessage } from './IterableInAppMessage'; const RNIterableAPI = NativeModules.RNIterableAPI; /** - * IterableInAppManager is set up as the inAppManager property of an Iterable instance. + * Manages in-app messages for the current user. + * + * This class provides methods to interact with in-app messages, including retrieving messages, + * displaying messages, removing messages, setting read status, and more. + * + * The `inAppManager` property of an `Iterable` instance is set to an instance of this class. */ - 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. + * Retrieve the current user's list of in-app messages stored in the local queue. * * 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 + * @example + * ```typescript + * Iterable.inAppManager.getMessages().then(messages => { + * messages.forEach(message => { + * console.log(JSON.stringify(message, null, 2)) + * }); + * }); + * ``` + * + * @returns A Promise that resolves to an array of in-app messages. */ - - getMessages(): Promise> { + getMessages(): Promise { Iterable.logger.log('InAppManager.getMessages'); 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`. + * Retrieves the current user's list of in-app messages designated for the + * mobile inbox and stored in the local queue. * * 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 + * @example + * ```typescript + * Iterable.inAppManager.getInboxMessages().then(messages => { + * messages.forEach(message => { + * console.log(JSON.stringify(message, null, 2)) + * }); + * }); + * ``` + * + * @returns A Promise that resolves to an array of messages marked as `saveToInbox`. */ - - getInboxMessages(): Promise> { + getInboxMessages(): Promise { Iterable.logger.log('InAppManager.getInboxMessages'); return RNIterableAPI.getInboxMessages(); } /** - * This method renders an in-app message and consumes it from the user's message queue if necessary. + * 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. + * If you skip showing an in-app message when it arrives, you can show it at + * another time by calling this method. * - * @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. + * @example + * ```typescript + * Iterable.inAppManager.showMessage(message, false).then(url => { + * console.log("url: " + url) + * }); + * ``` + * + * @param message - The message to show (an {@link_IterableInAppMessage} object) + * @param consume - Whether or not the message should be consumed from the user's message queue after being shown. This should be defaulted to true. + * + * @returns A Promise that resolves to the URL of the button or link the user tapped to close the in-app message. */ - showMessage( message: IterableInAppMessage, consume: boolean @@ -66,12 +93,22 @@ export class IterableInAppManager { } /** - * This method removes the specifed message from the current user's message queue. - * Also, this method calls the inAppConsume method internally. + * Remove the specified message from the current user's message queue. + * + * 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) + * @param message - The in-app message to remove (an {@link IterableInAppMessage} object) + * @param location - The message's location—whether or not it's in a mobile inbox. (an {@link IterableInAppLocation} enum) + * @param source - How the in-app message was deleted (an {@link IterableInAppDeleteSource} enum) + * + * @example + * ```typescript + * Iterable.inAppManager.removeMessage( + * message, + * IterableInAppLocation.inApp, + * IterableInAppDeleteSource.unknown, + * ); + * ``` */ removeMessage( message: IterableInAppMessage, @@ -84,10 +121,15 @@ export class IterableInAppManager { } /** - * This method sets the read status of specified in-app message. + * Set the read status of specified in-app message. + * + * @param message - The message for which to set the status. + * @param read - Whether the in-app message was read. * - * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) - * @param {boolean} read the boolean value indicating whether the in-app message was read + * @example + * ```typescript + * Iterable.inAppManager.setReadForMessage(message, true); + * ``` */ setReadForMessage(message: IterableInAppMessage, read: boolean) { Iterable.logger.log('InAppManager.setRead'); @@ -96,12 +138,17 @@ export class IterableInAppManager { } /** - * 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. + * Retrieve HTML in-app content for a specified in-app message. + * + * @param message - The message from which to get HTML content. + * + * @returns A Promise that resolves to an {@link IterableHtmlInAppContent} object. * - * @param {IterableInAppMessage} message the in-app message (an IterableInAppMessage object) + * @example + * ```typescript + * Iterable.inAppManager.getHtmlContentForMessage(message); + * ``` */ - getHtmlContentForMessage( message: IterableInAppMessage ): Promise { @@ -111,11 +158,18 @@ export class IterableInAppManager { } /** - * 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). + * Pause or unpause the automatic display 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 paused - Whether the automatic displaying should be paused * - * @param {boolean} paused whether the automatic displaying should be paused + * @example + * ```typescript + * Iterable.inAppManager.setAutoDisplayPaused(paused); + * ``` */ setAutoDisplayPaused(paused: boolean) { Iterable.logger.log('InAppManager.setAutoDisplayPaused'); diff --git a/src/inApp/classes/IterableInAppMessage.ts b/src/inApp/classes/IterableInAppMessage.ts index 0c752f39..9bec94df 100644 --- a/src/inApp/classes/IterableInAppMessage.ts +++ b/src/inApp/classes/IterableInAppMessage.ts @@ -2,6 +2,7 @@ import { type ViewToken } from 'react-native'; import { IterableUtil } from '../../core'; import { IterableInAppTriggerType } from '../enums'; +import type { IterableInAppMessageRaw } from '../types'; import { IterableInAppTrigger } from './IterableInAppTrigger'; import { IterableInboxMetadata } from './IterableInboxMetadata'; @@ -10,27 +11,27 @@ import { IterableInboxMetadata } from './IterableInboxMetadata'; */ export class IterableInAppMessage { /** - * the ID for the in-app message + * The ID for the in-app message */ readonly messageId: string; /** - * the campaign ID for this message + * The campaign ID for this message */ readonly campaignId: number; /** - * when to trigger this in-app + * Information regarding the triggering of this in-app message */ readonly trigger: IterableInAppTrigger; /** - * when was this message created + * When was this message created? */ readonly createdAt?: Date; /** - * when to expire this in-app (undefined means do not expire) + * When to expire this in-app (`undefined` means do not expire) */ readonly expiresAt?: Date; @@ -46,6 +47,33 @@ export class IterableInAppMessage { /** * Custom Payload for this message. + * + * @example + * If the custom payload was the following: + * ```json + * { + * "customDisplay": true, + * "promotionTitle": "Summer Sale", + * "promotionText": "Everything is 50% off." + * } + * ``` + * You could use the following code to determine whether to hide/show the message: + * ```typescript + * config.inAppHandler = (message: IterableInAppMessage) => { + * if (message.customPayload.customDisplay == true) { + * return IterableInAppShowResponse.skip + * } else { + * return Iterable.InAppShowResponse.show + * } + * }; + * ``` + * You could then handle the showing of this message through a custom function. EG: + * ```typescript + * Alert.alert( + * message.customPayload.promotionTitle, + * message.customPayload.promotionText, + * ); + * ``` */ readonly customPayload?: unknown; @@ -55,10 +83,24 @@ export class IterableInAppMessage { readonly read: boolean; /** - * the priority value this in-app message has + * The priority value of this in-app message */ readonly priorityLevel: number; + /** + * Constructs an instance of IterableInAppMessage. + * + * @param messageId - The unique identifier for the message. + * @param campaignId - The identifier for the campaign associated with the message. + * @param trigger - The trigger that caused the message to be displayed. + * @param createdAt - The date and time when the message was created. + * @param expiresAt - The date and time when the message expires. + * @param saveToInbox - A boolean indicating whether the message should be saved to the inbox. + * @param inboxMetadata - Metadata associated with the inbox message. + * @param customPayload - A custom payload associated with the message. + * @param read - A boolean indicating whether the message has been read. + * @param priorityLevel - The priority level of the message. + */ constructor( messageId: string, campaignId: number, @@ -83,6 +125,12 @@ export class IterableInAppMessage { this.priorityLevel = priorityLevel; } + /** + * Creates an instance of `IterableInAppMessage` from a given `ViewToken`. + * + * @param viewToken - The `ViewToken` containing the in-app message data. + * @returns A new instance of `IterableInAppMessage` populated with data from the `viewToken`. + */ static fromViewToken(viewToken: ViewToken) { const inAppMessage = viewToken.item.inAppMessage as IterableInAppMessage; @@ -100,6 +148,11 @@ export class IterableInAppMessage { ); } + /** + * Do you want the in-app message to be saved to the inbox without triggering a notification? + * + * @returns `true` if the message should be saved to the inbox without triggering a notification; otherwise, `false`. + */ isSilentInbox(): boolean { return ( // TODO: Figure out if this is purposeful @@ -108,22 +161,13 @@ export class IterableInAppMessage { ); } - static fromDict(dict: { - messageId: string; - campaignId: number; - trigger: IterableInAppTrigger; - createdAt: number; - expiresAt: number; - saveToInbox: boolean; - inboxMetadata: { - title: string | undefined; - subtitle: string | undefined; - icon: string | undefined; - }; - customPayload: unknown; - read: boolean; - priorityLevel: number; - }): IterableInAppMessage { + /** + * Creates an instance of `IterableInAppMessage` from a dictionary object. + * + * @param dict - The dictionary containing the properties of the in-app message. + * @returns An instance of `IterableInAppMessage` populated with the provided properties. + */ + static fromDict(dict: IterableInAppMessageRaw): IterableInAppMessage { const messageId = dict.messageId; const campaignId = dict.campaignId; const trigger = IterableInAppTrigger.fromDict(dict.trigger); diff --git a/src/inApp/classes/IterableInAppTrigger.ts b/src/inApp/classes/IterableInAppTrigger.ts index daaf3baa..d101c911 100644 --- a/src/inApp/classes/IterableInAppTrigger.ts +++ b/src/inApp/classes/IterableInAppTrigger.ts @@ -1,13 +1,27 @@ import type { IterableInAppTriggerType } from '../enums'; -// TODO: Add description +/** + * Trigger information for in-app messages in the Iterable SDK. + */ export class IterableInAppTrigger { + /** + * The type of the in-app trigger. + */ type: IterableInAppTriggerType; + /** + * Creates an instance of IterableInAppTrigger. + * @param type - The type of the in-app trigger. + */ constructor(type: IterableInAppTriggerType) { this.type = type; } + /** + * Creates an IterableInAppTrigger instance from a dictionary object. + * @param dict - The dictionary containing the type of the in-app trigger. + * @returns A new instance of IterableInAppTrigger. + */ static fromDict(dict: { type: IterableInAppTriggerType; }): IterableInAppTrigger { diff --git a/src/inApp/classes/IterableInboxMetadata.ts b/src/inApp/classes/IterableInboxMetadata.ts index 4bf47fd3..8c9ae14b 100644 --- a/src/inApp/classes/IterableInboxMetadata.ts +++ b/src/inApp/classes/IterableInboxMetadata.ts @@ -1,11 +1,21 @@ /** - * TODO: Add description + * Metadata for a message. */ export class IterableInboxMetadata { + /** The message title */ title?: string; + /** The message subtitle (this is sometimes a preview of the body text) */ subtitle?: string; + /** The icon associated with the message */ icon?: string; + /** + * Constructs an instance of IterableInboxMetadata. + * + * @param title - The title of the inbox item. + * @param subtitle - The subtitle of the inbox item. + * @param icon - The icon associated with the inbox item. + */ constructor( title: string | undefined, subtitle: string | undefined, @@ -16,9 +26,20 @@ export class IterableInboxMetadata { this.icon = icon; } + /** + * Creates an instance of `IterableInboxMetadata` from a dictionary object. + * + * @param dict - The dictionary object containing the metadata properties. + * This corresponds to the properties in {@link IterableInboxMetadata}. + * + * @returns A new instance of `IterableInboxMetadata` with the provided properties. + */ static fromDict(dict: { + /** The message title */ title: string | undefined; + /** The message subtitle (this is sometimes a preview of the body text) */ subtitle: string | undefined; + /** The icon associated with the message */ icon: string | undefined; }): IterableInboxMetadata { return new IterableInboxMetadata(dict.title, dict.subtitle, dict.icon); diff --git a/src/inApp/enums/IterableInAppCloseSource.ts b/src/inApp/enums/IterableInAppCloseSource.ts index ebd780ce..5e2734c1 100644 --- a/src/inApp/enums/IterableInAppCloseSource.ts +++ b/src/inApp/enums/IterableInAppCloseSource.ts @@ -1,6 +1,11 @@ -// TODO: Add description +/** + * The ways in which an in-app message might have been closed. + */ export enum IterableInAppCloseSource { + /** Closed by clicking the back button. */ back = 0, + /** Closed by clicking a link. */ link = 1, + /** Closed in an unknown way. */ unknown = 100, } diff --git a/src/inApp/enums/IterableInAppContentType.ts b/src/inApp/enums/IterableInAppContentType.ts index 4210a860..5256171b 100644 --- a/src/inApp/enums/IterableInAppContentType.ts +++ b/src/inApp/enums/IterableInAppContentType.ts @@ -1,6 +1,11 @@ -// TODO: Add description +/** + * Types of in-app content available in the Iterable SDK. + */ export enum IterableInAppContentType { + /** HTML Content */ html = 0, + /** Content for an alert. */ alert = 1, + /** Content for a banner. */ banner = 2, } diff --git a/src/inApp/enums/IterableInAppDeleteSource.ts b/src/inApp/enums/IterableInAppDeleteSource.ts index 10ae58f5..a75268f8 100644 --- a/src/inApp/enums/IterableInAppDeleteSource.ts +++ b/src/inApp/enums/IterableInAppDeleteSource.ts @@ -1,6 +1,11 @@ -// TODO: Add description +/** + * Ways in which an in-app message might have been deleted. + */ export enum IterableInAppDeleteSource { + /** Deleted by swiping in the inbox. */ inboxSwipe = 0, + /** Deleted by clicking the delete button. */ deleteButton = 1, + /** Deleted in an unknown way. */ unknown = 100, } diff --git a/src/inApp/enums/IterableInAppLocation.ts b/src/inApp/enums/IterableInAppLocation.ts index bca7fb8d..36e809b8 100644 --- a/src/inApp/enums/IterableInAppLocation.ts +++ b/src/inApp/enums/IterableInAppLocation.ts @@ -1,5 +1,7 @@ -// TODO: Add description +/** The places that an in-app message can exist */ export enum IterableInAppLocation { + /** Location is in-app */ inApp = 0, + /** Location is in the inbox */ inbox = 1, } diff --git a/src/inApp/enums/IterableInAppShowResponse.ts b/src/inApp/enums/IterableInAppShowResponse.ts index a6137e2f..cf42a0d8 100644 --- a/src/inApp/enums/IterableInAppShowResponse.ts +++ b/src/inApp/enums/IterableInAppShowResponse.ts @@ -1,4 +1,6 @@ -// TODO: Add description +/** + * Options for showing in-app messages + */ export enum IterableInAppShowResponse { /** Show in-app */ show = 0, diff --git a/src/inApp/enums/IterableInAppTriggerType.ts b/src/inApp/enums/IterableInAppTriggerType.ts index 4f1719a7..80cd9625 100644 --- a/src/inApp/enums/IterableInAppTriggerType.ts +++ b/src/inApp/enums/IterableInAppTriggerType.ts @@ -1,4 +1,6 @@ -// TODO: Add description +/** + * Different types of triggers for displaying in-app messages. + */ export enum IterableInAppTriggerType { /** Tries to display the in-app automatically immediately */ immediate = 0, diff --git a/src/inApp/types/IterableHtmlInAppContentRaw.ts b/src/inApp/types/IterableHtmlInAppContentRaw.ts index 141f4180..1248d2a5 100644 --- a/src/inApp/types/IterableHtmlInAppContentRaw.ts +++ b/src/inApp/types/IterableHtmlInAppContentRaw.ts @@ -1,7 +1,11 @@ import type { IterableEdgeInsetDetails } from '../../core'; -/** The raw in-App content details returned from the server */ +/** + * The raw in-App content details returned from the server. + */ export interface IterableHtmlInAppContentRaw { + /** The HTML content of the in-App message. */ edgeInsets: IterableEdgeInsetDetails; + /** The HTML content of the in-App message. */ html: string; } diff --git a/src/inApp/types/IterableInAppContent.ts b/src/inApp/types/IterableInAppContent.ts index a2a312e5..58c71fd4 100644 --- a/src/inApp/types/IterableInAppContent.ts +++ b/src/inApp/types/IterableInAppContent.ts @@ -1,6 +1,9 @@ import type { IterableInAppContentType } from '../enums'; -// TODO: Add description +/** + * Information about the content of an in-app message in the Iterable SDK. + */ export interface IterableInAppContent { + /** The type of content */ type: IterableInAppContentType; } diff --git a/src/inApp/types/IterableInAppMessageRaw.ts b/src/inApp/types/IterableInAppMessageRaw.ts new file mode 100644 index 00000000..78a1748a --- /dev/null +++ b/src/inApp/types/IterableInAppMessageRaw.ts @@ -0,0 +1,32 @@ +import type { IterableInAppTrigger } from '../classes/IterableInAppTrigger'; + +/** The raw message returned from the `IterableEventName.handleInAppCalled` event */ +export type IterableInAppMessageRaw = { + /** The unique identifier for the message. */ + messageId: string; + /** The campaign identifier associated with the message. */ + campaignId: number; + /** The trigger that initiates the in-app message. */ + trigger: IterableInAppTrigger; + /** The timestamp when the message was created, in milliseconds. */ + createdAt?: number; + /** The timestamp when the message expires, in milliseconds. */ + expiresAt?: number; + /** A boolean indicating if the message should be saved to the inbox. */ + saveToInbox?: boolean; + /** Metadata for the inbox message, including title, subtitle, and icon. */ + inboxMetadata?: { + /** The title of the inbox message. */ + title: string | undefined; + /** The subtitle of the inbox message. */ + subtitle: string | undefined; + /** The icon of the inbox message. */ + icon: string | undefined; + }; + /** Custom payload associated with the message. */ + customPayload?: unknown; + /** A boolean indicating if the message has been read. */ + read?: boolean; + /** The priority level of the message. */ + priorityLevel?: number; +}; diff --git a/src/inApp/types/index.ts b/src/inApp/types/index.ts index b0b0086f..7c5bf06b 100644 --- a/src/inApp/types/index.ts +++ b/src/inApp/types/index.ts @@ -1,2 +1,3 @@ -export * from './IterableInAppContent'; export * from './IterableHtmlInAppContentRaw'; +export * from './IterableInAppContent'; +export * from './IterableInAppMessageRaw'; diff --git a/src/inbox/classes/IterableInboxDataModel.ts b/src/inbox/classes/IterableInboxDataModel.ts index 98c7d12f..c4c2cf7c 100644 --- a/src/inbox/classes/IterableInboxDataModel.ts +++ b/src/inbox/classes/IterableInboxDataModel.ts @@ -15,15 +15,50 @@ import type { const RNIterableAPI = NativeModules.RNIterableAPI; -// TODO: Comment +/** + * The `IterableInboxDataModel` class provides methods to manage and manipulate + * inbox messages. + */ export class IterableInboxDataModel { + /** + * Optional function to filter messages. + * + * This function takes an `IterableInAppMessage` as an argument and returns a boolean value. + * It is used to determine whether a given message should be included based on custom criteria. + * + * @param message - The in-app message to be evaluated. + * @returns A boolean indicating whether the message meets the filter criteria. + */ filterFn?: (message: IterableInAppMessage) => boolean; + /** + * Optional comparator function to determine the order of messages. + * + * @param message1 - The first message to compare. + * @param message2 - The second message to compare. + * @returns A negative number if `message1` should come before `message2`, + * a positive number if `message1` should come after `message2`, + * or 0 if they are considered equal. + */ comparatorFn?: ( message1: IterableInAppMessage, message2: IterableInAppMessage ) => number; + /** + * Optional function to map an IterableInAppMessage to a date string or undefined. + * This function can be used to extract and format the date from a message. + * + * @param message - The IterableInAppMessage object to be mapped. + * @returns A string representing the date or undefined if no date is available. + */ dateMapperFn?: (message: IterableInAppMessage) => string | undefined; + /** + * Sets the filter, comparator, and date mapper functions for the inbox data model. + * + * @param filter - A function to filter messages. It takes an `IterableInAppMessage` as an argument and returns a boolean indicating whether the message should be included. + * @param comparator - A function to compare two messages. It takes two `IterableInAppMessage` objects as arguments and returns a number indicating their relative order. + * @param dateMapper - A function to map a message to a date string. It takes an `IterableInAppMessage` as an argument and returns a string representing the date, or undefined if no date is applicable. + */ set( filter?: (message: IterableInAppMessage) => boolean, comparator?: ( @@ -37,6 +72,12 @@ export class IterableInboxDataModel { this.dateMapperFn = dateMapper; } + /** + * Formats the creation date of an in-app message. + * + * @param message - The in-app message object containing the creation date. + * @returns The formatted date string. Returns an empty string if the creation date is undefined. + */ getFormattedDate(message: IterableInAppMessage) { if (message.createdAt === undefined) { return ''; @@ -49,6 +90,12 @@ export class IterableInboxDataModel { } } + /** + * Retrieves the HTML content for a given message ID. + * + * @param id - The ID of the message to retrieve HTML content for. + * @returns A promise that resolves to the HTML content of the specified message. + */ getHtmlContentForMessageId(id: string): Promise { Iterable.logger.log( 'IterableInboxDataModel.getHtmlContentForItem messageId: ' + id @@ -61,18 +108,35 @@ export class IterableInboxDataModel { ); } + /** + * Marks a message as read in the Iterable inbox. + * + * @param id - The unique identifier of the message to be marked as read. + */ setMessageAsRead(id: string) { Iterable.logger.log('IterableInboxDataModel.setMessageAsRead'); RNIterableAPI.setReadForMessage(id, true); } + /** + * Deletes an item from the inbox by its ID. + * + * @param id - The unique identifier of the item to be deleted. + * @param deleteSource - The source from which the delete action is initiated. + */ deleteItemById(id: string, deleteSource: IterableInAppDeleteSource) { Iterable.logger.log('IterableInboxDataModel.deleteItemById'); RNIterableAPI.removeMessage(id, IterableInAppLocation.inbox, deleteSource); } + /** + * Refreshes the inbox data by fetching the latest messages from Iterable. + * + * @returns A promise that resolves to an array of processed inbox row view models. + * If the fetch operation fails, the promise resolves to an empty array. + */ async refresh(): Promise { return RNIterableAPI.getInboxMessages().then( (messages: IterableInAppMessage[]) => { @@ -84,23 +148,52 @@ export class IterableInboxDataModel { ); } - // inbox session tracking functions - + /** + * Starts a tracking session for the inbox with the given visible rows. + * + * @param visibleRows - An array of `IterableInboxImpressionRowInfo` objects representing the rows that are currently visible. + */ startSession(visibleRows: IterableInboxImpressionRowInfo[] = []) { RNIterableAPI.startSession(visibleRows); } + /** + * Ends the current tracking session and updates the visible rows. + * + * @param visibleRows - An array of `IterableInboxImpressionRowInfo` objects representing the rows that are currently visible. Defaults to an empty array. + * @returns A promise that resolves when the session has ended and the visible rows have been updated. + */ async endSession(visibleRows: IterableInboxImpressionRowInfo[] = []) { await this.updateVisibleRows(visibleRows); RNIterableAPI.endSession(); } + /** + * Updates the visible rows in the inbox. + * + * This method takes an array of `IterableInboxImpressionRowInfo` objects + * representing the rows that are currently visible and updates the + * visibility status. (REVIEW: Where does it update it? In Iterable or in the + * interface?) + * + * @param visibleRows - An array of `IterableInboxImpressionRowInfo` objects + * representing the rows that are currently visible. + * Defaults to an empty array if not provided. + */ updateVisibleRows(visibleRows: IterableInboxImpressionRowInfo[] = []) { RNIterableAPI.updateVisibleRows(visibleRows); } - // private/internal - + /** + * A comparator function to sort `IterableInAppMessage` objects by their creation date in descending order. + * + * @param message1 - The first `IterableInAppMessage` object to compare. + * @param message2 - The second `IterableInAppMessage` object to compare. + * @returns A number indicating the sort order: + * - `1` if `message1` was created after `message2` + * - `-1` if `message1` was created before `message2` + * - `0` if both messages were created at the same time + */ private static sortByMostRecent = ( message1: IterableInAppMessage, message2: IterableInAppMessage @@ -114,6 +207,13 @@ export class IterableInboxDataModel { return 0; }; + /** + * Maps the creation date of an IterableInAppMessage to a formatted string. + * + * @param message - The message object containing the creation date. + * @returns A formatted string representing the creation date and time of the + * message. If the creation date is undefined, returns an empty string. + */ private defaultDateMapper(message: IterableInAppMessage): string { if (message.createdAt === undefined) { return ''; @@ -132,6 +232,13 @@ export class IterableInboxDataModel { return defaultDateString; } + /** + * Processes a list of in-app messages by sorting and filtering them, + * and then mapping each message to an inbox row view model. + * + * @param messages - An array of `IterableInAppMessage` objects to be processed. + * @returns An array of `IterableInboxRowViewModel` objects representing the processed messages. + */ private processMessages( messages: IterableInAppMessage[] ): IterableInboxRowViewModel[] { @@ -140,6 +247,12 @@ export class IterableInboxDataModel { ); } + /** + * Sorts and filters an array of `IterableInAppMessage` objects. + * + * @param messages - The array of messages to be sorted and filtered. + * @returns The sorted and filtered array of messages. + */ private sortAndFilter( messages: IterableInAppMessage[] ): IterableInAppMessage[] { @@ -162,6 +275,12 @@ export class IterableInboxDataModel { return sortedFilteredMessages; } + /** + * Converts an `IterableInAppMessage` into an `IterableInboxRowViewModel`. + * + * @param message - The in-app message to convert. + * @returns An object representing the inbox row view model. + */ private static getInboxRowViewModelForMessage( message: IterableInAppMessage ): IterableInboxRowViewModel { diff --git a/src/inbox/components/IterableInbox.tsx b/src/inbox/components/IterableInbox.tsx index 50ce0d95..53fcebf6 100644 --- a/src/inbox/components/IterableInbox.tsx +++ b/src/inbox/components/IterableInbox.tsx @@ -40,20 +40,152 @@ const ANDROID_HEADLINE_HEIGHT = 70; const HEADLINE_PADDING_LEFT_PORTRAIT = 30; const HEADLINE_PADDING_LEFT_LANDSCAPE = 70; -// TODO: Comment +/** + * Props for the IterableInbox component. + */ export interface IterableInboxProps extends Partial< Pick > { + /** + * Flag which, when switched, returns a user to their inbox from _within_ the + * inbox component (from the details of the particular message to the message + * list) if the inbox is already in view. + * + * @remarks + * Let's say you have bottom tabs in your app, and one of them is the inbox. + * If you click on a message, you may want to be able to return to the inbox + * by clicking on the bottom tab inbox icon. + * + * If this prop is included and correctly set up, clicking on the bottom inbox + * tab when a message is in focus will return the user to the inbox. + * + * If this prop is **NOT** included, clicking on the bottom inbox tab when a + * message is in focus will have no effect. + * + * @example + * ```tsx + * import { NavigationContainer } from '@react-navigation/native'; + * import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + * import { IterableInbox} from '@iterable/react-native-sdk/js/Iterable'; + * + * const Tab = createBottomTabNavigator(); + * + * const MyNavigation = () => { + * const [isInbox, setIsInbox] = useState(false); + * const [returnToInboxTrigger, setReturnToInboxTrigger] = useState(false); + * + * return ( + * + * + * setIsInbox(false)}} + * /> + * { + * // if this is true, then the inbox is already displayed, so + * // go back to the message list if it is not already in view + * if (isInbox) { + * setReturnToInboxTrigger(!returnToInboxTrigger); + * } + * setIsInbox(true); + * } + * }} + * > + * {() => ( + * + * )} + * + * setIsInbox(false)}} + * /> + * + * + * ); + * } + * ``` + */ returnToInboxTrigger?: boolean; + /** Customization for the look and feel of the inbox. */ customizations?: IterableInboxCustomizations; + /** + * The height of the tab bar. + * + * If your app uses custom tab bar dimensions, provide this value to make sure that the inbox component lays out as expected. + */ tabBarHeight?: number; + /** + * The padding of the tab bar. + * + * If your app uses custom tab bar dimensions, provide this value to make sure that the inbox component lays out as expected. + */ tabBarPadding?: number; + /** + * Is safe area mode enabled? + * + * @remarks + * This indicates whether or not the inbox should be displayed inside a React + * Native [`SafeAreaView`](https://reactnative.dev/docs/safeareaview). + * + * If the parent of the inbox component is already inside a `SafeAreaView`, set + * this to `false` as another `SafeAreaView` is not needed. + * + * @example + * ```tsx + * // Safe area mode should be `true` as it is NOT already inside a `SafeAreaView` + * const MyInbox = () => ; + * + * // Safe area mode should be `false` as it is already inside a `SafeAreaView` + * const MyInbox = () => ( + * + * + * + * ); + * ``` + */ safeAreaMode?: boolean; + /** Should the navigation title be shown? */ showNavTitle?: boolean; } -// TODO: Comment +/** + * The `IterableInbox` component is responsible for displaying an inbox of messages. + * It handles fetching messages, displaying them in a list, and showing individual message details. + * It also manages the state of the inbox, including loading state, selected message, and visible message impressions. + * + * @example + * ```tsx + * const [visible, setVisible] = useState(false); + * + * return ( + * <> + *