From de06973abb99a629c8cb8f37b4592b71d2acbcd3 Mon Sep 17 00:00:00 2001 From: Niamh Coleman <92738247+niamh-coleman@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:24:46 +0100 Subject: [PATCH 1/2] fix(android): guard deconstructRegistration against null Registration Intercom.client().fetchLoggedInUserAttributes() returns null when no user is registered (e.g. after the OS kills the app). The Android bridge passed that null straight into IntercomHelpers.deconstructRegistration, which dereferenced it via registration.getEmail() and crashed the process with NullPointerException. Return an empty WritableMap on null, matching the iOS bridge's nil-handling in IntercomAttributesBuilder.dictionaryForUserAttributes:. Fixes intercom/intercom#520691 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../main/java/com/intercom/reactnative/IntercomHelpers.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/src/main/java/com/intercom/reactnative/IntercomHelpers.java b/android/src/main/java/com/intercom/reactnative/IntercomHelpers.java index b40fa86f..7cef1ed1 100644 --- a/android/src/main/java/com/intercom/reactnative/IntercomHelpers.java +++ b/android/src/main/java/com/intercom/reactnative/IntercomHelpers.java @@ -248,6 +248,9 @@ public static String getValueAsStringForKey(ReadableMap map, String key) { public static WritableMap deconstructRegistration(Registration registration) { WritableMap registrationMap = Arguments.createMap(); + if (registration == null) { + return registrationMap; + } if (registration.getEmail() != null) { registrationMap.putString("email", registration.getEmail()); } From 4955d3e1e199ffb2fef3c0c6e1dde793176a7e47 Mon Sep 17 00:00:00 2001 From: Niamh Coleman <92738247+niamh-coleman@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:55:52 +0100 Subject: [PATCH 2/2] test(android): add JVM unit tests for IntercomHelpers.deconstructRegistration Bootstraps a minimal Android JVM unit-test setup for the library (JUnit 4 + Mockito 5) and adds a regression guard for the null Registration crash in intercom/intercom#520691. The null-case test fails with the exact production NPE message ("Cannot invoke ... Registration.getEmail() because registration is null") against the pre-fix helper and passes after the fix. Run with: cd examples/example/android && \ ./gradlew :intercomreactnative:testDebugUnitTest Co-Authored-By: Claude Opus 4.7 (1M context) --- android/build.gradle | 10 +++ .../reactnative/IntercomHelpersTest.java | 63 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 android/src/test/java/com/intercom/reactnative/IntercomHelpersTest.java diff --git a/android/build.gradle b/android/build.gradle index 3dc151b6..bd042c03 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -68,6 +68,13 @@ android { } } } + + testOptions { + unitTests { + includeAndroidResources = false + returnDefaultValues = true + } + } } repositories { @@ -86,4 +93,7 @@ dependencies { implementation "com.google.firebase:firebase-messaging:24.1.2" implementation 'io.intercom.android:intercom-sdk:18.2.0' implementation 'io.intercom.android:intercom-sdk-ui:18.2.0' + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.11.0' } diff --git a/android/src/test/java/com/intercom/reactnative/IntercomHelpersTest.java b/android/src/test/java/com/intercom/reactnative/IntercomHelpersTest.java new file mode 100644 index 00000000..39c33c32 --- /dev/null +++ b/android/src/test/java/com/intercom/reactnative/IntercomHelpersTest.java @@ -0,0 +1,63 @@ +package com.intercom.reactnative; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mockStatic; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.JavaOnlyMap; +import com.facebook.react.bridge.WritableMap; + +import io.intercom.android.sdk.identity.Registration; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; + +public class IntercomHelpersTest { + + private MockedStatic argumentsMock; + + @Before + public void setUp() { + argumentsMock = mockStatic(Arguments.class); + argumentsMock.when(Arguments::createMap).thenAnswer(invocation -> new JavaOnlyMap()); + } + + @After + public void tearDown() { + argumentsMock.close(); + } + + // Regression guard for intercom/intercom#520691: a null Registration arrives here whenever + // Intercom.client().fetchLoggedInUserAttributes() returns null (no logged-in user, app + // re-launched after OS kill, etc.). Before the fix, this crashed the host process with + // NullPointerException on registration.getEmail(). + @Test + public void deconstructRegistration_returnsEmptyMap_whenRegistrationIsNull() { + WritableMap result = IntercomHelpers.deconstructRegistration(null); + + assertNotNull("expected an empty map, not null", result); + JavaOnlyMap map = (JavaOnlyMap) result; + assertFalse("expected no email key when registration is null", map.hasKey("email")); + assertFalse("expected no userId key when registration is null", map.hasKey("userId")); + } + + @Test + public void deconstructRegistration_returnsBothFields_whenRegistrationIsFullyPopulated() { + Registration registration = new Registration() + .withEmail("test@example.com") + .withUserId("user-42"); + + WritableMap result = IntercomHelpers.deconstructRegistration(registration); + JavaOnlyMap map = (JavaOnlyMap) result; + + assertTrue(map.hasKey("email")); + assertEquals("test@example.com", map.getString("email")); + assertTrue(map.hasKey("userId")); + assertEquals("user-42", map.getString("userId")); + } +}