diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 35107e8ad..f19daef5d 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -14,6 +14,7 @@ import { IterableConfig, IterableInAppShowResponse, IterableLogLevel, + IterableRetryBackoff, } from '@iterable/react-native-sdk'; import { Route } from '../constants/routes'; @@ -126,6 +127,16 @@ export const IterableAppProvider: FunctionComponent< config.inAppDisplayInterval = 1.0; // Min gap between in-apps. No need to set this in production. + config.retryPolicy = { + maxRetry: 5, + retryInterval: 10, + retryBackoff: IterableRetryBackoff.LINEAR, + }; + + config.onJWTError = (authFailure) => { + console.error('Error fetching JWT:', authFailure); + }; + config.urlHandler = (url: string) => { const routeNames = [Route.Commerce, Route.Inbox, Route.User]; for (const route of routeNames) { diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index 390263153..1949c15bf 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -70,12 +70,14 @@ export class MockRNIterableAPI { static initialize2WithApiKey = jest.fn().mockResolvedValue(true); - static wakeApp = jest.fn() + static wakeApp = jest.fn(); static setInAppShowResponse = jest.fn(); static passAlongAuthToken = jest.fn(); + static pauseAuthRetries = jest.fn(); + static async getInAppMessages(): Promise { return await new Promise((resolve) => { resolve(MockRNIterableAPI.messages); diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index 4bddecb32..391fadbb7 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -116,6 +116,7 @@ export interface Spec extends TurboModule { // Auth passAlongAuthToken(authToken?: string | null): void; + pauseAuthRetries(pauseRetry: boolean): void; // Wake app -- android only wakeApp(): void; diff --git a/src/core/classes/Iterable.test.ts b/src/core/classes/Iterable.test.ts index d8540dc92..79c47792c 100644 --- a/src/core/classes/Iterable.test.ts +++ b/src/core/classes/Iterable.test.ts @@ -912,4 +912,261 @@ describe('Iterable', () => { }); }); }); + + describe('authManager', () => { + describe('pauseAuthRetries', () => { + it('should call RNIterableAPI.pauseAuthRetries with true when pauseRetry is true', () => { + // GIVEN pauseRetry is true + const pauseRetry = true; + + // WHEN pauseAuthRetries is called + Iterable.authManager.pauseAuthRetries(pauseRetry); + + // THEN RNIterableAPI.pauseAuthRetries is called with true + expect(MockRNIterableAPI.pauseAuthRetries).toBeCalledWith(true); + }); + + it('should call RNIterableAPI.pauseAuthRetries with false when pauseRetry is false', () => { + // GIVEN pauseRetry is false + const pauseRetry = false; + + // WHEN pauseAuthRetries is called + Iterable.authManager.pauseAuthRetries(pauseRetry); + + // THEN RNIterableAPI.pauseAuthRetries is called with false + expect(MockRNIterableAPI.pauseAuthRetries).toBeCalledWith(false); + }); + + it('should return the result from RNIterableAPI.pauseAuthRetries', () => { + // GIVEN RNIterableAPI.pauseAuthRetries returns a value + const expectedResult = 'pause-result'; + MockRNIterableAPI.pauseAuthRetries = jest + .fn() + .mockReturnValue(expectedResult); + + // WHEN pauseAuthRetries is called + const result = Iterable.authManager.pauseAuthRetries(true); + + // THEN the result is returned + expect(result).toBe(expectedResult); + }); + }); + + describe('passAlongAuthToken', () => { + it('should call RNIterableAPI.passAlongAuthToken with a valid string token', async () => { + // GIVEN a valid auth token + const authToken = 'valid-jwt-token'; + const expectedResponse = new IterableAuthResponse(); + expectedResponse.authToken = 'new-token'; + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN passAlongAuthToken is called + const result = await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN RNIterableAPI.passAlongAuthToken is called with the token + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith(authToken); + expect(result).toBe(expectedResponse); + }); + + it('should call RNIterableAPI.passAlongAuthToken with null token', async () => { + // GIVEN a null auth token + const authToken = null; + const expectedResponse = 'success'; + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN passAlongAuthToken is called + const result = await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN RNIterableAPI.passAlongAuthToken is called with null + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith(null); + expect(result).toBe(expectedResponse); + }); + + it('should call RNIterableAPI.passAlongAuthToken with undefined token', async () => { + // GIVEN an undefined auth token + const authToken = undefined; + const expectedResponse = undefined; + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN passAlongAuthToken is called + const result = await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN RNIterableAPI.passAlongAuthToken is called with undefined + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith(undefined); + expect(result).toBe(expectedResponse); + }); + + it('should call RNIterableAPI.passAlongAuthToken with empty string token', async () => { + // GIVEN an empty string auth token + const authToken = ''; + const expectedResponse = new IterableAuthResponse(); + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN passAlongAuthToken is called + const result = await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN RNIterableAPI.passAlongAuthToken is called with empty string + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith(''); + expect(result).toBe(expectedResponse); + }); + + it('should return IterableAuthResponse when API returns IterableAuthResponse', async () => { + // GIVEN API returns IterableAuthResponse + const authToken = 'test-token'; + const expectedResponse = new IterableAuthResponse(); + expectedResponse.authToken = 'new-token'; + expectedResponse.successCallback = jest.fn(); + expectedResponse.failureCallback = jest.fn(); + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN passAlongAuthToken is called + const result = await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN the result is the expected IterableAuthResponse + expect(result).toBe(expectedResponse); + expect(result).toBeInstanceOf(IterableAuthResponse); + }); + + it('should return string when API returns string', async () => { + // GIVEN API returns string + const authToken = 'test-token'; + const expectedResponse = 'success-string'; + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN passAlongAuthToken is called + const result = await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN the result is the expected string + expect(result).toBe(expectedResponse); + expect(typeof result).toBe('string'); + }); + + it('should return undefined when API returns undefined', async () => { + // GIVEN API returns undefined + const authToken = 'test-token'; + const expectedResponse = undefined; + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN passAlongAuthToken is called + const result = await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN the result is undefined + expect(result).toBeUndefined(); + }); + + it('should handle API rejection and propagate the error', async () => { + // GIVEN API rejects with an error + const authToken = 'test-token'; + const expectedError = new Error('API Error'); + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockRejectedValue(expectedError); + + // WHEN passAlongAuthToken is called + // THEN the error is propagated + await expect( + Iterable.authManager.passAlongAuthToken(authToken) + ).rejects.toThrow('API Error'); + }); + + it('should handle API rejection with network error', async () => { + // GIVEN API rejects with a network error + const authToken = 'test-token'; + const networkError = new Error('Network request failed'); + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockRejectedValue(networkError); + + // WHEN passAlongAuthToken is called + // THEN the network error is propagated + await expect( + Iterable.authManager.passAlongAuthToken(authToken) + ).rejects.toThrow('Network request failed'); + }); + + it('should handle API rejection with timeout error', async () => { + // GIVEN API rejects with a timeout error + const authToken = 'test-token'; + const timeoutError = new Error('Request timeout'); + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockRejectedValue(timeoutError); + + // WHEN passAlongAuthToken is called + // THEN the timeout error is propagated + await expect( + Iterable.authManager.passAlongAuthToken(authToken) + ).rejects.toThrow('Request timeout'); + }); + }); + + describe('integration', () => { + it('should work with both methods in sequence', async () => { + // GIVEN a sequence of operations + const authToken = 'test-token'; + const expectedResponse = new IterableAuthResponse(); + MockRNIterableAPI.pauseAuthRetries = jest + .fn() + .mockReturnValue('paused'); + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValue(expectedResponse); + + // WHEN calling both methods in sequence + const pauseResult = Iterable.authManager.pauseAuthRetries(true); + const tokenResult = + await Iterable.authManager.passAlongAuthToken(authToken); + + // THEN both operations should work correctly + expect(pauseResult).toBe('paused'); + expect(tokenResult).toBe(expectedResponse); + expect(MockRNIterableAPI.pauseAuthRetries).toBeCalledWith(true); + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith(authToken); + }); + + it('should handle rapid successive calls', async () => { + // GIVEN rapid successive calls + const authToken1 = 'token1'; + const authToken2 = 'token2'; + const response1 = new IterableAuthResponse(); + const response2 = 'success'; + MockRNIterableAPI.passAlongAuthToken = jest + .fn() + .mockResolvedValueOnce(response1) + .mockResolvedValueOnce(response2); + + // WHEN making rapid successive calls + const promise1 = Iterable.authManager.passAlongAuthToken(authToken1); + const promise2 = Iterable.authManager.passAlongAuthToken(authToken2); + const [result1, result2] = await Promise.all([promise1, promise2]); + + // THEN both calls should work correctly + expect(result1).toBe(response1); + expect(result2).toBe(response2); + expect(MockRNIterableAPI.passAlongAuthToken).toHaveBeenCalledTimes(2); + expect(MockRNIterableAPI.passAlongAuthToken).toHaveBeenNthCalledWith( + 1, + authToken1 + ); + expect(MockRNIterableAPI.passAlongAuthToken).toHaveBeenNthCalledWith( + 2, + authToken2 + ); + }); + }); + }); }); diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index a617a179b..a893236b8 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -1,4 +1,3 @@ -/* eslint-disable eslint-comments/no-unlimited-disable */ import { Linking, NativeEventEmitter, Platform } from 'react-native'; import { buildInfo } from '../../itblBuildInfo'; @@ -10,7 +9,8 @@ import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage'; import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSource'; import { IterableInAppDeleteSource } from '../../inApp/enums/IterableInAppDeleteSource'; import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation'; -import { IterableAuthResponseResult, IterableEventName } from '../enums'; +import { IterableAuthResponseResult } from '../enums/IterableAuthResponseResult'; +import { IterableEventName } from '../enums/IterableEventName'; // Add this type-only import to avoid circular dependency import type { IterableInAppManager } from '../../inApp/classes/IterableInAppManager'; @@ -22,6 +22,8 @@ import { IterableAuthResponse } from './IterableAuthResponse'; import type { IterableCommerceItem } from './IterableCommerceItem'; import { IterableConfig } from './IterableConfig'; import { IterableLogger } from './IterableLogger'; +import type { IterableAuthFailure } from '../types/IterableAuthFailure'; +import { IterableAuthManager } from './IterableAuthManager'; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); @@ -79,7 +81,7 @@ export class Iterable { const { IterableInAppManager, - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-require-imports } = require('../../inApp/classes/IterableInAppManager'); this._inAppManager = new IterableInAppManager(); } @@ -88,6 +90,19 @@ export class Iterable { private static _inAppManager: IterableInAppManager | undefined; + /** + * Authentication manager for the current user. + * + * This property provides access to authentication functionality including + * pausing the authentication retry mechanism. + * + * @example + * ```typescript + * Iterable.authManager.pauseAuthRetries(true); + * ``` + */ + static authManager: IterableAuthManager = new IterableAuthManager(); + /** * Initializes the Iterable React Native SDK in your app's Javascript or Typescript code. * @@ -953,7 +968,7 @@ export class Iterable { * @internal */ private static setupEventHandlers() { - //Remove all listeners to avoid duplicate listeners + // Remove all listeners to avoid duplicate listeners RNEventEmitter.removeAllListeners(IterableEventName.handleUrlCalled); RNEventEmitter.removeAllListeners(IterableEventName.handleInAppCalled); RNEventEmitter.removeAllListeners( @@ -1005,14 +1020,14 @@ export class Iterable { let authResponseCallback: IterableAuthResponseResult; RNEventEmitter.addListener(IterableEventName.handleAuthCalled, () => { // MOB-10423: Check if we can use chain operator (?.) here instead - + // Asks frontend of the client/app to pass authToken Iterable.savedConfig.authHandler!() .then((promiseResult) => { // Promise result can be either just String OR of type AuthResponse. // If type AuthReponse, authToken will be parsed looking for `authToken` within promised object. Two additional listeners will be registered for success and failure callbacks sent by native bridge layer. // Else it will be looked for as a String. if (typeof promiseResult === typeof new IterableAuthResponse()) { - RNIterableAPI.passAlongAuthToken( + Iterable.authManager.passAlongAuthToken( (promiseResult as IterableAuthResponse).authToken ); @@ -1026,6 +1041,8 @@ export class Iterable { } else if ( authResponseCallback === IterableAuthResponseResult.FAILURE ) { + // We are currently only reporting JWT related errors. In + // the future, we should handle other types of errors as well. if ((promiseResult as IterableAuthResponse).failureCallback) { (promiseResult as IterableAuthResponse).failureCallback?.(); } @@ -1037,9 +1054,9 @@ export class Iterable { }, 1000); // Use unref() to prevent the timeout from keeping the process alive timeoutId.unref(); - } else if (typeof promiseResult === typeof '') { + } else if (typeof promiseResult === 'string') { //If promise only returns string - RNIterableAPI.passAlongAuthToken(promiseResult as string); + Iterable.authManager.passAlongAuthToken(promiseResult as string); } else { Iterable?.logger?.log( 'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.' @@ -1057,8 +1074,14 @@ export class Iterable { ); RNEventEmitter.addListener( IterableEventName.handleAuthFailureCalled, - () => { + (authFailureResponse: IterableAuthFailure) => { + // Mark the flag for above listener to indicate something failed. + // `catch(err)` will only indicate failure on high level. No actions + // should be taken inside `catch(err)`. authResponseCallback = IterableAuthResponseResult.FAILURE; + + // Call the actual JWT error with `authFailure` object. + Iterable.savedConfig?.onJWTError?.(authFailureResponse); } ); } diff --git a/src/core/classes/IterableAuthManager.ts b/src/core/classes/IterableAuthManager.ts new file mode 100644 index 000000000..9df15c03a --- /dev/null +++ b/src/core/classes/IterableAuthManager.ts @@ -0,0 +1,44 @@ +import RNIterableAPI from '../../api'; +import { IterableAuthResponse } from './IterableAuthResponse'; + +/** + * Manages the authentication for the Iterable SDK. + * + * @example + * ```typescript + * const authManager = new IterableAuthManager(); + * ``` + */ +export class IterableAuthManager { + /** + * Pause the authentication retry mechanism. + * + * @param pauseRetry - Whether to pause the authentication retry mechanism + * + * @example + * ```typescript + * const authManager = new IterableAuthManager(); + * authManager.pauseAuthRetries(true); + * ``` + */ + pauseAuthRetries(pauseRetry: boolean) { + return RNIterableAPI.pauseAuthRetries(pauseRetry); + } + + /** + * Pass along an auth token to the SDK. + * + * @param authToken - The auth token to pass along + * + * @example + * ```typescript + * const authManager = new IterableAuthManager(); + * authManager.passAlongAuthToken(MY_AUTH_TOKEN); + * ``` + */ + passAlongAuthToken( + authToken: string | null | undefined + ): Promise { + return RNIterableAPI.passAlongAuthToken(authToken); + } +} diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 1c0550b0c..c8ee67400 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -1,10 +1,10 @@ import { type IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage'; -import { IterableInAppShowResponse } from '../../inApp/enums'; -import { - IterableDataRegion, - IterableLogLevel, - IterablePushPlatform, -} from '../enums'; +import { IterableInAppShowResponse } from '../../inApp/enums/IterableInAppShowResponse'; +import { IterableDataRegion } from '../enums/IterableDataRegion'; +import { IterableLogLevel } from '../enums/IterableLogLevel'; +import { IterablePushPlatform } from '../enums/IterablePushPlatform'; +import type { IterableAuthFailure } from '../types/IterableAuthFailure'; +import type { IterableRetryPolicy } from '../types/IterableRetryPolicy'; import { IterableAction } from './IterableAction'; import type { IterableActionContext } from './IterableActionContext'; import type { IterableAuthResponse } from './IterableAuthResponse'; @@ -206,6 +206,25 @@ export class IterableConfig { */ authHandler?: () => Promise; + /** + * A callback function that is called when the SDK encounters an error while + * validing the JWT. + * + * The retry for JWT should be automatically handled by the native SDK, so + * this is just for logging/transparency purposes. + * + * @param authFailure - The details of the auth failure. + * + * @example + * ```typescript + * const config = new IterableConfig(); + * config.onJWTError = (authFailure) => { + * console.error('Error fetching JWT:', authFailure); + * }; + * ``` + */ + onJWTError?: (authFailure: IterableAuthFailure) => void; + /** * Set the verbosity of Android and iOS project's log system. * @@ -213,6 +232,12 @@ export class IterableConfig { */ logLevel: IterableLogLevel = IterableLogLevel.info; + /** + * Configuration for JWT refresh retry behavior. + * If not specified, the SDK will use default retry behavior. + */ + retryPolicy?: IterableRetryPolicy; + /** * Set whether the React Native SDK should print function calls to console. * @@ -342,6 +367,7 @@ export class IterableConfig { dataRegion: this.dataRegion, pushPlatform: this.pushPlatform, encryptionEnforced: this.encryptionEnforced, + retryPolicy: this.retryPolicy, }; } } diff --git a/src/core/enums/IterableAuthFailureReason.ts b/src/core/enums/IterableAuthFailureReason.ts new file mode 100644 index 000000000..a86c6f782 --- /dev/null +++ b/src/core/enums/IterableAuthFailureReason.ts @@ -0,0 +1,39 @@ +/** + * The reason for the failure of an authentication attempt. + * + * This is generally related to JWT token validation. + */ +export enum IterableAuthFailureReason { + /** + * An auth token's expiration must be less than one year from its issued-at + * time. + */ + AUTH_TOKEN_EXPIRATION_INVALID = 'AUTH_TOKEN_EXPIRATION_INVALID', + /** The token has expired. */ + AUTH_TOKEN_EXPIRED = 'AUTH_TOKEN_EXPIRED', + /** Token has an invalid format (failed a regular expression check). */ + AUTH_TOKEN_FORMAT_INVALID = 'AUTH_TOKEN_FORMAT_INVALID', + /** `onAuthTokenRequested` threw an exception. */ + AUTH_TOKEN_GENERATION_ERROR = 'AUTH_TOKEN_GENERATION_ERROR', + /** Any other error not captured by another constant. */ + AUTH_TOKEN_GENERIC_ERROR = 'AUTH_TOKEN_GENERIC_ERROR', + /** Iterable has invalidated this token and it cannot be used. */ + AUTH_TOKEN_INVALIDATED = 'AUTH_TOKEN_INVALIDATED', + /** The request to Iterable's API did not include a JWT authorization header. */ + AUTH_TOKEN_MISSING = 'AUTH_TOKEN_MISSING', + /** `onAuthTokenRequested` returned a null JWT token. */ + AUTH_TOKEN_NULL = 'AUTH_TOKEN_NULL', + /** + * Iterable could not decode the token's payload (`iat`, `exp`, `email`, + * or `userId`). + */ + AUTH_TOKEN_PAYLOAD_INVALID = 'AUTH_TOKEN_PAYLOAD_INVALID', + /** Iterable could not validate the token's authenticity. */ + AUTH_TOKEN_SIGNATURE_INVALID = 'AUTH_TOKEN_SIGNATURE_INVALID', + /** + * The token doesn't include an `email` or a `userId`. Or, one of these + * values is included, but it references a user that isn't in the Iterable + * project. + */ + AUTH_TOKEN_USER_KEY_INVALID = 'AUTH_TOKEN_USER_KEY_INVALID', +} diff --git a/src/core/enums/IterableRetryBackoff.ts b/src/core/enums/IterableRetryBackoff.ts new file mode 100644 index 000000000..526b58eaf --- /dev/null +++ b/src/core/enums/IterableRetryBackoff.ts @@ -0,0 +1,17 @@ +/* eslint-disable tsdoc/syntax */ + +/** + * The type of backoff to use when retrying a request. + */ +export enum IterableRetryBackoff { + /** + * Linear backoff (each retry will wait for a fixed interval) + * TODO: check with @Ayyanchira if this is correct + */ + LINEAR = 'LINEAR', + /** + * Exponential backoff (each retry will wait for an interval that increases exponentially) + * TODO: check with @Ayyanchira if this is correct + */ + EXPONENTIAL = 'EXPONENTIAL', +} diff --git a/src/core/enums/index.ts b/src/core/enums/index.ts index e95b5350c..52f4eb20d 100644 --- a/src/core/enums/index.ts +++ b/src/core/enums/index.ts @@ -1,6 +1,8 @@ export * from './IterableActionSource'; +export * from './IterableAuthFailureReason'; export * from './IterableAuthResponseResult'; export * from './IterableDataRegion'; export * from './IterableEventName'; export * from './IterableLogLevel'; export * from './IterablePushPlatform'; +export * from './IterableRetryBackoff'; diff --git a/src/core/types/IterableAuthFailure.ts b/src/core/types/IterableAuthFailure.ts new file mode 100644 index 000000000..0f2d1cf5e --- /dev/null +++ b/src/core/types/IterableAuthFailure.ts @@ -0,0 +1,18 @@ +import type { IterableAuthFailureReason } from "../enums/IterableAuthFailureReason"; + +/** + * The details of an auth failure. + */ +export interface IterableAuthFailure { + /** `userId` or `email` of the signed-in user */ + userKey: string; + + /** The `authToken` which caused the failure */ + failedAuthToken: string; + + /** The timestamp of the failed request */ + failedRequestTime: number; + + /** Indicates a reason for failure */ + failureReason: IterableAuthFailureReason; +} diff --git a/src/core/types/IterableRetryPolicy.ts b/src/core/types/IterableRetryPolicy.ts new file mode 100644 index 000000000..318b705a0 --- /dev/null +++ b/src/core/types/IterableRetryPolicy.ts @@ -0,0 +1,16 @@ +import type { IterableRetryBackoff } from "../enums/IterableRetryBackoff"; + +/** + * The policy for retrying an authentication attempt. + */ +export interface IterableRetryPolicy { + /** Number of consecutive JWT refresh retries the SDK should attempt */ + maxRetry: number; + /** + * Duration between JWT refresh retries in seconds + * (starting point for retry backoff) + */ + retryInterval: number; + /** The backoff pattern to apply between retry attempts. */ + retryBackoff: IterableRetryBackoff; +} diff --git a/src/core/types/index.ts b/src/core/types/index.ts index f5d846482..7659a76e4 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -1 +1,3 @@ +export * from './IterableAuthFailure'; export * from './IterableEdgeInsetDetails'; +export * from './IterableRetryPolicy'; diff --git a/src/index.tsx b/src/index.tsx index 885cd74bd..240ac51f5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -14,17 +14,24 @@ export { } from './core/classes'; export { IterableActionSource, + IterableAuthFailureReason, + IterableAuthResponseResult, IterableDataRegion, IterableEventName, IterableLogLevel, IterablePushPlatform, + IterableRetryBackoff, } from './core/enums'; export { useAppStateListener, useDeviceOrientation, type IterableDeviceOrientation, } from './core/hooks'; -export { type IterableEdgeInsetDetails } from './core/types'; +export type { + IterableAuthFailure, + IterableEdgeInsetDetails, + IterableRetryPolicy, +} from './core/types'; export { IterableHtmlInAppContent, IterableInAppCloseSource,