From ccf92c44a44ed50b260bc9e0dce10211c10d96ac Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 15:15:29 +0000 Subject: [PATCH 1/2] fix: surface push token registration failures to JS layer (#469) When autoPushRegistration is enabled, device token registration failures (e.g., from invalid pushIntegrationName) were silently swallowed in the native layer. This adds an onTokenRegistrationFailed callback to IterableConfig that developers can use to detect and handle these errors. Changes: - Add onTokenRegistrationFailed optional callback to IterableConfig - Add handleTokenRegistrationFailedCalled event name - iOS: Implement onTokenRegistrationFailed delegate to emit event - Android: Implement onTokenRegistrationFailed to emit event - Wire up event listener in setupEventHandlers https://claude.ai/code/session_017dWRachduo3ATqU37Ef3mh --- .../reactnative/RNIterableAPIModuleImpl.java | 18 +++++++++++- ios/RNIterableAPI/ReactIterableAPI.swift | 12 ++++++++ src/core/classes/Iterable.test.ts | 3 ++ src/core/classes/Iterable.ts | 12 ++++++++ src/core/classes/IterableConfig.ts | 29 +++++++++++++++++++ src/core/enums/IterableEventName.ts | 2 ++ 6 files changed, 75 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..d1300e884 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -681,6 +681,21 @@ public void onTokenRegistrationSuccessful(String authToken) { sendEvent(EventName.handleAuthSuccessCalled.name(), null); } + @Override + public void onTokenRegistrationFailed(String reason) { + IterableLogger.e(TAG, "Token registration failed: " + reason); + JSONObject messageJson = new JSONObject(); + try { + if (reason != null) { + messageJson.put("reason", reason); + } + WritableMap eventData = Serialization.convertJsonToMap(messageJson); + 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 +826,6 @@ enum EventName { handleInAppCalled, handleUrlCalled, receivedIterableEmbeddedMessagesChanged, - receivedIterableInboxChanged + receivedIterableInboxChanged, + handleTokenRegistrationFailedCalled } diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index c7b5fc745..c09424d35 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,17 @@ extension ReactIterableAPI: IterableAuthDelegate { } public func onTokenRegistrationFailed(_ reason: String?) { + ITBInfo() + guard shouldEmit else { + return + } + var body: [String: Any] = [:] + if let reason = reason { + body["reason"] = reason + } + delegate?.sendEvent( + withName: EventName.handleTokenRegistrationFailedCalled.rawValue, + body: body) } } diff --git a/src/core/classes/Iterable.test.ts b/src/core/classes/Iterable.test.ts index 20ad805d7..64938f0af 100644 --- a/src/core/classes/Iterable.test.ts +++ b/src/core/classes/Iterable.test.ts @@ -47,6 +47,9 @@ describe('Iterable', () => { nativeEmitter.removeAllListeners( IterableEventName.handleEmbeddedMessagingDisabledCalled ); + nativeEmitter.removeAllListeners( + IterableEventName.handleTokenRegistrationFailedCalled + ); // Clear any pending timers jest.clearAllTimers(); diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 983fab49b..82505a398 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 + ); } /** @@ -1090,6 +1093,15 @@ export class Iterable { ); } + if (Iterable.savedConfig.onTokenRegistrationFailed) { + RNEventEmitter.addListener( + IterableEventName.handleTokenRegistrationFailedCalled, + (dict: { reason?: string } | null) => { + Iterable.savedConfig.onTokenRegistrationFailed?.(dict?.reason); + } + ); + } + if (Iterable.savedConfig.enableEmbeddedMessaging) { if (Iterable.savedConfig.onEmbeddedMessageUpdate) { RNEventEmitter.addListener( diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 34befbbc8..e1653de99 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -357,6 +357,32 @@ export class IterableConfig { */ onEmbeddedMessageUpdate?: () => void; + /** + * A callback function that is called when device token registration fails + * during automatic push registration. + * + * When `autoPushRegistration` is enabled (the default), calling `setEmail` + * or `setUserId` triggers an automatic device token registration with Iterable's + * servers. If this registration fails (e.g., due to an invalid `pushIntegrationName`), + * this callback will be invoked with the failure reason. + * + * Without this callback, push registration failures are silent and can only + * be detected by inspecting network traffic. + * + * @param reason - A string describing why the token registration failed, + * or undefined if the reason is unknown. + * + * @example + * ```typescript + * const config = new IterableConfig(); + * config.onTokenRegistrationFailed = (reason) => { + * console.error('Push token registration failed:', reason); + * }; + * Iterable.initialize('', config); + * ``` + */ + onTokenRegistrationFailed?: (reason?: string) => void; + /** * A callback function that is called when embedded messaging is disabled. * @@ -428,6 +454,9 @@ export class IterableConfig { // eslint-disable-next-line eqeqeq onEmbeddedMessagingDisabledPresent: this.onEmbeddedMessagingDisabled != undefined, + // eslint-disable-next-line eqeqeq + onTokenRegistrationFailedPresent: + this.onTokenRegistrationFailed != undefined, /** The log level for the SDK. */ logLevel: this.logLevel, expiringAuthTokenRefreshPeriod: this.expiringAuthTokenRefreshPeriod, diff --git a/src/core/enums/IterableEventName.ts b/src/core/enums/IterableEventName.ts index 6ea79c754..6617ea984 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 device token registration fails */ + handleTokenRegistrationFailedCalled = 'handleTokenRegistrationFailedCalled', } From ed05bcac2ff4c38e1c395c9d4fdee5731b9c632c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 15:15:49 +0000 Subject: [PATCH 2/2] fix: use strict equality for onTokenRegistrationFailed check https://claude.ai/code/session_017dWRachduo3ATqU37Ef3mh --- src/core/classes/IterableConfig.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index e1653de99..05de938ef 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -454,9 +454,8 @@ export class IterableConfig { // eslint-disable-next-line eqeqeq onEmbeddedMessagingDisabledPresent: this.onEmbeddedMessagingDisabled != undefined, - // eslint-disable-next-line eqeqeq onTokenRegistrationFailedPresent: - this.onTokenRegistrationFailed != undefined, + this.onTokenRegistrationFailed !== undefined, /** The log level for the SDK. */ logLevel: this.logLevel, expiringAuthTokenRefreshPeriod: this.expiringAuthTokenRefreshPeriod,