From d72712ad834060f1c2858c8e0402c7aa5c591713 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Tue, 7 Apr 2026 16:17:05 +0100 Subject: [PATCH] fix: surface push registration errors instead of failing silently (#469) When pushIntegrationName is set to an invalid value, the registerDeviceToken API call fails but the error was silently swallowed in the native layer. This change implements the onTokenRegistrationFailed callback on both iOS and Android native modules to emit an event to the React Native layer. A console.error is always logged, and developers can optionally provide an onTokenRegistrationFailed callback in IterableConfig for programmatic error handling. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../reactnative/RNIterableAPIModuleImpl.java | 16 +++++++++++++- ios/RNIterableAPI/ReactIterableAPI.swift | 8 +++++++ src/core/classes/Iterable.ts | 22 +++++++++++++++++++ src/core/classes/IterableConfig.ts | 22 +++++++++++++++++++ src/core/enums/IterableEventName.ts | 2 ++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index caef783e6..94f5a0ab3 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -681,6 +681,19 @@ public void onTokenRegistrationSuccessful(String authToken) { sendEvent(EventName.handleAuthSuccessCalled.name(), null); } + @Override + public void onTokenRegistrationFailed(String reason) { + IterableLogger.e(TAG, "Token registration failed: " + reason); + JSONObject reasonJson = new JSONObject(); + try { + reasonJson.put("reason", reason != null ? reason : "unknown"); + WritableMap eventData = Serialization.convertJsonToMap(reasonJson); + sendEvent(EventName.handleTokenRegistrationFailedCalled.name(), eventData); + } catch (JSONException e) { + IterableLogger.e(TAG, "Failed to send token registration failure event"); + } + } + public void addListener(String eventName) { // Keep: Required for RN built in Event Emitter Calls. } @@ -811,5 +824,6 @@ enum EventName { handleInAppCalled, handleUrlCalled, receivedIterableEmbeddedMessagesChanged, - receivedIterableInboxChanged + receivedIterableInboxChanged, + handleTokenRegistrationFailedCalled } diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index c7b5fc745..52606bbcf 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -35,6 +35,7 @@ import React case handleAuthFailureCalled case handleEmbeddedMessageUpdateCalled case handleEmbeddedMessagingDisabledCalled + case handleTokenRegistrationFailedCalled } @objc public static var supportedEvents: [String] { @@ -818,6 +819,13 @@ extension ReactIterableAPI: IterableAuthDelegate { } public func onTokenRegistrationFailed(_ reason: String?) { + ITBError("Token registration failed: \(reason ?? "unknown reason")") + guard shouldEmit else { + return + } + delegate?.sendEvent( + withName: EventName.handleTokenRegistrationFailedCalled.rawValue, + body: ["reason": reason ?? "unknown"] as [String: Any]) } } diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 983fab49b..c33762bd9 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -956,6 +956,9 @@ export class Iterable { RNEventEmitter.removeAllListeners( IterableEventName.handleEmbeddedMessagingDisabledCalled ); + RNEventEmitter.removeAllListeners( + IterableEventName.handleTokenRegistrationFailedCalled + ); } /** @@ -1109,6 +1112,25 @@ export class Iterable { ); } } + + // Always listen for token registration failures so they are surfaced + // to developers. Without this, push registration errors (e.g. invalid + // pushIntegrationName) are silently swallowed. + RNEventEmitter.addListener( + IterableEventName.handleTokenRegistrationFailedCalled, + (dict: { reason: string }) => { + const reason = dict?.reason ?? 'unknown'; + + // Always log to console.error so developers can see the failure + // even without the callback configured + console.error( + `[Iterable] Push token registration failed: ${reason}. ` + + 'Check that pushIntegrationName in your IterableConfig is valid.' + ); + + Iterable.savedConfig.onTokenRegistrationFailed?.(reason); + } + ); } /** diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 34befbbc8..22d6cf2ca 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -375,6 +375,28 @@ export class IterableConfig { */ onEmbeddedMessagingDisabled?: () => void; + /** + * A callback function that is called when push token registration fails. + * + * This can happen when `pushIntegrationName` is set to an invalid value, + * or when the `/api/users/registerDeviceToken` endpoint returns an error. + * + * Without this callback, push registration errors are silently ignored, + * making it difficult to diagnose push notification setup issues. + * + * @param reason - A string describing why token registration failed. + * + * @example + * ```typescript + * const config = new IterableConfig(); + * config.onTokenRegistrationFailed = (reason) => { + * console.error('Push token registration failed:', reason); + * }; + * Iterable.initialize('', config); + * ``` + */ + onTokenRegistrationFailed?: (reason: string) => void; + /** * Converts the IterableConfig instance to a dictionary object. * diff --git a/src/core/enums/IterableEventName.ts b/src/core/enums/IterableEventName.ts index 6ea79c754..e3d2c90c8 100644 --- a/src/core/enums/IterableEventName.ts +++ b/src/core/enums/IterableEventName.ts @@ -23,4 +23,6 @@ export enum IterableEventName { handleEmbeddedMessageUpdateCalled = 'handleEmbeddedMessageUpdateCalled', /** Event that fires when embedded messaging is disabled */ handleEmbeddedMessagingDisabledCalled = 'handleEmbeddedMessagingDisabledCalled', + /** Event that fires when push token registration fails (e.g., invalid pushIntegrationName) */ + handleTokenRegistrationFailedCalled = 'handleTokenRegistrationFailedCalled', }