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..05de938ef 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,8 @@ export class IterableConfig { // eslint-disable-next-line eqeqeq onEmbeddedMessagingDisabledPresent: this.onEmbeddedMessagingDisabled != undefined, + 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', }