Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bd32975
feat: add authentication failure and retry policy enums and types
lposen Oct 7, 2025
50ef0e6
chore: update eslint-config-prettier and add prettier-eslint dependency
lposen Oct 7, 2025
af0065e
feat: export new authentication and retry policy types in index files
lposen Oct 7, 2025
762f333
feat: enhance IterableConfig with JWT error handling and retry policy…
lposen Oct 7, 2025
cfe1de2
feat: add onAuthFailure and pauseAuthRetries methods to Iterable class
lposen Oct 7, 2025
7fde4e7
feat: implement retry policy and JWT error handling in IterableAppPro…
lposen Oct 7, 2025
5810a0a
feat: improve JWT error handling and enhance IterableConfig with addi…
lposen Oct 7, 2025
e85d660
refactor: remove onAuthFailure method and update event handler setup …
lposen Oct 7, 2025
c32447f
chore: remove unused index.ts file from hooks directory
lposen Oct 7, 2025
6d8c45a
refactor: simplify authHandler type and standardize IterableAuthFailu…
lposen Oct 7, 2025
a63b9dc
refactor: remove onJWTErrorPresent flag from IterableConfig to stream…
lposen Oct 7, 2025
786a079
Merge branch 'jwt/master' into jwt/MOB-10946-task-2-authfailure-and-r…
lposen Oct 9, 2025
f1d10cb
chore: update yarn.lock
lposen Oct 9, 2025
65cb551
feat: add authentication manager to Iterable class
lposen Oct 10, 2025
5bbb5fc
refactor: remove pauseAuthRetries method from Iterable class
lposen Oct 10, 2025
92fbff1
chore: disable TSDoc syntax rule for IterableRetryBackoff enum
lposen Oct 10, 2025
e94100f
feat: add pauseAuthRetries method to authentication manager and enhan…
lposen Oct 10, 2025
841f63f
Merge branch 'jwt/master' into jwt/MOB-10946-task-2-authfailure-and-r…
lposen Oct 10, 2025
1dbd4e2
Merge branch 'jwt/master' into jwt/MOB-10946-task-2-authfailure-and-r…
lposen Oct 13, 2025
71ac5c4
docs: add better comments to IterableAuthManager
lposen Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions example/src/hooks/useIterableApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
IterableConfig,
IterableInAppShowResponse,
IterableLogLevel,
IterableRetryBackoff,
} from '@iterable/react-native-sdk';

import { Route } from '../constants/routes';
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion src/__mocks__/MockRNIterableAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IterableInAppMessage[] | undefined> {
return await new Promise((resolve) => {
resolve(MockRNIterableAPI.messages);
Expand Down
1 change: 1 addition & 0 deletions src/api/NativeRNIterableAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
257 changes: 257 additions & 0 deletions src/core/classes/Iterable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
});
});
});
});
Loading