From 4c4ccaf387c5f19115ded4478f24e8e21d33a009 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Tue, 7 Apr 2026 16:09:08 +0100 Subject: [PATCH] Surface push registration errors instead of failing silently When pushIntegrationName is invalid, the registerDeviceToken API call previously failed silently. This change implements the onTokenRegistrationFailed callback in both iOS and Android native modules to log errors and emit events that JS code can listen to. Fixes #469 Co-Authored-By: Claude Opus 4.6 --- .../reactnative/RNIterableAPIModuleImpl.java | 17 ++++++++++++++++- ios/RNIterableAPI/ReactIterableAPI.swift | 9 +++++++++ src/core/classes/Iterable.ts | 16 ++++++++++++++++ src/core/classes/IterableConfig.ts | 19 +++++++++++++++++++ src/core/enums/IterableEventName.ts | 2 ++ 5 files changed, 62 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..ae926b720 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -681,6 +681,20 @@ public void onTokenRegistrationSuccessful(String authToken) { sendEvent(EventName.handleAuthSuccessCalled.name(), null); } + @Override + public void onTokenRegistrationFailed(String reason) { + String failureReason = reason != null ? reason : "unknown reason"; + IterableLogger.e(TAG, "Push token registration failed: " + failureReason); + JSONObject eventDataJson = new JSONObject(); + try { + eventDataJson.put("reason", failureReason); + WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); + 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 +825,6 @@ enum EventName { handleInAppCalled, handleUrlCalled, receivedIterableEmbeddedMessagesChanged, - receivedIterableInboxChanged + receivedIterableInboxChanged, + handleTokenRegistrationFailedCalled } diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index c7b5fc745..5cd091b74 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,14 @@ extension ReactIterableAPI: IterableAuthDelegate { } public func onTokenRegistrationFailed(_ reason: String?) { + let failureReason = reason ?? "unknown reason" + ITBError("Push token registration failed: \(failureReason)") + guard shouldEmit else { + return + } + delegate?.sendEvent( + withName: EventName.handleTokenRegistrationFailedCalled.rawValue, + body: ["reason": failureReason] as [String: Any]) } } diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 983fab49b..dee25007e 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,19 @@ export class Iterable { ); } } + + // Always listen for token registration failures so they are surfaced + RNEventEmitter.addListener( + IterableEventName.handleTokenRegistrationFailedCalled, + (dict: { reason: string }) => { + IterableLogger?.log( + 'Push token registration failed: ' + (dict?.reason ?? 'unknown reason') + ); + Iterable.savedConfig.onTokenRegistrationFailed?.( + dict?.reason ?? 'unknown reason' + ); + } + ); } /** diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 34befbbc8..b8f020274 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -375,6 +375,25 @@ export class IterableConfig { */ onEmbeddedMessagingDisabled?: () => void; + /** + * A callback function that is called when device token registration fails. + * + * This can happen when the pushIntegrationName is invalid or when the + * registerDeviceToken API call fails for any reason. + * + * @param reason - A string describing why the registration failed. + * + * @example + * ```typescript + * const config = new IterableConfig(); + * config.onTokenRegistrationFailed = (reason) => { + * console.error('Push 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..72618a21c 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 */ + handleTokenRegistrationFailedCalled = 'handleTokenRegistrationFailedCalled', }