From f26eda23bf996ac6e5af199faf1c029b1dc91518 Mon Sep 17 00:00:00 2001 From: GitHub Management Service Date: Tue, 1 Jul 2025 14:14:10 +0000 Subject: [PATCH 1/3] feat(auth): Implement JWT authentication for Intercom SDK (iOS 19.0.0, Android 17.0.+) --- __tests__/intercom-jwt.test.js | 206 ++++++++++++++++++ __tests__/setupTests.js | 70 ++++++ android/build.gradle | 2 +- .../reactnative/IntercomErrorCodes.java | 11 +- .../intercom/reactnative/IntercomModule.java | 12 + intercom-react-native.podspec | 2 +- ios/IntercomModule.m | 16 +- package.json | 4 + src/index.tsx | 13 ++ 9 files changed, 327 insertions(+), 9 deletions(-) create mode 100644 __tests__/intercom-jwt.test.js create mode 100644 __tests__/setupTests.js diff --git a/__tests__/intercom-jwt.test.js b/__tests__/intercom-jwt.test.js new file mode 100644 index 00000000..da6206b8 --- /dev/null +++ b/__tests__/intercom-jwt.test.js @@ -0,0 +1,206 @@ +/** + * Tests for setUserJWT API specifically + */ + +describe('Intercom setUserJWT API', () => { + let mockIntercomModule; + let Intercom; + + beforeEach(() => { + jest.resetModules(); + + // Mock React Native + jest.doMock('react-native', () => ({ + NativeModules: { + IntercomModule: { + setUserJWT: jest.fn(), + setUserHash: jest.fn(), + loginUserWithUserAttributes: jest.fn(), + logout: jest.fn(), + updateUser: jest.fn(), + isUserLoggedIn: jest.fn(), + }, + IntercomEventEmitter: { + UNREAD_COUNT_CHANGE_NOTIFICATION: 'UNREAD_COUNT_CHANGE_NOTIFICATION', + WINDOW_DID_HIDE_NOTIFICATION: 'WINDOW_DID_HIDE_NOTIFICATION', + WINDOW_DID_SHOW_NOTIFICATION: 'WINDOW_DID_SHOW_NOTIFICATION', + HELP_CENTER_WINDOW_DID_SHOW_NOTIFICATION: + 'HELP_CENTER_WINDOW_DID_SHOW_NOTIFICATION', + HELP_CENTER_WINDOW_DID_HIDE_NOTIFICATION: + 'HELP_CENTER_WINDOW_DID_HIDE_NOTIFICATION', + startEventListener: jest.fn(), + removeEventListener: jest.fn(), + }, + }, + NativeEventEmitter: jest.fn().mockImplementation(() => ({ + addListener: jest.fn().mockReturnValue({ + remove: jest.fn(), + }), + })), + Platform: { + OS: 'ios', + select: jest.fn((obj) => obj.ios || obj.default), + }, + })); + + const { NativeModules } = require('react-native'); + mockIntercomModule = NativeModules.IntercomModule; + + // Import Intercom after mocking + Intercom = require('../src/index.tsx').default; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('setUserJWT method', () => { + test('should call native setUserJWT with valid JWT', async () => { + const testJWT = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test'; + mockIntercomModule.setUserJWT.mockResolvedValue(true); + + const result = await Intercom.setUserJWT(testJWT); + + expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(testJWT); + expect(result).toBe(true); + }); + + test('should handle JWT authentication errors', async () => { + const invalidJWT = 'invalid.jwt'; + const error = new Error('JWT validation failed'); + mockIntercomModule.setUserJWT.mockRejectedValue(error); + + await expect(Intercom.setUserJWT(invalidJWT)).rejects.toThrow( + 'JWT validation failed' + ); + }); + + test('should work with empty JWT string', async () => { + const emptyJWT = ''; + mockIntercomModule.setUserJWT.mockResolvedValue(true); + + const result = await Intercom.setUserJWT(emptyJWT); + + expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(emptyJWT); + expect(result).toBe(true); + }); + + test('should handle long JWT tokens', async () => { + const longJWT = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNDU2Nzg5MCIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImN1c3RvbV9hdHRyaWJ1dGVzIjp7InBsYW4iOiJwcmVtaXVtIiwiY29tcGFueSI6IkFjbWUgSW5jIn19.very_long_signature'; + mockIntercomModule.setUserJWT.mockResolvedValue(true); + + const result = await Intercom.setUserJWT(longJWT); + + expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(longJWT); + expect(result).toBe(true); + }); + }); + + describe('JWT authentication workflow', () => { + test('should set JWT before user login', async () => { + const jwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.test'; + const userAttributes = { email: 'test@example.com' }; + + mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.loginUserWithUserAttributes.mockResolvedValue(true); + + await Intercom.setUserJWT(jwt); + await Intercom.loginUserWithUserAttributes(userAttributes); + + expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(jwt); + expect( + mockIntercomModule.loginUserWithUserAttributes + ).toHaveBeenCalledWith(userAttributes); + // Verify setUserJWT was called first by checking call counts + expect(mockIntercomModule.setUserJWT).toHaveBeenCalledTimes(1); + expect( + mockIntercomModule.loginUserWithUserAttributes + ).toHaveBeenCalledTimes(1); + }); + + test('should support both JWT and HMAC methods', async () => { + const jwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.test'; + const hash = 'hmac_hash_123'; + + mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.setUserHash.mockResolvedValue(true); + + const jwtResult = await Intercom.setUserJWT(jwt); + const hashResult = await Intercom.setUserHash(hash); + + expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(jwt); + expect(mockIntercomModule.setUserHash).toHaveBeenCalledWith(hash); + expect(jwtResult).toBe(true); + expect(hashResult).toBe(true); + }); + + test('should handle complete authentication flow', async () => { + const jwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.test'; + const userAttributes = { userId: '123', email: 'test@example.com' }; + + mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.loginUserWithUserAttributes.mockResolvedValue(true); + mockIntercomModule.isUserLoggedIn.mockResolvedValue(true); + mockIntercomModule.updateUser.mockResolvedValue(true); + + // Set JWT first + await Intercom.setUserJWT(jwt); + + // Login user + await Intercom.loginUserWithUserAttributes(userAttributes); + + // Check login status + const isLoggedIn = await Intercom.isUserLoggedIn(); + + // Update user + await Intercom.updateUser({ name: 'Updated Name' }); + + expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(jwt); + expect( + mockIntercomModule.loginUserWithUserAttributes + ).toHaveBeenCalledWith(userAttributes); + expect(isLoggedIn).toBe(true); + expect(mockIntercomModule.updateUser).toHaveBeenCalledWith({ + name: 'Updated Name', + }); + }); + }); + + describe('Error handling', () => { + test('should handle network errors', async () => { + const jwt = 'test.jwt.token'; + const networkError = new Error('Network request failed'); + mockIntercomModule.setUserJWT.mockRejectedValue(networkError); + + await expect(Intercom.setUserJWT(jwt)).rejects.toThrow( + 'Network request failed' + ); + }); + + test('should handle invalid JWT format errors', async () => { + const invalidJWT = 'not.a.valid.jwt'; + const formatError = new Error('Invalid JWT format'); + mockIntercomModule.setUserJWT.mockRejectedValue(formatError); + + await expect(Intercom.setUserJWT(invalidJWT)).rejects.toThrow( + 'Invalid JWT format' + ); + }); + + test('should handle JWT signature verification errors', async () => { + const jwtWithBadSignature = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.bad_signature'; + const signatureError = new Error('JWT signature verification failed'); + mockIntercomModule.setUserJWT.mockRejectedValue(signatureError); + + await expect(Intercom.setUserJWT(jwtWithBadSignature)).rejects.toThrow( + 'JWT signature verification failed' + ); + }); + }); +}); diff --git a/__tests__/setupTests.js b/__tests__/setupTests.js new file mode 100644 index 00000000..2557d300 --- /dev/null +++ b/__tests__/setupTests.js @@ -0,0 +1,70 @@ +/** + * Test setup for React Native mocking + */ + +import { NativeModules } from 'react-native'; + +// Mock the native modules +const mockIntercomModule = { + setUserHash: jest.fn(), + setUserJWT: jest.fn(), + loginUnidentifiedUser: jest.fn(), + loginUserWithUserAttributes: jest.fn(), + logout: jest.fn(), + updateUser: jest.fn(), + isUserLoggedIn: jest.fn(), + fetchLoggedInUserAttributes: jest.fn(), + logEvent: jest.fn(), + presentIntercom: jest.fn(), + presentIntercomSpace: jest.fn(), + presentContent: jest.fn(), + presentMessageComposer: jest.fn(), + getUnreadConversationCount: jest.fn(), + hideIntercom: jest.fn(), + setBottomPadding: jest.fn(), + setInAppMessageVisibility: jest.fn(), + setLauncherVisibility: jest.fn(), + setNeedsStatusBarAppearanceUpdate: jest.fn(), + handlePushMessage: jest.fn(), + sendTokenToIntercom: jest.fn(), + setLogLevel: jest.fn(), + fetchHelpCenterCollections: jest.fn(), + fetchHelpCenterCollection: jest.fn(), + searchHelpCenter: jest.fn(), +}; + +const mockEventEmitter = { + UNREAD_COUNT_CHANGE_NOTIFICATION: 'UNREAD_COUNT_CHANGE_NOTIFICATION', + WINDOW_DID_HIDE_NOTIFICATION: 'WINDOW_DID_HIDE_NOTIFICATION', + WINDOW_DID_SHOW_NOTIFICATION: 'WINDOW_DID_SHOW_NOTIFICATION', + HELP_CENTER_WINDOW_DID_SHOW_NOTIFICATION: + 'HELP_CENTER_WINDOW_DID_SHOW_NOTIFICATION', + HELP_CENTER_WINDOW_DID_HIDE_NOTIFICATION: + 'HELP_CENTER_WINDOW_DID_HIDE_NOTIFICATION', + startEventListener: jest.fn(), + removeEventListener: jest.fn(), +}; + +NativeModules.IntercomModule = mockIntercomModule; +NativeModules.IntercomEventEmitter = mockEventEmitter; + +// Mock Platform +const mockPlatform = { + OS: 'ios', + select: jest.fn((obj) => obj.ios || obj.default), +}; + +jest.doMock('react-native', () => ({ + NativeModules: { + IntercomModule: mockIntercomModule, + IntercomEventEmitter: mockEventEmitter, + }, + NativeEventEmitter: jest.fn().mockImplementation(() => ({ + addListener: jest.fn().mockReturnValue({ + remove: jest.fn(), + }), + })), + Platform: mockPlatform, +})); + +export { mockIntercomModule, mockEventEmitter, mockPlatform }; diff --git a/android/build.gradle b/android/build.gradle index 47e83995..7baded6e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -69,5 +69,5 @@ dependencies { //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules implementation "com.google.firebase:firebase-messaging:${safeExtGet('firebaseMessagingVersion', '20.2.+')}" - implementation 'io.intercom.android:intercom-sdk:15.16.+' + implementation 'io.intercom.android:intercom-sdk:17.0.+' } diff --git a/android/src/main/java/com/intercom/reactnative/IntercomErrorCodes.java b/android/src/main/java/com/intercom/reactnative/IntercomErrorCodes.java index 68b7ba76..3d69208e 100644 --- a/android/src/main/java/com/intercom/reactnative/IntercomErrorCodes.java +++ b/android/src/main/java/com/intercom/reactnative/IntercomErrorCodes.java @@ -4,11 +4,12 @@ public class IntercomErrorCodes { public static final String UNIDENTIFIED_REGISTRATION = "101"; public static final String IDENTIFIED_REGISTRATION = "102"; public static final String SET_USER_HASH = "103"; - public static final String UPDATE_USER_HASH = "104"; - public static final String LOG_EVENT_HASH = "105"; - public static final String LOGOUT = "106"; - public static final String SET_LOG_LEVEL = "107"; - public static final String GET_UNREAD_CONVERSATION = "108"; + public static final String SET_USER_JWT = "104"; + public static final String UPDATE_USER_HASH = "105"; + public static final String LOG_EVENT_HASH = "106"; + public static final String LOGOUT = "107"; + public static final String SET_LOG_LEVEL = "108"; + public static final String GET_UNREAD_CONVERSATION = "109"; public static final String DISPLAY_MESSENGER = "201"; public static final String DISPLAY_MESSENGER_COMPOSER = "202"; public static final String DISPLAY_CONTENT = "203"; diff --git a/android/src/main/java/com/intercom/reactnative/IntercomModule.java b/android/src/main/java/com/intercom/reactnative/IntercomModule.java index 2b94bab5..b8c8fa56 100644 --- a/android/src/main/java/com/intercom/reactnative/IntercomModule.java +++ b/android/src/main/java/com/intercom/reactnative/IntercomModule.java @@ -179,6 +179,18 @@ public void setUserHash(String userHash, Promise promise) { } } + @ReactMethod + public void setUserJWT(String jwt, Promise promise) { + try { + Intercom.client().setUserJWT(jwt); + promise.resolve(true); + } catch (Exception err) { + Log.e(NAME, "setUserJWT error:"); + Log.e(NAME, err.toString()); + promise.reject(IntercomErrorCodes.SET_USER_JWT, err.toString()); + } + } + @ReactMethod public void updateUser(ReadableMap params, Promise promise) { UserAttributes userAttributes = IntercomHelpers.buildUserAttributes(params); diff --git a/intercom-react-native.podspec b/intercom-react-native.podspec index 962eab99..80a23ab2 100644 --- a/intercom-react-native.podspec +++ b/intercom-react-native.podspec @@ -20,5 +20,5 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" } s.dependency "React-Core" - s.dependency "Intercom", '~> 18.6.1' + s.dependency "Intercom", '~> 19.0.0' end diff --git a/ios/IntercomModule.m b/ios/IntercomModule.m index 5db7318c..b94b1880 100644 --- a/ios/IntercomModule.m +++ b/ios/IntercomModule.m @@ -12,8 +12,9 @@ @implementation IntercomModule NSString *UNIDENTIFIED_REGISTRATION = @"101"; NSString *IDENTIFIED_REGISTRATION = @"102"; NSString *SET_USER_HASH = @"103"; -NSString *UPDATE_USER = @"104"; -NSString *LOG_EVENT = @"105"; +NSString *SET_USER_JWT = @"104"; +NSString *UPDATE_USER = @"105"; +NSString *LOG_EVENT = @"106"; NSString *UNREAD_CONVERSATION_COUNT = @"107"; NSString *SEND_TOKEN_TO_INTERCOM = @"302"; NSString *FETCH_HELP_CENTER_COLLECTIONS = @"901"; @@ -154,6 +155,17 @@ - (NSData *)dataFromHexString:(NSString *)string { } }; +RCT_EXPORT_METHOD(setUserJWT:(NSString *)jwt + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + @try { + [Intercom setUserJWT:jwt]; + resolve(@(YES)); + } @catch (NSException *exception) { + reject(UPDATE_USER, @"Error in setUserJWT", [self exceptionToError:exception :SET_USER_JWT :@"setUserJWT"]); + } +}; + #pragma mark - Events diff --git a/package.json b/package.json index 57085aab..29129530 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,10 @@ }, "jest": { "preset": "react-native", + "setupFilesAfterEnv": ["/__tests__/setupTests.js"], + "testPathIgnorePatterns": [ + "/__tests__/setupTests.js" + ], "modulePathIgnorePatterns": [ "/example/node_modules", "/sandboxes/node_modules", diff --git a/src/index.tsx b/src/index.tsx index 3bcc776f..e3808294 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -139,6 +139,18 @@ export type IntercomType = { */ setUserHash(hash: string): Promise; + /** + * Set JWT string for authenticating users in the Messenger. + * @note This should be called before any user login takes place. + * + * JWT (JSON Web Token) is the recommended method to secure your Messenger. With JWT, you can ensure that + * bad actors can't impersonate your users, see their conversation history, or make unauthorized updates to data. + * + * @see More information on JWT authentication can be found {@link https://www.intercom.com/help/en/articles/10589769-authenticating-users-in-the-messenger-with-json-web-tokens-jwts here} + * @param jwt A JWT string generated by your server using your Intercom secret key. + */ + setUserJWT(jwt: string): Promise; + /** * Update a user in Intercom with data specified in {@link UserAttributes}. * Full details of the data data attributes that can be stored on a user can be found in {@link UserAttributes}. @@ -303,6 +315,7 @@ const Intercom: IntercomType = { IntercomModule.loginUserWithUserAttributes(userAttributes), logout: () => IntercomModule.logout(), setUserHash: (hash) => IntercomModule.setUserHash(hash), + setUserJWT: (jwt) => IntercomModule.setUserJWT(jwt), updateUser: (userAttributes) => IntercomModule.updateUser(userAttributes), isUserLoggedIn: () => IntercomModule.isUserLoggedIn(), fetchLoggedInUserAttributes: () => From 3915cf22bfc93f533cce21de1b78ee082ff895ce Mon Sep 17 00:00:00 2001 From: GitHub Management Service Date: Wed, 2 Jul 2025 08:36:27 +0000 Subject: [PATCH 2/3] feat: Implement requested changes from PR comment feedback --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 03cfe293..84979308 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -176,6 +176,10 @@ jobs: - restore_cache: keys: - pod-dependencies-{{ checksum "~/project/example/ios/Podfile" }} + - run: + working_directory: example/ios + name: Remove Podfile.lock to avoid version conflicts + command: rm -f Podfile.lock - run: working_directory: example/ios name: Install Pods From 9f963df8ae5a8ab1ebb066a5c8e5269a1b812503 Mon Sep 17 00:00:00 2001 From: GitHub Management Service Date: Wed, 2 Jul 2025 10:04:25 +0000 Subject: [PATCH 3/3] Apply follow-up changes requested in PR comments --- __tests__/intercom-jwt.test.js | 66 +++++++++---------- __tests__/setupTests.js | 2 +- .../intercom/reactnative/IntercomModule.java | 6 +- ios/IntercomModule.m | 6 +- src/index.tsx | 4 +- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/__tests__/intercom-jwt.test.js b/__tests__/intercom-jwt.test.js index da6206b8..5c1fb4a3 100644 --- a/__tests__/intercom-jwt.test.js +++ b/__tests__/intercom-jwt.test.js @@ -1,8 +1,8 @@ /** - * Tests for setUserJWT API specifically + * Tests for setUserJwt API specifically */ -describe('Intercom setUserJWT API', () => { +describe('Intercom setUserJwt API', () => { let mockIntercomModule; let Intercom; @@ -13,7 +13,7 @@ describe('Intercom setUserJWT API', () => { jest.doMock('react-native', () => ({ NativeModules: { IntercomModule: { - setUserJWT: jest.fn(), + setUserJwt: jest.fn(), setUserHash: jest.fn(), loginUserWithUserAttributes: jest.fn(), logout: jest.fn(), @@ -54,46 +54,46 @@ describe('Intercom setUserJWT API', () => { jest.clearAllMocks(); }); - describe('setUserJWT method', () => { - test('should call native setUserJWT with valid JWT', async () => { + describe('setUserJwt method', () => { + test('should call native setUserJwt with valid JWT', async () => { const testJWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test'; - mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.setUserJwt.mockResolvedValue(true); - const result = await Intercom.setUserJWT(testJWT); + const result = await Intercom.setUserJwt(testJWT); - expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(testJWT); + expect(mockIntercomModule.setUserJwt).toHaveBeenCalledWith(testJWT); expect(result).toBe(true); }); test('should handle JWT authentication errors', async () => { const invalidJWT = 'invalid.jwt'; const error = new Error('JWT validation failed'); - mockIntercomModule.setUserJWT.mockRejectedValue(error); + mockIntercomModule.setUserJwt.mockRejectedValue(error); - await expect(Intercom.setUserJWT(invalidJWT)).rejects.toThrow( + await expect(Intercom.setUserJwt(invalidJWT)).rejects.toThrow( 'JWT validation failed' ); }); test('should work with empty JWT string', async () => { const emptyJWT = ''; - mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.setUserJwt.mockResolvedValue(true); - const result = await Intercom.setUserJWT(emptyJWT); + const result = await Intercom.setUserJwt(emptyJWT); - expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(emptyJWT); + expect(mockIntercomModule.setUserJwt).toHaveBeenCalledWith(emptyJWT); expect(result).toBe(true); }); test('should handle long JWT tokens', async () => { const longJWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNDU2Nzg5MCIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImN1c3RvbV9hdHRyaWJ1dGVzIjp7InBsYW4iOiJwcmVtaXVtIiwiY29tcGFueSI6IkFjbWUgSW5jIn19.very_long_signature'; - mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.setUserJwt.mockResolvedValue(true); - const result = await Intercom.setUserJWT(longJWT); + const result = await Intercom.setUserJwt(longJWT); - expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(longJWT); + expect(mockIntercomModule.setUserJwt).toHaveBeenCalledWith(longJWT); expect(result).toBe(true); }); }); @@ -104,18 +104,18 @@ describe('Intercom setUserJWT API', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.test'; const userAttributes = { email: 'test@example.com' }; - mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.setUserJwt.mockResolvedValue(true); mockIntercomModule.loginUserWithUserAttributes.mockResolvedValue(true); - await Intercom.setUserJWT(jwt); + await Intercom.setUserJwt(jwt); await Intercom.loginUserWithUserAttributes(userAttributes); - expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(jwt); + expect(mockIntercomModule.setUserJwt).toHaveBeenCalledWith(jwt); expect( mockIntercomModule.loginUserWithUserAttributes ).toHaveBeenCalledWith(userAttributes); - // Verify setUserJWT was called first by checking call counts - expect(mockIntercomModule.setUserJWT).toHaveBeenCalledTimes(1); + // Verify setUserJwt was called first by checking call counts + expect(mockIntercomModule.setUserJwt).toHaveBeenCalledTimes(1); expect( mockIntercomModule.loginUserWithUserAttributes ).toHaveBeenCalledTimes(1); @@ -126,13 +126,13 @@ describe('Intercom setUserJWT API', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.test'; const hash = 'hmac_hash_123'; - mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.setUserJwt.mockResolvedValue(true); mockIntercomModule.setUserHash.mockResolvedValue(true); - const jwtResult = await Intercom.setUserJWT(jwt); + const jwtResult = await Intercom.setUserJwt(jwt); const hashResult = await Intercom.setUserHash(hash); - expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(jwt); + expect(mockIntercomModule.setUserJwt).toHaveBeenCalledWith(jwt); expect(mockIntercomModule.setUserHash).toHaveBeenCalledWith(hash); expect(jwtResult).toBe(true); expect(hashResult).toBe(true); @@ -143,13 +143,13 @@ describe('Intercom setUserJWT API', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.test'; const userAttributes = { userId: '123', email: 'test@example.com' }; - mockIntercomModule.setUserJWT.mockResolvedValue(true); + mockIntercomModule.setUserJwt.mockResolvedValue(true); mockIntercomModule.loginUserWithUserAttributes.mockResolvedValue(true); mockIntercomModule.isUserLoggedIn.mockResolvedValue(true); mockIntercomModule.updateUser.mockResolvedValue(true); // Set JWT first - await Intercom.setUserJWT(jwt); + await Intercom.setUserJwt(jwt); // Login user await Intercom.loginUserWithUserAttributes(userAttributes); @@ -160,7 +160,7 @@ describe('Intercom setUserJWT API', () => { // Update user await Intercom.updateUser({ name: 'Updated Name' }); - expect(mockIntercomModule.setUserJWT).toHaveBeenCalledWith(jwt); + expect(mockIntercomModule.setUserJwt).toHaveBeenCalledWith(jwt); expect( mockIntercomModule.loginUserWithUserAttributes ).toHaveBeenCalledWith(userAttributes); @@ -175,9 +175,9 @@ describe('Intercom setUserJWT API', () => { test('should handle network errors', async () => { const jwt = 'test.jwt.token'; const networkError = new Error('Network request failed'); - mockIntercomModule.setUserJWT.mockRejectedValue(networkError); + mockIntercomModule.setUserJwt.mockRejectedValue(networkError); - await expect(Intercom.setUserJWT(jwt)).rejects.toThrow( + await expect(Intercom.setUserJwt(jwt)).rejects.toThrow( 'Network request failed' ); }); @@ -185,9 +185,9 @@ describe('Intercom setUserJWT API', () => { test('should handle invalid JWT format errors', async () => { const invalidJWT = 'not.a.valid.jwt'; const formatError = new Error('Invalid JWT format'); - mockIntercomModule.setUserJWT.mockRejectedValue(formatError); + mockIntercomModule.setUserJwt.mockRejectedValue(formatError); - await expect(Intercom.setUserJWT(invalidJWT)).rejects.toThrow( + await expect(Intercom.setUserJwt(invalidJWT)).rejects.toThrow( 'Invalid JWT format' ); }); @@ -196,9 +196,9 @@ describe('Intercom setUserJWT API', () => { const jwtWithBadSignature = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.bad_signature'; const signatureError = new Error('JWT signature verification failed'); - mockIntercomModule.setUserJWT.mockRejectedValue(signatureError); + mockIntercomModule.setUserJwt.mockRejectedValue(signatureError); - await expect(Intercom.setUserJWT(jwtWithBadSignature)).rejects.toThrow( + await expect(Intercom.setUserJwt(jwtWithBadSignature)).rejects.toThrow( 'JWT signature verification failed' ); }); diff --git a/__tests__/setupTests.js b/__tests__/setupTests.js index 2557d300..335c28af 100644 --- a/__tests__/setupTests.js +++ b/__tests__/setupTests.js @@ -7,7 +7,7 @@ import { NativeModules } from 'react-native'; // Mock the native modules const mockIntercomModule = { setUserHash: jest.fn(), - setUserJWT: jest.fn(), + setUserJwt: jest.fn(), loginUnidentifiedUser: jest.fn(), loginUserWithUserAttributes: jest.fn(), logout: jest.fn(), diff --git a/android/src/main/java/com/intercom/reactnative/IntercomModule.java b/android/src/main/java/com/intercom/reactnative/IntercomModule.java index b8c8fa56..47b9d1a7 100644 --- a/android/src/main/java/com/intercom/reactnative/IntercomModule.java +++ b/android/src/main/java/com/intercom/reactnative/IntercomModule.java @@ -180,12 +180,12 @@ public void setUserHash(String userHash, Promise promise) { } @ReactMethod - public void setUserJWT(String jwt, Promise promise) { + public void setUserJwt(String jwt, Promise promise) { try { - Intercom.client().setUserJWT(jwt); + Intercom.client().setUserJwt(jwt); promise.resolve(true); } catch (Exception err) { - Log.e(NAME, "setUserJWT error:"); + Log.e(NAME, "setUserJwt error:"); Log.e(NAME, err.toString()); promise.reject(IntercomErrorCodes.SET_USER_JWT, err.toString()); } diff --git a/ios/IntercomModule.m b/ios/IntercomModule.m index b94b1880..02490ec2 100644 --- a/ios/IntercomModule.m +++ b/ios/IntercomModule.m @@ -155,14 +155,14 @@ - (NSData *)dataFromHexString:(NSString *)string { } }; -RCT_EXPORT_METHOD(setUserJWT:(NSString *)jwt +RCT_EXPORT_METHOD(setUserJwt:(NSString *)jwt resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { @try { - [Intercom setUserJWT:jwt]; + [Intercom setUserJwt:jwt]; resolve(@(YES)); } @catch (NSException *exception) { - reject(UPDATE_USER, @"Error in setUserJWT", [self exceptionToError:exception :SET_USER_JWT :@"setUserJWT"]); + reject(UPDATE_USER, @"Error in setUserJwt", [self exceptionToError:exception :SET_USER_JWT :@"setUserJwt"]); } }; diff --git a/src/index.tsx b/src/index.tsx index e3808294..d3391e11 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -149,7 +149,7 @@ export type IntercomType = { * @see More information on JWT authentication can be found {@link https://www.intercom.com/help/en/articles/10589769-authenticating-users-in-the-messenger-with-json-web-tokens-jwts here} * @param jwt A JWT string generated by your server using your Intercom secret key. */ - setUserJWT(jwt: string): Promise; + setUserJwt(jwt: string): Promise; /** * Update a user in Intercom with data specified in {@link UserAttributes}. @@ -315,7 +315,7 @@ const Intercom: IntercomType = { IntercomModule.loginUserWithUserAttributes(userAttributes), logout: () => IntercomModule.logout(), setUserHash: (hash) => IntercomModule.setUserHash(hash), - setUserJWT: (jwt) => IntercomModule.setUserJWT(jwt), + setUserJwt: (jwt) => IntercomModule.setUserJwt(jwt), updateUser: (userAttributes) => IntercomModule.updateUser(userAttributes), isUserLoggedIn: () => IntercomModule.isUserLoggedIn(), fetchLoggedInUserAttributes: () =>