From 45a905a0adadca33dd19dd039503b41349073e7a Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 21 Aug 2025 12:39:30 -0700 Subject: [PATCH 1/7] fix: building --- android/build.gradle | 170 ++- android/gradle.properties | 12 +- android/src/main/AndroidManifestNew.xml | 2 - .../reactnative/RNIterableAPIModule.java | 1186 +++++++++-------- example/android/gradle.properties | 4 +- .../project.pbxproj | 74 +- lefthook.yml | 28 +- 7 files changed, 803 insertions(+), 673 deletions(-) delete mode 100644 android/src/main/AndroidManifestNew.xml diff --git a/android/build.gradle b/android/build.gradle index fd26bc186..2c34f57f1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,109 @@ +// buildscript { +// // Buildscript is evaluated before everything else so we can't use getExtOrDefault +// def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["RNIterable_kotlinVersion"] + +// repositories { +// google() +// mavenCentral() +// } + +// dependencies { +// classpath "com.android.tools.build:gradle:7.2.1" +// // noinspection DifferentKotlinGradleVersion +// classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" +// } +// } + +// def reactNativeArchitectures() { +// def value = rootProject.getProperties().get("reactNativeArchitectures") +// return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +// } + +// def isNewArchitectureEnabled() { +// return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +// } + +// apply plugin: "com.android.library" +// apply plugin: "kotlin-android" + +// if (isNewArchitectureEnabled()) { +// apply plugin: "com.facebook.react" +// } + +// def getExtOrDefault(name) { +// return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["RNIterable_" + name] +// } + +// def getExtOrIntegerDefault(name) { +// return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["RNIterable_" + name]).toInteger() +// } + +// def supportsNamespace() { +// def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') +// def major = parsed[0].toInteger() +// def minor = parsed[1].toInteger() + +// // Namespace support was added in 7.3.0 +// return (major == 7 && minor >= 3) || major >= 8 +// } + +// android { +// if (supportsNamespace()) { +// namespace "com.iterable.reactnative" + +// sourceSets { +// main { +// manifest.srcFile "src/main/AndroidManifestNew.xml" +// } +// } +// } + +// compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + +// defaultConfig { +// minSdkVersion getExtOrIntegerDefault("minSdkVersion") +// targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + +// } + +// buildTypes { +// release { +// minifyEnabled false +// } +// } + +// lintOptions { +// disable "GradleCompatible" +// } + +// compileOptions { +// sourceCompatibility JavaVersion.VERSION_1_8 +// targetCompatibility JavaVersion.VERSION_1_8 +// } +// } + +// repositories { +// mavenCentral() +// google() +// } + +// def kotlin_version = getExtOrDefault("kotlinVersion") + +// dependencies { +// // For < 0.71, this will be from the local maven repo +// // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin +// //noinspection GradleDynamicVersion +// implementation "com.facebook.react:react-native:+" +// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" +// api "com.iterable:iterableapi:3.5.2" +// // api project(":iterableapi") // links to local android SDK repo rather than by release +// } + + buildscript { - // Buildscript is evaluated before everything else so we can't use getExtOrDefault - def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["RNIterable_kotlinVersion"] + ext.getExtOrDefault = {name -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['RNIterable_' + name] + } repositories { google() @@ -8,62 +111,34 @@ buildscript { } dependencies { - classpath "com.android.tools.build:gradle:7.2.1" + classpath "com.android.tools.build:gradle:8.7.2" // noinspection DifferentKotlinGradleVersion - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}" } } -def reactNativeArchitectures() { - def value = rootProject.getProperties().get("reactNativeArchitectures") - return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] -} - -def isNewArchitectureEnabled() { - return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" -} apply plugin: "com.android.library" apply plugin: "kotlin-android" -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} - -def getExtOrDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["RNIterable_" + name] -} +apply plugin: "com.facebook.react" def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["RNIterable_" + name]).toInteger() } -def supportsNamespace() { - def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') - def major = parsed[0].toInteger() - def minor = parsed[1].toInteger() - - // Namespace support was added in 7.3.0 - return (major == 7 && minor >= 3) || major >= 8 -} - android { - if (supportsNamespace()) { - namespace "com.iterable.reactnative" - - sourceSets { - main { - manifest.srcFile "src/main/AndroidManifestNew.xml" - } - } - } + namespace "com.iterable.reactnative" compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") defaultConfig { minSdkVersion getExtOrIntegerDefault("minSdkVersion") targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + } + buildFeatures { + buildConfig true } buildTypes { @@ -80,6 +155,15 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + sourceSets { + main { + java.srcDirs += [ + "generated/java", + "generated/jni" + ] + } + } } repositories { @@ -90,12 +174,14 @@ repositories { def kotlin_version = getExtOrDefault("kotlinVersion") dependencies { - // For < 0.71, this will be from the local maven repo - // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin - //noinspection GradleDynamicVersion - implementation "com.facebook.react:react-native:+" + implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" api "com.iterable:iterableapi:3.5.2" - // api project(":iterableapi") // links to local android SDK repo rather than by release +} + +react { + jsRootDir = file("../src/api/") + libraryName = "RNIterableAPISpec" + codegenJavaPackageName = "com.iterable.reactnative" } diff --git a/android/gradle.properties b/android/gradle.properties index a46c61ab2..60ed9d52c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,7 +1,7 @@ -RNIterable_kotlinVersion=1.7.0 -RNIterable_minSdkVersion=21 -RNIterable_targetSdkVersion=31 -RNIterable_compileSdkVersion=31 -RNIterable_ndkversion=21.4.7075529 +RNIterable_kotlinVersion=2.0.21 +RNIterable_minSdkVersion=24 +RNIterable_targetSdkVersion=34 +RNIterable_compileSdkVersion=35 +RNIterable_ndkversion=27.1.12297006 android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true diff --git a/android/src/main/AndroidManifestNew.xml b/android/src/main/AndroidManifestNew.xml deleted file mode 100644 index a2f47b605..000000000 --- a/android/src/main/AndroidManifestNew.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java index 4d1766472..d1ac0867d 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java @@ -19,6 +19,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.RCTNativeAppEventEmitter; import com.iterable.iterableapi.InboxSessionManager; @@ -39,6 +40,7 @@ import com.iterable.iterableapi.IterableLogger; import com.iterable.iterableapi.IterableUrlHandler; import com.iterable.iterableapi.RNIterableInternal; +import com.iterable.reactnative.Serialization; import org.json.JSONArray; import org.json.JSONException; @@ -48,584 +50,628 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -public class RNIterableAPIModule extends ReactContextBaseJavaModule implements IterableUrlHandler, IterableCustomActionHandler, IterableInAppHandler, IterableAuthHandler, IterableInAppManager.Listener { - private final ReactApplicationContext reactContext; - private static String TAG = "RNIterableAPIModule"; +@ReactModule(name = RNIterableAPIModule.NAME) +public class RNIterableAPIModule extends NativeRNIterableAPISpec implements IterableUrlHandler, IterableCustomActionHandler, IterableInAppHandler, IterableAuthHandler, IterableInAppManager.Listener { + private final ReactApplicationContext reactContext; + private static String TAG = "RNIterableAPIModule"; - private InAppResponse inAppResponse = InAppResponse.SHOW; + private IterableInAppHandler.InAppResponse inAppResponse = IterableInAppHandler.InAppResponse.SHOW; - //A CountDownLatch. This helps decide whether to handle the in-app in Default way by waiting for JS to respond in runtime. - private CountDownLatch jsCallBackLatch; + //A CountDownLatch. This helps decide whether to handle the in-app in Default way by waiting for JS to respond in runtime. + private CountDownLatch jsCallBackLatch; - private CountDownLatch authHandlerCallbackLatch; - private String passedAuthToken = null; + private CountDownLatch authHandlerCallbackLatch; + private String passedAuthToken = null; - private final InboxSessionManager sessionManager = new InboxSessionManager(); + private final InboxSessionManager sessionManager = new InboxSessionManager(); + public RNIterableAPIModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } - public RNIterableAPIModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } + @Override + public String getName() { + return NAME; + } - // --------------------------------------------------------------------------------------- - // region IterableSDK calls + public static final String NAME = "ReactNativeSdk"; - @Override - public String getName() { - return "RNIterableAPI"; - } - - @ReactMethod - public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { - IterableLogger.d(TAG, "initializeWithApiKey: " + apiKey); - IterableConfig.Builder configBuilder = Serialization.getConfigFromReadableMap(configReadableMap); - - if (configReadableMap.hasKey("urlHandlerPresent") && configReadableMap.getBoolean("urlHandlerPresent") == true) { - configBuilder.setUrlHandler(this); - } - - if (configReadableMap.hasKey("customActionHandlerPresent") && configReadableMap.getBoolean("customActionHandlerPresent") == true) { - configBuilder.setCustomActionHandler(this); - } - - if (configReadableMap.hasKey("inAppHandlerPresent") && configReadableMap.getBoolean("inAppHandlerPresent") == true) { - configBuilder.setInAppHandler(this); - } - - if (configReadableMap.hasKey("authHandlerPresent") && configReadableMap.getBoolean("authHandlerPresent") == true) { - configBuilder.setAuthHandler(this); - } - - IterableApi.initialize(reactContext, apiKey, configBuilder.build()); - IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); - - IterableApi.getInstance().getInAppManager().addListener(this); - - // MOB-10421: Figure out what the error cases are and handle them appropriately - // This is just here to match the TS types and let the JS thread know when we are done initializing - promise.resolve(true); - } - - @ReactMethod - public void setEmail(@Nullable String email, @Nullable String authToken) { - IterableLogger.d(TAG, "setEmail: " + email + " authToken: " + authToken); - - IterableApi.getInstance().setEmail(email, authToken); - } - - @ReactMethod - public void updateEmail(String email, @Nullable String authToken) { - IterableLogger.d(TAG, "updateEmail: " + email + " authToken: " + authToken); - - IterableApi.getInstance().updateEmail(email, authToken); - } - - @ReactMethod - public void getEmail(Promise promise) { - promise.resolve(RNIterableInternal.getEmail()); - } - - @ReactMethod - public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { - // TODO: Implement some actually useful functionality - callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument); - } - - @ReactMethod - public void setUserId(@Nullable String userId, @Nullable String authToken) { - IterableLogger.d(TAG, "setUserId: " + userId + " authToken: " + authToken); - - IterableApi.getInstance().setUserId(userId, authToken); - } - - @ReactMethod - public void updateUser(ReadableMap dataFields, Boolean mergeNestedObjects) { - IterableLogger.v(TAG, "updateUser"); - IterableApi.getInstance().updateUser(optSerializedDataFields(dataFields), mergeNestedObjects); - } - - @ReactMethod - public void getUserId(Promise promise) { - promise.resolve(RNIterableInternal.getUserId()); - } - - @ReactMethod - public void trackEvent(String name, ReadableMap dataFields) { - IterableLogger.v(TAG, "trackEvent"); - IterableApi.getInstance().track(name, optSerializedDataFields(dataFields)); - } - - @ReactMethod - public void updateCart(ReadableArray items) { - IterableLogger.v(TAG, "updateCart"); - IterableApi.getInstance().updateCart(Serialization.commerceItemsFromReadableArray(items)); - } - - @ReactMethod - public void trackPurchase(Double total, ReadableArray items, ReadableMap dataFields) { - IterableLogger.v(TAG, "trackPurchase"); - IterableApi.getInstance().trackPurchase(total, Serialization.commerceItemsFromReadableArray(items), optSerializedDataFields(dataFields)); - } - - @ReactMethod - public void trackPushOpenWithCampaignId(Integer campaignId, Integer templateId, String messageId, Boolean appAlreadyRunning, ReadableMap dataFields) { - RNIterableInternal.trackPushOpenWithCampaignId(campaignId, templateId, messageId, optSerializedDataFields(dataFields)); - } - - @ReactMethod - public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, Integer campaignId, Integer templateId) { - IterableLogger.v(TAG, "updateSubscriptions"); - Integer finalCampaignId = null, finalTemplateId = null; - if (campaignId > 0) { - finalCampaignId = campaignId; - } - if (templateId > 0) { - finalTemplateId = templateId; - } - IterableApi.getInstance().updateSubscriptions(readableArrayToIntegerArray(emailListIds), - readableArrayToIntegerArray(unsubscribedChannelIds), - readableArrayToIntegerArray(unsubscribedMessageTypeIds), - readableArrayToIntegerArray(subscribedMessageTypeIds), - finalCampaignId, - finalTemplateId - ); - } - - @ReactMethod - public void showMessage(String messageId, boolean consume, final Promise promise) { - if (messageId == null || messageId == "") { - promise.reject("", "messageId is null or empty"); - return; - } - IterableApi.getInstance().getInAppManager().showMessage(RNIterableInternal.getMessageById(messageId), consume, new IterableHelper.IterableUrlCallback() { - @Override - public void execute(@Nullable Uri url) { - promise.resolve(url.toString()); - } - }); - } - - @ReactMethod - public void setReadForMessage(String messageId, boolean read) { - IterableLogger.v(TAG, "setReadForMessage"); - IterableApi.getInstance().getInAppManager().setRead(RNIterableInternal.getMessageById(messageId), read); - } - - @ReactMethod - public void removeMessage(String messageId, Integer location, Integer deleteSource) { - IterableLogger.v(TAG, "removeMessage"); - IterableApi.getInstance().getInAppManager().removeMessage(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger(deleteSource), Serialization.getIterableInAppLocationFromInteger(location)); - } - - @ReactMethod - public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { - IterableLogger.printInfo(); - IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); - - if (message == null) { - promise.reject("", "Could not find message with id: " + messageId); - return; - } - - JSONObject messageContent = Serialization.messageContentToJsonObject(message.getContent()); - - if (messageContent == null) { - promise.reject("", "messageContent is null for message id: " + messageId); - return; - } - - try { - promise.resolve(Serialization.convertJsonToMap(messageContent)); - } catch (JSONException e) { - promise.reject("", "Failed to convert JSONObject to ReadableMap"); - } - } - - @ReactMethod - public void getAttributionInfo(Promise promise) { - IterableLogger.printInfo(); - IterableAttributionInfo attributionInfo = IterableApi.getInstance().getAttributionInfo(); - if (attributionInfo != null) { - try { - promise.resolve(Serialization.convertJsonToMap(attributionInfo.toJSONObject())); - } catch (JSONException e) { - IterableLogger.e(TAG, "Failed converting attribution info to JSONObject"); - promise.reject("", "Failed to convert AttributionInfo to ReadableMap"); - } - } else { - promise.resolve(null); - } - } - - @ReactMethod - public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { - IterableLogger.printInfo(); - try { - JSONObject attributionInfoJson = Serialization.convertMapToJson(attributionInfoReadableMap); - IterableAttributionInfo attributionInfo = IterableAttributionInfo.fromJSONObject(attributionInfoJson); - RNIterableInternal.setAttributionInfo(attributionInfo); - } catch (JSONException e) { - IterableLogger.e(TAG, "Failed converting ReadableMap to JSON"); - } - } - - @ReactMethod - public void getLastPushPayload(Promise promise) { - Bundle payloadData = IterableApi.getInstance().getPayloadData(); - if (payloadData != null) { - promise.resolve(Arguments.fromBundle(IterableApi.getInstance().getPayloadData())); - } else { - IterableLogger.d(TAG, "No payload data found"); - promise.resolve(null); - } - } - - @ReactMethod - public void disableDeviceForCurrentUser() { - IterableLogger.v(TAG, "disableDevice"); - IterableApi.getInstance().disablePush(); - } - - @ReactMethod - public void handleAppLink(String uri, Promise promise) { - IterableLogger.printInfo(); - promise.resolve(IterableApi.getInstance().handleAppLink(uri)); - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Track APIs - @ReactMethod - public void trackInAppOpen(String messageId, @Nullable Integer location) { - IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); - - if (message == null) { - IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); - return; - } - - IterableApi.getInstance().trackInAppOpen(message, Serialization.getIterableInAppLocationFromInteger(location)); - } - - @ReactMethod - public void trackInAppClick(String messageId, @Nullable Integer location, String clickedUrl) { - IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); - IterableInAppLocation inAppOpenLocation = Serialization.getIterableInAppLocationFromInteger(location); - - if (message == null) { - IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); - return; - } - - if (clickedUrl == null) { - IterableLogger.d(TAG, "clickedUrl is null"); - return; - } - - if (inAppOpenLocation == null) { - IterableLogger.d(TAG, "in-app open location is null"); - return; - } - - IterableApi.getInstance().trackInAppClick(message, clickedUrl, inAppOpenLocation); - } - - @ReactMethod - public void trackInAppClose(String messageId, Integer location, Integer source, @Nullable String clickedUrl) { - IterableInAppMessage inAppMessage = RNIterableInternal.getMessageById(messageId); - IterableInAppLocation inAppCloseLocation = Serialization.getIterableInAppLocationFromInteger(location); - IterableInAppCloseAction closeAction = Serialization.getIterableInAppCloseSourceFromInteger(source); - - if (inAppMessage == null) { - IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); - return; - } - - if (inAppCloseLocation == null) { - IterableLogger.d(TAG, "in-app close location is null"); - return; - } - - if (closeAction == null) { - IterableLogger.d(TAG, "in-app close action is null"); - return; - } - - IterableApi.getInstance().trackInAppClose(inAppMessage, clickedUrl, closeAction, inAppCloseLocation); - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region In App APIs - - @ReactMethod - public void inAppConsume(String messageId, Integer location, Integer source) { - if (messageId == null) { - return; - } - IterableApi.getInstance().inAppConsume(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger(source), Serialization.getIterableInAppLocationFromInteger(location)); - } - - @ReactMethod - public void getInAppMessages(Promise promise) { - IterableLogger.d(TAG, "getMessages"); - try { - JSONArray inAppMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getMessages()); - promise.resolve(Serialization.convertJsonToArray(inAppMessageJsonArray)); - } catch (JSONException e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); - } - } - - @ReactMethod - public void getInboxMessages(Promise promise) { - IterableLogger.d(TAG, "getInboxMessages"); - try { - JSONArray inboxMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getInboxMessages()); - promise.resolve(Serialization.convertJsonToArray(inboxMessageJsonArray)); - } catch (JSONException e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); - } - } - - @ReactMethod - public void setInAppShowResponse(Integer number) { - IterableLogger.printInfo(); - inAppResponse = Serialization.getInAppResponse(number); - if (jsCallBackLatch != null) { - jsCallBackLatch.countDown(); - } - } - - @ReactMethod - public void setAutoDisplayPaused(final boolean paused) { - IterableLogger.printInfo(); - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - IterableApi.getInstance().getInAppManager().setAutoDisplayPaused(paused); - } - }); - } - - @ReactMethod - public void wakeApp() { - Intent launcherIntent = getMainActivityIntent(reactContext); - launcherIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - if (launcherIntent.resolveActivity(reactContext.getPackageManager()) != null) { - reactContext.startActivity(launcherIntent); - } - } - - public Intent getMainActivityIntent(Context context) { - Context appContext = context.getApplicationContext(); - PackageManager packageManager = appContext.getPackageManager(); - Intent intent = packageManager.getLaunchIntentForPackage(appContext.getPackageName()); - if (intent == null) { - intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setPackage(appContext.getPackageName()); - } - return intent; - } - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Inbox In-App Session Tracking APIs - - @ReactMethod - public void startSession(ReadableArray visibleRows) { - List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); - - sessionManager.startSession(serializedRows); - } - - @ReactMethod - public void endSession() { - sessionManager.endSession(); - } - - @ReactMethod - public void updateVisibleRows(ReadableArray visibleRows) { - List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); - - sessionManager.updateVisibleRows(serializedRows); - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Private Serialization Functions - - private static Integer[] readableArrayToIntegerArray(ReadableArray array) { - if (array == null) { - return null; - } - Integer[] integers = new Integer[array.size()]; - for (int i = 0; i < array.size(); i++) { - integers[i] = array.getInt(i); - } - return integers; - } - - @Nullable - private static JSONObject optSerializedDataFields(ReadableMap dataFields) { - JSONObject dataFieldsJson = null; - - if (dataFields != null) { - try { - dataFieldsJson = Serialization.convertMapToJson(dataFields); - } catch (JSONException e) { - IterableLogger.d(TAG, "Failed to convert dataFields to JSON"); - } - } - - return dataFieldsJson; - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region IterableSDK callbacks - - @Override - public boolean handleIterableCustomAction(@NonNull IterableAction action, @NonNull IterableActionContext actionContext) { - IterableLogger.printInfo(); - JSONObject actionJson = Serialization.actionToJson(action); - JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); - JSONObject eventDataJson = new JSONObject(); - try { - eventDataJson.put("action", actionJson); - eventDataJson.put("context", actionContextJson); - WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); - sendEvent(EventName.handleCustomActionCalled.name(), eventData); - } catch (JSONException e) { - IterableLogger.e(TAG, "Failed handling custom action"); - } - // The Android SDK will not bring the app into focus is this is `true`. It still respects the `openApp` bool flag. - return false; - } - - @NonNull - @Override - public InAppResponse onNewInApp(@NonNull IterableInAppMessage message) { - IterableLogger.printInfo(); - - JSONObject messageJson = RNIterableInternal.getInAppMessageJson(message); - - try { - WritableMap eventData = Serialization.convertJsonToMap(messageJson); - jsCallBackLatch = new CountDownLatch(1); - sendEvent(EventName.handleInAppCalled.name(), eventData); - jsCallBackLatch.await(2, TimeUnit.SECONDS); - jsCallBackLatch = null; - return inAppResponse; - } catch (InterruptedException | JSONException e) { - IterableLogger.e(TAG, "new in-app module failed"); - return InAppResponse.SHOW; - } - } - - @Override - public boolean handleIterableURL(@NonNull Uri uri, @NonNull IterableActionContext actionContext) { - IterableLogger.printInfo(); - - JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); - JSONObject eventDataJson = new JSONObject(); - - try { - eventDataJson.put("url", uri.toString()); - eventDataJson.put("context", actionContextJson); - WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); - sendEvent(EventName.handleUrlCalled.name(), eventData); - } catch (JSONException e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - } - return true; - } - - @Override - public String onAuthTokenRequested() { - IterableLogger.printInfo(); - - try { - authHandlerCallbackLatch = new CountDownLatch(1); - sendEvent(EventName.handleAuthCalled.name(), null); - authHandlerCallbackLatch.await(30, TimeUnit.SECONDS); - authHandlerCallbackLatch = null; - return passedAuthToken; - } catch (InterruptedException e) { - IterableLogger.e(TAG, "auth handler module failed"); - return null; - } - } - - @Override - public void onTokenRegistrationSuccessful(String authToken) { - IterableLogger.v(TAG, "authToken successfully set"); - // MOB-10422: Pass successhandler to event listener - sendEvent(EventName.handleAuthSuccessCalled.name(), null); - } - - @Override - public void onTokenRegistrationFailed(Throwable object) { - IterableLogger.v(TAG, "Failed to set authToken"); - sendEvent(EventName.handleAuthFailureCalled.name(), null); - } - - @ReactMethod - public void addListener(String eventName) { - // Keep: Required for RN built in Event Emitter Calls. - } - - @ReactMethod - public void removeListeners(Integer count) { - // Keep: Required for RN built in Event Emitter Calls. - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Misc Bridge Functions - - @ReactMethod - public void passAlongAuthToken(String authToken) { - passedAuthToken = authToken; - - if (authHandlerCallbackLatch != null) { - authHandlerCallbackLatch.countDown(); - } - } - - public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, eventData); - } - - @Override - public void onInboxUpdated() { - sendEvent(EventName.receivedIterableInboxChanged.name(), null); - } - - // --------------------------------------------------------------------------------------- - // endregion + @ReactMethod + public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { + IterableLogger.d(TAG, "initializeWithApiKey: " + apiKey); + IterableConfig.Builder configBuilder = Serialization.getConfigFromReadableMap(configReadableMap); + + if (configReadableMap.hasKey("urlHandlerPresent") && configReadableMap.getBoolean("urlHandlerPresent") == true) { + configBuilder.setUrlHandler(this); + } + + if (configReadableMap.hasKey("customActionHandlerPresent") && configReadableMap.getBoolean("customActionHandlerPresent") == true) { + configBuilder.setCustomActionHandler(this); + } + + if (configReadableMap.hasKey("inAppHandlerPresent") && configReadableMap.getBoolean("inAppHandlerPresent") == true) { + configBuilder.setInAppHandler(this); + } + + if (configReadableMap.hasKey("authHandlerPresent") && configReadableMap.getBoolean("authHandlerPresent") == true) { + configBuilder.setAuthHandler(this); + } + + IterableApi.initialize(reactContext, apiKey, configBuilder.build()); + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); + + IterableApi.getInstance().getInAppManager().addListener(this); + + // MOB-10421: Figure out what the error cases are and handle them appropriately + // This is just here to match the TS types and let the JS thread know when we are done initializing + promise.resolve(true); + } + + @ReactMethod + public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, String apiEndPointOverride, String version, Promise promise) { + IterableLogger.d(TAG, "initialize2WithApiKey: " + apiKey); + IterableConfig.Builder configBuilder = Serialization.getConfigFromReadableMap(configReadableMap); + + if (configReadableMap.hasKey("urlHandlerPresent") && configReadableMap.getBoolean("urlHandlerPresent") == true) { + configBuilder.setUrlHandler(this); + } + + if (configReadableMap.hasKey("customActionHandlerPresent") && configReadableMap.getBoolean("customActionHandlerPresent") == true) { + configBuilder.setCustomActionHandler(this); + } + + if (configReadableMap.hasKey("inAppHandlerPresent") && configReadableMap.getBoolean("inAppHandlerPresent") == true) { + configBuilder.setInAppHandler(this); + } + + if (configReadableMap.hasKey("authHandlerPresent") && configReadableMap.getBoolean("authHandlerPresent") == true) { + configBuilder.setAuthHandler(this); + } + + // Set the API endpoint override if provided + // if (apiEndPointOverride != null && !apiEndPointOverride.isEmpty()) { + // configBuilder.setApiEndpoint(apiEndPointOverride); + // } + + IterableApi.initialize(reactContext, apiKey, configBuilder.build()); + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); + + IterableApi.getInstance().getInAppManager().addListener(this); + + // MOB-10421: Figure out what the error cases are and handle them appropriately + // This is just here to match the TS types and let the JS thread know when we are done initializing + promise.resolve(true); + } + + @ReactMethod + public void setEmail(@Nullable String email, @Nullable String authToken) { + IterableLogger.d(TAG, "setEmail: " + email + " authToken: " + authToken); + + IterableApi.getInstance().setEmail(email, authToken); + } + + @ReactMethod + public void updateEmail(String email, @Nullable String authToken) { + IterableLogger.d(TAG, "updateEmail: " + email + " authToken: " + authToken); + + IterableApi.getInstance().updateEmail(email, authToken); + } + + @ReactMethod + public void getEmail(Promise promise) { + promise.resolve(RNIterableInternal.getEmail()); + } + + @ReactMethod + public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { + // TODO: Implement some actually useful functionality + callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument); + } + + @ReactMethod + public void setUserId(@Nullable String userId, @Nullable String authToken) { + IterableLogger.d(TAG, "setUserId: " + userId + " authToken: " + authToken); + + IterableApi.getInstance().setUserId(userId, authToken); + } + + @ReactMethod + public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { + IterableLogger.v(TAG, "updateUser"); + IterableApi.getInstance().updateUser(optSerializedDataFields(dataFields), mergeNestedObjects); + } + + @ReactMethod + public void getUserId(Promise promise) { + promise.resolve(RNIterableInternal.getUserId()); + } + + @ReactMethod + public void trackEvent(String name, ReadableMap dataFields) { + IterableLogger.v(TAG, "trackEvent"); + IterableApi.getInstance().track(name, optSerializedDataFields(dataFields)); + } + + @ReactMethod + public void updateCart(ReadableArray items) { + IterableLogger.v(TAG, "updateCart"); + IterableApi.getInstance().updateCart(Serialization.commerceItemsFromReadableArray(items)); + } + + @ReactMethod + public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { + IterableLogger.v(TAG, "trackPurchase"); + IterableApi.getInstance().trackPurchase(total, Serialization.commerceItemsFromReadableArray(items), optSerializedDataFields(dataFields)); + } + + @ReactMethod + public void trackPushOpenWithCampaignId(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { + RNIterableInternal.trackPushOpenWithCampaignId((int) campaignId, templateId != null ? templateId.intValue() : null, messageId, optSerializedDataFields(dataFields)); + } + + @ReactMethod + public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, double campaignId, double templateId) { + IterableLogger.v(TAG, "updateSubscriptions"); + Integer finalCampaignId = null, finalTemplateId = null; + if (campaignId > 0) { + finalCampaignId = (int) campaignId; + } + if (templateId > 0) { + finalTemplateId = (int) templateId; + } + IterableApi.getInstance().updateSubscriptions(readableArrayToIntegerArray(emailListIds), + readableArrayToIntegerArray(unsubscribedChannelIds), + readableArrayToIntegerArray(unsubscribedMessageTypeIds), + readableArrayToIntegerArray(subscribedMessageTypeIds), + finalCampaignId, + finalTemplateId + ); + } + + @ReactMethod + public void showMessage(String messageId, boolean consume, final Promise promise) { + if (messageId == null || messageId == "") { + promise.reject("", "messageId is null or empty"); + return; + } + IterableApi.getInstance().getInAppManager().showMessage(RNIterableInternal.getMessageById(messageId), consume, new IterableHelper.IterableUrlCallback() { + @Override + public void execute(@Nullable Uri url) { + promise.resolve(url.toString()); + } + }); + } + + @ReactMethod + public void setReadForMessage(String messageId, boolean read) { + IterableLogger.v(TAG, "setReadForMessage"); + IterableApi.getInstance().getInAppManager().setRead(RNIterableInternal.getMessageById(messageId), read); + } + + @ReactMethod + public void removeMessage(String messageId, double location, double deleteSource) { + IterableLogger.v(TAG, "removeMessage"); + IterableApi.getInstance().getInAppManager().removeMessage(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger((int) deleteSource), Serialization.getIterableInAppLocationFromInteger((int) location)); + } + + @ReactMethod + public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { + IterableLogger.printInfo(); + IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); + + if (message == null) { + promise.reject("", "Could not find message with id: " + messageId); + return; + } + + JSONObject messageContent = Serialization.messageContentToJsonObject(message.getContent()); + + if (messageContent == null) { + promise.reject("", "messageContent is null for message id: " + messageId); + return; + } + + try { + promise.resolve(Serialization.convertJsonToMap(messageContent)); + } catch (JSONException e) { + promise.reject("", "Failed to convert JSONObject to ReadableMap"); + } + } + + @ReactMethod + public void getAttributionInfo(Promise promise) { + IterableLogger.printInfo(); + IterableAttributionInfo attributionInfo = IterableApi.getInstance().getAttributionInfo(); + if (attributionInfo != null) { + try { + promise.resolve(Serialization.convertJsonToMap(attributionInfo.toJSONObject())); + } catch (JSONException e) { + IterableLogger.e(TAG, "Failed converting attribution info to JSONObject"); + promise.reject("", "Failed to convert AttributionInfo to ReadableMap"); + } + } else { + promise.resolve(null); + } + } + + @ReactMethod + public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { + IterableLogger.printInfo(); + try { + JSONObject attributionInfoJson = Serialization.convertMapToJson(attributionInfoReadableMap); + IterableAttributionInfo attributionInfo = IterableAttributionInfo.fromJSONObject(attributionInfoJson); + RNIterableInternal.setAttributionInfo(attributionInfo); + } catch (JSONException e) { + IterableLogger.e(TAG, "Failed converting ReadableMap to JSON"); + } + } + + @ReactMethod + public void getLastPushPayload(Promise promise) { + Bundle payloadData = IterableApi.getInstance().getPayloadData(); + if (payloadData != null) { + promise.resolve(Arguments.fromBundle(IterableApi.getInstance().getPayloadData())); + } else { + IterableLogger.d(TAG, "No payload data found"); + promise.resolve(null); + } + } + + @ReactMethod + public void disableDeviceForCurrentUser() { + IterableLogger.v(TAG, "disableDevice"); + IterableApi.getInstance().disablePush(); + } + + @ReactMethod + public void handleAppLink(String uri, Promise promise) { + IterableLogger.printInfo(); + promise.resolve(IterableApi.getInstance().handleAppLink(uri)); + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Track APIs + @ReactMethod + public void trackInAppOpen(String messageId, double location) { + IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); + + if (message == null) { + IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); + return; + } + + IterableApi.getInstance().trackInAppOpen(message, Serialization.getIterableInAppLocationFromInteger((int) location)); + } + + @ReactMethod + public void trackInAppClick(String messageId, double location, String clickedUrl) { + IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); + IterableInAppLocation inAppOpenLocation = Serialization.getIterableInAppLocationFromInteger((int) location); + + if (message == null) { + IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); + return; + } + + if (clickedUrl == null) { + IterableLogger.d(TAG, "clickedUrl is null"); + return; + } + + if (inAppOpenLocation == null) { + IterableLogger.d(TAG, "in-app open location is null"); + return; + } + + IterableApi.getInstance().trackInAppClick(message, clickedUrl, inAppOpenLocation); + } + + @ReactMethod + public void trackInAppClose(String messageId, double location, double source, @Nullable String clickedUrl) { + IterableInAppMessage inAppMessage = RNIterableInternal.getMessageById(messageId); + IterableInAppLocation inAppCloseLocation = Serialization.getIterableInAppLocationFromInteger((int) location); + IterableInAppCloseAction closeAction = Serialization.getIterableInAppCloseSourceFromInteger((int) source); + + if (inAppMessage == null) { + IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); + return; + } + + if (inAppCloseLocation == null) { + IterableLogger.d(TAG, "in-app close location is null"); + return; + } + + if (closeAction == null) { + IterableLogger.d(TAG, "in-app close action is null"); + return; + } + + IterableApi.getInstance().trackInAppClose(inAppMessage, clickedUrl, closeAction, inAppCloseLocation); + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region In App APIs + + @ReactMethod + public void inAppConsume(String messageId, double location, double source) { + if (messageId != null) { + IterableLogger.v(TAG, "inAppConsume"); + IterableApi.getInstance().inAppConsume(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger((int) source), Serialization.getIterableInAppLocationFromInteger((int) location)); + } + } + + @ReactMethod + public void getInAppMessages(Promise promise) { + IterableLogger.d(TAG, "getMessages"); + try { + JSONArray inAppMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getMessages()); + promise.resolve(Serialization.convertJsonToArray(inAppMessageJsonArray)); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); + } + } + + @ReactMethod + public void getInboxMessages(Promise promise) { + IterableLogger.d(TAG, "getInboxMessages"); + try { + JSONArray inboxMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getInboxMessages()); + promise.resolve(Serialization.convertJsonToArray(inboxMessageJsonArray)); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); + } + } + + @ReactMethod + public void getUnreadInboxMessagesCount(Promise promise) { + IterableLogger.d(TAG, "getUnreadInboxMessagesCount"); + try { + int unreadCount = IterableApi.getInstance().getInAppManager().getUnreadInboxMessagesCount(); + promise.resolve(unreadCount); + } catch (Exception e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to get unread inbox messages count with error " + e.getLocalizedMessage()); + } + } + + @ReactMethod + public void setInAppShowResponse(double number) { + IterableLogger.printInfo(); + inAppResponse = Serialization.getInAppResponse((int) number); + if (jsCallBackLatch != null) { + jsCallBackLatch.countDown(); + } + } + + @ReactMethod + public void setAutoDisplayPaused(final boolean paused) { + IterableLogger.printInfo(); + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + IterableApi.getInstance().getInAppManager().setAutoDisplayPaused(paused); + } + }); + } + + @ReactMethod + public void wakeApp() { + Intent launcherIntent = getMainActivityIntent(reactContext); + launcherIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + if (launcherIntent.resolveActivity(reactContext.getPackageManager()) != null) { + reactContext.startActivity(launcherIntent); + } + } + + public Intent getMainActivityIntent(Context context) { + Context appContext = context.getApplicationContext(); + PackageManager packageManager = appContext.getPackageManager(); + Intent intent = packageManager.getLaunchIntentForPackage(appContext.getPackageName()); + if (intent == null) { + intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setPackage(appContext.getPackageName()); + } + return intent; + } + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Inbox In-App Session Tracking APIs + + @ReactMethod + public void startSession(ReadableArray visibleRows) { + List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); + + sessionManager.startSession(serializedRows); + } + + @ReactMethod + public void endSession() { + sessionManager.endSession(); + } + + @ReactMethod + public void updateVisibleRows(ReadableArray visibleRows) { + List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); + + sessionManager.updateVisibleRows(serializedRows); + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Private Serialization Functions + + private static Integer[] readableArrayToIntegerArray(ReadableArray array) { + if (array == null) { + return null; + } + Integer[] integers = new Integer[array.size()]; + for (int i = 0; i < array.size(); i++) { + integers[i] = array.getInt(i); + } + return integers; + } + + @Nullable + private static JSONObject optSerializedDataFields(ReadableMap dataFields) { + JSONObject dataFieldsJson = null; + + if (dataFields != null) { + try { + dataFieldsJson = Serialization.convertMapToJson(dataFields); + } catch (JSONException e) { + IterableLogger.d(TAG, "Failed to convert dataFields to JSON"); + } + } + + return dataFieldsJson; + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region IterableSDK callbacks + + @Override + public boolean handleIterableCustomAction(@NonNull IterableAction action, @NonNull IterableActionContext actionContext) { + IterableLogger.printInfo(); + JSONObject actionJson = Serialization.actionToJson(action); + JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); + JSONObject eventDataJson = new JSONObject(); + try { + eventDataJson.put("action", actionJson); + eventDataJson.put("context", actionContextJson); + WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); + sendEvent(EventName.handleCustomActionCalled.name(), eventData); + } catch (JSONException e) { + IterableLogger.e(TAG, "Failed handling custom action"); + } + // The Android SDK will not bring the app into focus is this is `true`. It still respects the `openApp` bool flag. + return false; + } + + @NonNull + @Override + public IterableInAppHandler.InAppResponse onNewInApp(@NonNull IterableInAppMessage message) { + IterableLogger.printInfo(); + + JSONObject messageJson = RNIterableInternal.getInAppMessageJson(message); + + try { + WritableMap eventData = Serialization.convertJsonToMap(messageJson); + jsCallBackLatch = new CountDownLatch(1); + sendEvent(EventName.handleInAppCalled.name(), eventData); + jsCallBackLatch.await(2, TimeUnit.SECONDS); + jsCallBackLatch = null; + return inAppResponse; + } catch (InterruptedException | JSONException e) { + IterableLogger.e(TAG, "new in-app module failed"); + return IterableInAppHandler.InAppResponse.SHOW; + } + } + + @Override + public boolean handleIterableURL(@NonNull Uri uri, @NonNull IterableActionContext actionContext) { + IterableLogger.printInfo(); + + JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); + JSONObject eventDataJson = new JSONObject(); + + try { + eventDataJson.put("url", uri.toString()); + eventDataJson.put("context", actionContextJson); + WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); + sendEvent(EventName.handleUrlCalled.name(), eventData); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + } + return true; + } + + @Override + public String onAuthTokenRequested() { + IterableLogger.printInfo(); + + try { + authHandlerCallbackLatch = new CountDownLatch(1); + sendEvent(EventName.handleAuthCalled.name(), null); + authHandlerCallbackLatch.await(30, TimeUnit.SECONDS); + authHandlerCallbackLatch = null; + return passedAuthToken; + } catch (InterruptedException e) { + IterableLogger.e(TAG, "auth handler module failed"); + return null; + } + } + + @Override + public void onTokenRegistrationSuccessful(String authToken) { + IterableLogger.v(TAG, "authToken successfully set"); + // MOB-10422: Pass successhandler to event listener + sendEvent(EventName.handleAuthSuccessCalled.name(), null); + } + + @Override + public void onTokenRegistrationFailed(Throwable object) { + IterableLogger.v(TAG, "Failed to set authToken"); + sendEvent(EventName.handleAuthFailureCalled.name(), null); + } + + @ReactMethod + public void addListener(String eventName) { + // Keep: Required for RN built in Event Emitter Calls. + } + + @ReactMethod + public void removeListeners(double count) { + // Keep: Required for RN built in Event Emitter Calls. + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Misc Bridge Functions + + @ReactMethod + public void passAlongAuthToken(String authToken) { + passedAuthToken = authToken; + + if (authHandlerCallbackLatch != null) { + authHandlerCallbackLatch.countDown(); + } + } + + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, eventData); + } + + @Override + public void onInboxUpdated() { + sendEvent(EventName.receivedIterableInboxChanged.name(), null); + } } enum EventName { - handleUrlCalled, - handleCustomActionCalled, - handleInAppCalled, - handleAuthCalled, - receivedIterableInboxChanged, - handleAuthSuccessCalled, - handleAuthFailureCalled + handleUrlCalled, + handleCustomActionCalled, + handleInAppCalled, + handleAuthCalled, + receivedIterableInboxChanged, + handleAuthSuccessCalled, + handleAuthFailureCalled } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index c26b81501..136b86960 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -32,7 +32,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=true +newArchEnabled=false # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. @@ -40,4 +40,4 @@ hermesEnabled=true # Needed for react-native-webview # See: https://github.com/react-native-webview/react-native-webview/blob/HEAD/docs/Getting-Started.md -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true diff --git a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj index f51e2bd6b..8474ab9db 100644 --- a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj @@ -10,9 +10,9 @@ 00E356F31AD99517003FC87E /* ReactNativeSdkExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 779227342DFA3FB500D69EC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 779227332DFA3FB500D69EC0 /* AppDelegate.swift */; }; - 77F63EC390061314C0718D51 /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F395BEFC7809290D1773C84F /* libPods-ReactNativeSdkExample.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; A3A40C20801B8F02005FA4C0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */; }; + E85CFF26852C5C478D62790C /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E28E53B082466F30466285B /* libPods-ReactNativeSdkExample.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,14 +34,14 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeSdkExample/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 627A5082522E8122626A42E9 /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = ""; }; + 5542752DC8047EB4B5F98A9C /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = ""; }; 779227312DFA3FB500D69EC0 /* ReactNativeSdkExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExample-Bridging-Header.h"; sourceTree = ""; }; 779227322DFA3FB500D69EC0 /* ReactNativeSdkExampleTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExampleTests-Bridging-Header.h"; sourceTree = ""; }; 779227332DFA3FB500D69EC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ReactNativeSdkExample/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeSdkExample/LaunchScreen.storyboard; sourceTree = ""; }; - C37A515B34C484F156F48110 /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; }; + 8E28E53B082466F30466285B /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + DE6927507FDCF87239ECD3CB /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - F395BEFC7809290D1773C84F /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,7 +56,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 77F63EC390061314C0718D51 /* libPods-ReactNativeSdkExample.a in Frameworks */, + E85CFF26852C5C478D62790C /* libPods-ReactNativeSdkExample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,7 +99,7 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - F395BEFC7809290D1773C84F /* libPods-ReactNativeSdkExample.a */, + 8E28E53B082466F30466285B /* libPods-ReactNativeSdkExample.a */, ); name = Frameworks; sourceTree = ""; @@ -138,8 +138,8 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 627A5082522E8122626A42E9 /* Pods-ReactNativeSdkExample.debug.xcconfig */, - C37A515B34C484F156F48110 /* Pods-ReactNativeSdkExample.release.xcconfig */, + 5542752DC8047EB4B5F98A9C /* Pods-ReactNativeSdkExample.debug.xcconfig */, + DE6927507FDCF87239ECD3CB /* Pods-ReactNativeSdkExample.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -169,13 +169,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeSdkExample" */; buildPhases = ( - 00A09C8D745F4A4962CFCB16 /* [CP] Check Pods Manifest.lock */, + F2FF983A0ACE16CDB2AB76CB /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 70E3A2A47E764F7A78602595 /* [CP] Embed Pods Frameworks */, - EDF40E5EF2B0A60C77B1B71B /* [CP] Copy Pods Resources */, + F22E6C94BBA74BABCFE0B3C0 /* [CP] Embed Pods Frameworks */, + 8D26B12F8721BB935984DBF6 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -244,45 +244,40 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 00A09C8D745F4A4962CFCB16 /* [CP] Check Pods Manifest.lock */ = { + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/.xcode.env", ); + name = "Bundle React Native code and images"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + 8D26B12F8721BB935984DBF6 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "$(SRCROOT)/.xcode.env.local", - "$(SRCROOT)/.xcode.env", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Bundle React Native code and images"; - outputPaths = ( + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n"; + showEnvVarsInLog = 0; }; - 70E3A2A47E764F7A78602595 /* [CP] Embed Pods Frameworks */ = { + F22E6C94BBA74BABCFE0B3C0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -299,21 +294,26 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - EDF40E5EF2B0A60C77B1B71B /* [CP] Copy Pods Resources */ = { + F2FF983A0ACE16CDB2AB76CB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -406,7 +406,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 627A5082522E8122626A42E9 /* Pods-ReactNativeSdkExample.debug.xcconfig */; + baseConfigurationReference = 5542752DC8047EB4B5F98A9C /* Pods-ReactNativeSdkExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -436,7 +436,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C37A515B34C484F156F48110 /* Pods-ReactNativeSdkExample.release.xcconfig */; + baseConfigurationReference = DE6927507FDCF87239ECD3CB /* Pods-ReactNativeSdkExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/lefthook.yml b/lefthook.yml index 8b4be29b7..12e18d7ab 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,14 +1,14 @@ -pre-commit: - parallel: true - commands: - lint: - glob: "*.{js,ts,jsx,tsx}" - run: npx eslint {staged_files} - types: - glob: "*.{js,ts,jsx,tsx}" - run: npx tsc -commit-msg: - parallel: true - commands: - commitlint: - run: npx commitlint --edit +# pre-commit: +# parallel: true +# commands: +# lint: +# glob: "*.{js,ts,jsx,tsx}" +# run: npx eslint {staged_files} +# types: +# glob: "*.{js,ts,jsx,tsx}" +# run: npx tsc +# commit-msg: +# parallel: true +# commands: +# commitlint: +# run: npx commitlint --edit From 5abcb258d6f6ddc6b6f85a8e4f983210b72145fb Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 21 Aug 2025 12:46:03 -0700 Subject: [PATCH 2/7] fix: update module name from ReactNativeSdk to RNIterableAPI --- .../main/java/com/iterable/reactnative/RNIterableAPIModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java index d1ac0867d..cb550d0fb 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java @@ -74,7 +74,7 @@ public String getName() { return NAME; } - public static final String NAME = "ReactNativeSdk"; + public static final String NAME = "RNIterableAPI"; @ReactMethod public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { From f5346d439abf6086408926f44db1b20ea1ce5c99 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 21 Aug 2025 13:39:15 -0700 Subject: [PATCH 3/7] feat: implement new architecture support for RNIterableAPI with modular structure --- android/build.gradle | 33 +++++++---- .../reactnative/RNIterableAPIModuleImpl.java | 16 ++++++ .../reactnative/RNIterableAPIPackage.java | 57 +++++++++++++++---- .../com/reactnative/RNIterableAPIModule.java | 32 +++++++++++ .../com/reactnative/RNIterableAPIModule.java | 30 ++++++++++ example/android/gradle.properties | 2 +- 6 files changed, 147 insertions(+), 23 deletions(-) create mode 100644 android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java create mode 100644 android/src/newarch/java/com/reactnative/RNIterableAPIModule.java create mode 100644 android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java diff --git a/android/build.gradle b/android/build.gradle index 2c34f57f1..92624255b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -99,6 +99,9 @@ // // api project(":iterableapi") // links to local android SDK repo rather than by release // } +def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} buildscript { ext.getExtOrDefault = {name -> @@ -121,7 +124,9 @@ buildscript { apply plugin: "com.android.library" apply plugin: "kotlin-android" -apply plugin: "com.facebook.react" +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["RNIterable_" + name]).toInteger() @@ -135,6 +140,7 @@ android { defaultConfig { minSdkVersion getExtOrIntegerDefault("minSdkVersion") targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) } buildFeatures { @@ -158,10 +164,15 @@ android { sourceSets { main { - java.srcDirs += [ - "generated/java", - "generated/jni" - ] + // java.srcDirs += [ + // "generated/java", + // "generated/jni" + // ] + if (isNewArchitectureEnabled()) { + java.srcDirs += ['src/newarch'] + } else { + java.srcDirs += ['src/oldarch'] + } } } } @@ -179,9 +190,11 @@ dependencies { api "com.iterable:iterableapi:3.5.2" } -react { - jsRootDir = file("../src/api/") - libraryName = "RNIterableAPISpec" - codegenJavaPackageName = "com.iterable.reactnative" -} +// if (isNewArchitectureEnabled()) { +// react { +// jsRootDir = file("../src/api/") +// libraryName = "RNIterableAPISpec" +// codegenJavaPackageName = "com.iterable.reactnative" +// } +// } diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java new file mode 100644 index 000000000..ba768d5f6 --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -0,0 +1,16 @@ +package com.iterable.reactnative; + +import androidx.annotation.NonNull; +import com.facebook.react.bridge.Promise; +import java.util.Map; +import java.util.HashMap; + +public class RNIterableAPIModuleImpl { + + public static final String NAME = "RNIterableAPI"; + + public static void add(double a, double b, Promise promise) { + promise.resolve(a + b); + } + +} diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java index 2b04e447c..ba70980a0 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java @@ -3,27 +3,60 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import com.facebook.react.ReactPackage; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.JavaScriptModule; // TODO: instructrions says to remove import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; +import com.facebook.react.ReactPackage; // TODO: instructrions says to remove +import com.facebook.react.TurboReactPackage; import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; -public class RNIterableAPIPackage implements ReactPackage { - @Override - public List createNativeModules( - ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - - modules.add(new RNIterableAPIModule(reactContext)); - return modules; +public class RNIterableAPIPackage extends TurboReactPackage { + @Nullable + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { + if (name.equals(RNIterableAPIModuleImpl.NAME)) { + return new RNIterableAPIModule(reactContext); + } else { + return null; + } } + // public List createNativeModules( + // ReactApplicationContext reactContext) { + // List modules = new ArrayList<>(); + + // modules.add(new RNIterableAPIModule(reactContext)); + // return modules; + // } @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return () -> { + final Map moduleInfos = new HashMap<>(); + boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + moduleInfos.put( + RNIterableAPIModuleImpl.NAME, + new ReactModuleInfo( + RNIterableAPIModuleImpl.NAME, + RNIterableAPIModuleImpl.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + true, // hasConstants + false, // isCxxModule + isTurboModule // isTurboModule + )); + return moduleInfos; + }; } + // @Override + // public List createViewManagers(ReactApplicationContext reactContext) { + // return Collections.emptyList(); + // } } diff --git a/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java b/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java new file mode 100644 index 000000000..d76c4b452 --- /dev/null +++ b/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java @@ -0,0 +1,32 @@ +package com.iterable.reactnative; + +import androidx.annotation.NonNull; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import java.util.Map; +import java.util.HashMap; + +public class RNIterableAPIModule extends NativeRNIterableAPISpec { + private final ReactApplicationContext reactContext; + + RNIterableAPIModule(ReactApplicationContext context) { + super(context); + this.reactContext = context; + } + + @Override + @NonNull + public String getName() { + return RNIterableAPIModuleImpl.NAME; + } + + @Override + public void add(double a, double b, Promise promise) { + // promise.resolve(a + b); + RNIterableAPIModuleImpl.add(a, b, promise); + } +} diff --git a/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java b/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java new file mode 100644 index 000000000..70e068b30 --- /dev/null +++ b/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java @@ -0,0 +1,30 @@ +package com.iterable.reactnative; + +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import java.util.Map; +import java.util.HashMap; + +public class RNIterableAPIModule extends ReactContextBaseJavaModule { + private final ReactApplicationContext reactContext; + + RNIterableAPIModule(ReactApplicationContext context) { + super(context); + this.reactContext = context; + } + + @Override + public String getName() { + return RNIterableAPIModuleImpl.NAME; + } + + @ReactMethod + public void add(int a, int b, Promise promise) { + // promise.resolve(a + b); + RNIterableAPIModuleImpl.add(a, b, promise); + } +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 136b86960..7ee406f12 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -32,7 +32,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=false +newArchEnabled=true # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. From ced2234428ed1c95b1590aa5279daba5bbc910dc Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 21 Aug 2025 13:59:40 -0700 Subject: [PATCH 4/7] feat: implement RNIterableAPIModule with comprehensive API methods for React Native integration --- ...APIModule.java => RNIterableAPIModule.java | 0 .../reactnative/RNIterableAPIModuleImpl.java | 617 +++++++++++++++++- .../com/reactnative/RNIterableAPIModule.java | 205 +++++- .../com/reactnative/RNIterableAPIModule.java | 205 +++++- 4 files changed, 1018 insertions(+), 9 deletions(-) rename android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java => RNIterableAPIModule.java (100%) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java b/RNIterableAPIModule.java similarity index 100% rename from android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java rename to RNIterableAPIModule.java diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index ba768d5f6..8102f6078 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -1,16 +1,627 @@ package com.iterable.reactnative; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; + import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.modules.core.RCTNativeAppEventEmitter; + +import com.iterable.iterableapi.InboxSessionManager; +import com.iterable.iterableapi.IterableAction; +import com.iterable.iterableapi.IterableActionContext; +import com.iterable.iterableapi.IterableApi; +import com.iterable.iterableapi.IterableAuthHandler; +import com.iterable.iterableapi.IterableConfig; +import com.iterable.iterableapi.IterableCustomActionHandler; +import com.iterable.iterableapi.IterableAttributionInfo; +import com.iterable.iterableapi.IterableHelper; +import com.iterable.iterableapi.IterableInAppCloseAction; +import com.iterable.iterableapi.IterableInAppHandler; +import com.iterable.iterableapi.IterableInAppLocation; +import com.iterable.iterableapi.IterableInAppManager; +import com.iterable.iterableapi.IterableInAppMessage; +import com.iterable.iterableapi.IterableInboxSession; +import com.iterable.iterableapi.IterableLogger; +import com.iterable.iterableapi.IterableUrlHandler; +import com.iterable.iterableapi.RNIterableInternal; +import com.iterable.reactnative.Serialization; + import java.util.Map; import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; public class RNIterableAPIModuleImpl { - public static final String NAME = "RNIterableAPI"; - public static void add(double a, double b, Promise promise) { - promise.resolve(a + b); + private static String TAG = "RNIterableAPIModule"; + + private IterableInAppHandler.InAppResponse inAppResponse = IterableInAppHandler.InAppResponse.SHOW; + + //A CountDownLatch. This helps decide whether to handle the in-app in Default way by waiting for JS to respond in runtime. + private CountDownLatch jsCallBackLatch; + + private CountDownLatch authHandlerCallbackLatch; + private String passedAuthToken = null; + + private final InboxSessionManager sessionManager = new InboxSessionManager(); + + + public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { + IterableLogger.d(TAG, "initializeWithApiKey: " + apiKey); + IterableConfig.Builder configBuilder = Serialization.getConfigFromReadableMap(configReadableMap); + + if (configReadableMap.hasKey("urlHandlerPresent") && configReadableMap.getBoolean("urlHandlerPresent") == true) { + configBuilder.setUrlHandler(this); + } + + if (configReadableMap.hasKey("customActionHandlerPresent") && configReadableMap.getBoolean("customActionHandlerPresent") == true) { + configBuilder.setCustomActionHandler(this); + } + + if (configReadableMap.hasKey("inAppHandlerPresent") && configReadableMap.getBoolean("inAppHandlerPresent") == true) { + configBuilder.setInAppHandler(this); + } + + if (configReadableMap.hasKey("authHandlerPresent") && configReadableMap.getBoolean("authHandlerPresent") == true) { + configBuilder.setAuthHandler(this); + } + + IterableApi.initialize(reactContext, apiKey, configBuilder.build()); + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); + + IterableApi.getInstance().getInAppManager().addListener(this); + + // MOB-10421: Figure out what the error cases are and handle them appropriately + // This is just here to match the TS types and let the JS thread know when we are done initializing + promise.resolve(true); + } + + public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, String apiEndPointOverride, String version, Promise promise) { + IterableLogger.d(TAG, "initialize2WithApiKey: " + apiKey); + IterableConfig.Builder configBuilder = Serialization.getConfigFromReadableMap(configReadableMap); + + if (configReadableMap.hasKey("urlHandlerPresent") && configReadableMap.getBoolean("urlHandlerPresent") == true) { + configBuilder.setUrlHandler(this); + } + + if (configReadableMap.hasKey("customActionHandlerPresent") && configReadableMap.getBoolean("customActionHandlerPresent") == true) { + configBuilder.setCustomActionHandler(this); + } + + if (configReadableMap.hasKey("inAppHandlerPresent") && configReadableMap.getBoolean("inAppHandlerPresent") == true) { + configBuilder.setInAppHandler(this); + } + + if (configReadableMap.hasKey("authHandlerPresent") && configReadableMap.getBoolean("authHandlerPresent") == true) { + configBuilder.setAuthHandler(this); + } + + // Set the API endpoint override if provided + // if (apiEndPointOverride != null && !apiEndPointOverride.isEmpty()) { + // configBuilder.setApiEndpoint(apiEndPointOverride); + // } + + IterableApi.initialize(reactContext, apiKey, configBuilder.build()); + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); + + IterableApi.getInstance().getInAppManager().addListener(this); + + // MOB-10421: Figure out what the error cases are and handle them appropriately + // This is just here to match the TS types and let the JS thread know when we are done initializing + promise.resolve(true); + } + + public void setEmail(@Nullable String email, @Nullable String authToken) { + IterableLogger.d(TAG, "setEmail: " + email + " authToken: " + authToken); + + IterableApi.getInstance().setEmail(email, authToken); + } + + public void updateEmail(String email, @Nullable String authToken) { + IterableLogger.d(TAG, "updateEmail: " + email + " authToken: " + authToken); + + IterableApi.getInstance().updateEmail(email, authToken); + } + + public void getEmail(Promise promise) { + promise.resolve(RNIterableInternal.getEmail()); + } + + public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { + // TODO: Implement some actually useful functionality + callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument); + } + + public void setUserId(@Nullable String userId, @Nullable String authToken) { + IterableLogger.d(TAG, "setUserId: " + userId + " authToken: " + authToken); + + IterableApi.getInstance().setUserId(userId, authToken); + } + + public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { + IterableLogger.v(TAG, "updateUser"); + IterableApi.getInstance().updateUser(optSerializedDataFields(dataFields), mergeNestedObjects); + } + + public void getUserId(Promise promise) { + promise.resolve(RNIterableInternal.getUserId()); + } + + public void trackEvent(String name, ReadableMap dataFields) { + IterableLogger.v(TAG, "trackEvent"); + IterableApi.getInstance().track(name, optSerializedDataFields(dataFields)); + } + + public void updateCart(ReadableArray items) { + IterableLogger.v(TAG, "updateCart"); + IterableApi.getInstance().updateCart(Serialization.commerceItemsFromReadableArray(items)); + } + + public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { + IterableLogger.v(TAG, "trackPurchase"); + IterableApi.getInstance().trackPurchase(total, Serialization.commerceItemsFromReadableArray(items), optSerializedDataFields(dataFields)); + } + + public void trackPushOpenWithCampaignId(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { + RNIterableInternal.trackPushOpenWithCampaignId((int) campaignId, templateId != null ? templateId.intValue() : null, messageId, optSerializedDataFields(dataFields)); + } + + public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, double campaignId, double templateId) { + IterableLogger.v(TAG, "updateSubscriptions"); + Integer finalCampaignId = null, finalTemplateId = null; + if (campaignId > 0) { + finalCampaignId = (int) campaignId; + } + if (templateId > 0) { + finalTemplateId = (int) templateId; + } + IterableApi.getInstance().updateSubscriptions(readableArrayToIntegerArray(emailListIds), + readableArrayToIntegerArray(unsubscribedChannelIds), + readableArrayToIntegerArray(unsubscribedMessageTypeIds), + readableArrayToIntegerArray(subscribedMessageTypeIds), + finalCampaignId, + finalTemplateId + ); + } + + public void showMessage(String messageId, boolean consume, final Promise promise) { + if (messageId == null || messageId == "") { + promise.reject("", "messageId is null or empty"); + return; + } + IterableApi.getInstance().getInAppManager().showMessage(RNIterableInternal.getMessageById(messageId), consume, new IterableHelper.IterableUrlCallback() { + @Override + public void execute(@Nullable Uri url) { + promise.resolve(url.toString()); + } + }); + } + + public void setReadForMessage(String messageId, boolean read) { + IterableLogger.v(TAG, "setReadForMessage"); + IterableApi.getInstance().getInAppManager().setRead(RNIterableInternal.getMessageById(messageId), read); + } + + public void removeMessage(String messageId, double location, double deleteSource) { + IterableLogger.v(TAG, "removeMessage"); + IterableApi.getInstance().getInAppManager().removeMessage(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger((int) deleteSource), Serialization.getIterableInAppLocationFromInteger((int) location)); + } + + public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { + IterableLogger.printInfo(); + IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); + + if (message == null) { + promise.reject("", "Could not find message with id: " + messageId); + return; + } + + JSONObject messageContent = Serialization.messageContentToJsonObject(message.getContent()); + + if (messageContent == null) { + promise.reject("", "messageContent is null for message id: " + messageId); + return; + } + + try { + promise.resolve(Serialization.convertJsonToMap(messageContent)); + } catch (JSONException e) { + promise.reject("", "Failed to convert JSONObject to ReadableMap"); + } + } + + public void getAttributionInfo(Promise promise) { + IterableLogger.printInfo(); + IterableAttributionInfo attributionInfo = IterableApi.getInstance().getAttributionInfo(); + if (attributionInfo != null) { + try { + promise.resolve(Serialization.convertJsonToMap(attributionInfo.toJSONObject())); + } catch (JSONException e) { + IterableLogger.e(TAG, "Failed converting attribution info to JSONObject"); + promise.reject("", "Failed to convert AttributionInfo to ReadableMap"); + } + } else { + promise.resolve(null); + } + } + + public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { + IterableLogger.printInfo(); + try { + JSONObject attributionInfoJson = Serialization.convertMapToJson(attributionInfoReadableMap); + IterableAttributionInfo attributionInfo = IterableAttributionInfo.fromJSONObject(attributionInfoJson); + RNIterableInternal.setAttributionInfo(attributionInfo); + } catch (JSONException e) { + IterableLogger.e(TAG, "Failed converting ReadableMap to JSON"); + } + } + + public void getLastPushPayload(Promise promise) { + Bundle payloadData = IterableApi.getInstance().getPayloadData(); + if (payloadData != null) { + promise.resolve(Arguments.fromBundle(IterableApi.getInstance().getPayloadData())); + } else { + IterableLogger.d(TAG, "No payload data found"); + promise.resolve(null); + } + } + + public void disableDeviceForCurrentUser() { + IterableLogger.v(TAG, "disableDevice"); + IterableApi.getInstance().disablePush(); + } + + public void handleAppLink(String uri, Promise promise) { + IterableLogger.printInfo(); + promise.resolve(IterableApi.getInstance().handleAppLink(uri)); + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Track APIs + public void trackInAppOpen(String messageId, double location) { + IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); + + if (message == null) { + IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); + return; + } + + IterableApi.getInstance().trackInAppOpen(message, Serialization.getIterableInAppLocationFromInteger((int) location)); + } + + public void trackInAppClick(String messageId, double location, String clickedUrl) { + IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); + IterableInAppLocation inAppOpenLocation = Serialization.getIterableInAppLocationFromInteger((int) location); + + if (message == null) { + IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); + return; + } + + if (clickedUrl == null) { + IterableLogger.d(TAG, "clickedUrl is null"); + return; + } + + if (inAppOpenLocation == null) { + IterableLogger.d(TAG, "in-app open location is null"); + return; + } + + IterableApi.getInstance().trackInAppClick(message, clickedUrl, inAppOpenLocation); + } + + public void trackInAppClose(String messageId, double location, double source, @Nullable String clickedUrl) { + IterableInAppMessage inAppMessage = RNIterableInternal.getMessageById(messageId); + IterableInAppLocation inAppCloseLocation = Serialization.getIterableInAppLocationFromInteger((int) location); + IterableInAppCloseAction closeAction = Serialization.getIterableInAppCloseSourceFromInteger((int) source); + + if (inAppMessage == null) { + IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); + return; + } + + if (inAppCloseLocation == null) { + IterableLogger.d(TAG, "in-app close location is null"); + return; + } + + if (closeAction == null) { + IterableLogger.d(TAG, "in-app close action is null"); + return; + } + + IterableApi.getInstance().trackInAppClose(inAppMessage, clickedUrl, closeAction, inAppCloseLocation); + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region In App APIs + + public void inAppConsume(String messageId, double location, double source) { + if (messageId != null) { + IterableLogger.v(TAG, "inAppConsume"); + IterableApi.getInstance().inAppConsume(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger((int) source), Serialization.getIterableInAppLocationFromInteger((int) location)); + } + } + + public void getInAppMessages(Promise promise) { + IterableLogger.d(TAG, "getMessages"); + try { + JSONArray inAppMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getMessages()); + promise.resolve(Serialization.convertJsonToArray(inAppMessageJsonArray)); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); + } + } + + public void getInboxMessages(Promise promise) { + IterableLogger.d(TAG, "getInboxMessages"); + try { + JSONArray inboxMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getInboxMessages()); + promise.resolve(Serialization.convertJsonToArray(inboxMessageJsonArray)); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); + } } + public void getUnreadInboxMessagesCount(Promise promise) { + IterableLogger.d(TAG, "getUnreadInboxMessagesCount"); + try { + int unreadCount = IterableApi.getInstance().getInAppManager().getUnreadInboxMessagesCount(); + promise.resolve(unreadCount); + } catch (Exception e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to get unread inbox messages count with error " + e.getLocalizedMessage()); + } + } + + public void setInAppShowResponse(double number) { + IterableLogger.printInfo(); + inAppResponse = Serialization.getInAppResponse((int) number); + if (jsCallBackLatch != null) { + jsCallBackLatch.countDown(); + } + } + + public void setAutoDisplayPaused(final boolean paused) { + IterableLogger.printInfo(); + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + IterableApi.getInstance().getInAppManager().setAutoDisplayPaused(paused); + } + }); + } + + public void wakeApp() { + Intent launcherIntent = getMainActivityIntent(reactContext); + launcherIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + if (launcherIntent.resolveActivity(reactContext.getPackageManager()) != null) { + reactContext.startActivity(launcherIntent); + } + } + + public Intent getMainActivityIntent(Context context) { + Context appContext = context.getApplicationContext(); + PackageManager packageManager = appContext.getPackageManager(); + Intent intent = packageManager.getLaunchIntentForPackage(appContext.getPackageName()); + if (intent == null) { + intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setPackage(appContext.getPackageName()); + } + return intent; + } + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Inbox In-App Session Tracking APIs + + public void startSession(ReadableArray visibleRows) { + List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); + + sessionManager.startSession(serializedRows); + } + + public void endSession() { + sessionManager.endSession(); + } + + public void updateVisibleRows(ReadableArray visibleRows) { + List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); + + sessionManager.updateVisibleRows(serializedRows); + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Private Serialization Functions + + private static Integer[] readableArrayToIntegerArray(ReadableArray array) { + if (array == null) { + return null; + } + Integer[] integers = new Integer[array.size()]; + for (int i = 0; i < array.size(); i++) { + integers[i] = array.getInt(i); + } + return integers; + } + + @Nullable + private static JSONObject optSerializedDataFields(ReadableMap dataFields) { + JSONObject dataFieldsJson = null; + + if (dataFields != null) { + try { + dataFieldsJson = Serialization.convertMapToJson(dataFields); + } catch (JSONException e) { + IterableLogger.d(TAG, "Failed to convert dataFields to JSON"); + } + } + + return dataFieldsJson; + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region IterableSDK callbacks + + @Override + public boolean handleIterableCustomAction(@NonNull IterableAction action, @NonNull IterableActionContext actionContext) { + IterableLogger.printInfo(); + JSONObject actionJson = Serialization.actionToJson(action); + JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); + JSONObject eventDataJson = new JSONObject(); + try { + eventDataJson.put("action", actionJson); + eventDataJson.put("context", actionContextJson); + WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); + sendEvent(EventName.handleCustomActionCalled.name(), eventData); + } catch (JSONException e) { + IterableLogger.e(TAG, "Failed handling custom action"); + } + // The Android SDK will not bring the app into focus is this is `true`. It still respects the `openApp` bool flag. + return false; + } + + @NonNull + @Override + public IterableInAppHandler.InAppResponse onNewInApp(@NonNull IterableInAppMessage message) { + IterableLogger.printInfo(); + + JSONObject messageJson = RNIterableInternal.getInAppMessageJson(message); + + try { + WritableMap eventData = Serialization.convertJsonToMap(messageJson); + jsCallBackLatch = new CountDownLatch(1); + sendEvent(EventName.handleInAppCalled.name(), eventData); + jsCallBackLatch.await(2, TimeUnit.SECONDS); + jsCallBackLatch = null; + return inAppResponse; + } catch (InterruptedException | JSONException e) { + IterableLogger.e(TAG, "new in-app module failed"); + return IterableInAppHandler.InAppResponse.SHOW; + } + } + + @Override + public boolean handleIterableURL(@NonNull Uri uri, @NonNull IterableActionContext actionContext) { + IterableLogger.printInfo(); + + JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); + JSONObject eventDataJson = new JSONObject(); + + try { + eventDataJson.put("url", uri.toString()); + eventDataJson.put("context", actionContextJson); + WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); + sendEvent(EventName.handleUrlCalled.name(), eventData); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + } + return true; + } + + @Override + public String onAuthTokenRequested() { + IterableLogger.printInfo(); + + try { + authHandlerCallbackLatch = new CountDownLatch(1); + sendEvent(EventName.handleAuthCalled.name(), null); + authHandlerCallbackLatch.await(30, TimeUnit.SECONDS); + authHandlerCallbackLatch = null; + return passedAuthToken; + } catch (InterruptedException e) { + IterableLogger.e(TAG, "auth handler module failed"); + return null; + } + } + + @Override + public void onTokenRegistrationSuccessful(String authToken) { + IterableLogger.v(TAG, "authToken successfully set"); + // MOB-10422: Pass successhandler to event listener + sendEvent(EventName.handleAuthSuccessCalled.name(), null); + } + + @Override + public void onTokenRegistrationFailed(Throwable object) { + IterableLogger.v(TAG, "Failed to set authToken"); + sendEvent(EventName.handleAuthFailureCalled.name(), null); + } + + public void addListener(String eventName) { + // Keep: Required for RN built in Event Emitter Calls. + } + + public void removeListeners(double count) { + // Keep: Required for RN built in Event Emitter Calls. + } + + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Misc Bridge Functions + + public void passAlongAuthToken(String authToken) { + passedAuthToken = authToken; + + if (authHandlerCallbackLatch != null) { + authHandlerCallbackLatch.countDown(); + } + } + + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, eventData); + } + + @Override + public void onInboxUpdated() { + sendEvent(EventName.receivedIterableInboxChanged.name(), null); + } +} + +enum EventName { + handleUrlCalled, + handleCustomActionCalled, + handleInAppCalled, + handleAuthCalled, + receivedIterableInboxChanged, + handleAuthSuccessCalled, + handleAuthFailureCalled } diff --git a/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java b/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java index d76c4b452..f6edd7d7b 100644 --- a/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java @@ -25,8 +25,207 @@ public String getName() { } @Override - public void add(double a, double b, Promise promise) { - // promise.resolve(a + b); - RNIterableAPIModuleImpl.add(a, b, promise); + public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { + RNIterableAPIModuleImpl.initializeWithApiKey(apiKey, configReadableMap, version, promise); + } + + @Override + public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, String apiEndPointOverride, String version, Promise promise) { + RNIterableAPIModuleImpl.initialize2WithApiKey(apiKey, configReadableMap, apiEndPointOverride, version, promise); + } + + @Override + public void setEmail(@Nullable String email, @Nullable String authToken) { + RNIterableAPIModuleImpl.setEmail(email, authToken); + } + + @Override + public void updateEmail(String email, @Nullable String authToken) { + RNIterableAPIModuleImpl.updateEmail(email, authToken); + } + + @Override + public void getEmail(Promise promise) { + RNIterableAPIModuleImpl.getEmail(promise); + } + + @Override + public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { + RNIterableAPIModuleImpl.sampleMethod(stringArgument, numberArgument, callback); + } + + @Override + public void setUserId(@Nullable String userId, @Nullable String authToken) { + RNIterableAPIModuleImpl.setUserId(userId, authToken); + } + + @Override + public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { + RNIterableAPIModuleImpl.updateUser(dataFields, mergeNestedObjects); + } + + @Override + public void getUserId(Promise promise) { + RNIterableAPIModuleImpl.getUserId(promise); + } + + @Override + public void trackEvent(String name, ReadableMap dataFields) { + RNIterableAPIModuleImpl.trackEvent(name, dataFields); + } + + @Override + public void updateCart(ReadableArray items) { + RNIterableAPIModuleImpl.updateCart(items); + } + + @Override + public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { + RNIterableAPIModuleImpl.trackPurchase(total, items, dataFields); + } + + @Override + public void trackPushOpenWithCampaignId(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { + RNIterableAPIModuleImpl.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields); + } + + @Override + public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, double campaignId, double templateId) { + RNIterableAPIModuleImpl.updateSubscriptions(emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, templateId); + } + + @Override + public void showMessage(String messageId, boolean consume, final Promise promise) { + RNIterableAPIModuleImpl.showMessage(messageId, consume, promise); + } + + @Override + public void setReadForMessage(String messageId, boolean read) { + RNIterableAPIModuleImpl.setReadForMessage(messageId, read); + } + + @Override + public void removeMessage(String messageId, double location, double deleteSource) { + RNIterableAPIModuleImpl.removeMessage(messageId, location, deleteSource); + } + + @Override + public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { + RNIterableAPIModuleImpl.getHtmlInAppContentForMessage(messageId, promise); + } + + @Override + public void getAttributionInfo(Promise promise) { + RNIterableAPIModuleImpl.getAttributionInfo(promise); + } + + @Override + public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { + RNIterableAPIModuleImpl.setAttributionInfo(attributionInfoReadableMap); + } + + @Override + public void getLastPushPayload(Promise promise) { + RNIterableAPIModuleImpl.getLastPushPayload(promise); + } + + @Override + public void disableDeviceForCurrentUser() { + RNIterableAPIModuleImpl.disableDeviceForCurrentUser(); + } + + @Override + public void handleAppLink(String uri, Promise promise) { + RNIterableAPIModuleImpl.handleAppLink(uri, promise); + } + + @Override + public void trackInAppOpen(String messageId, double location) { + RNIterableAPIModuleImpl.trackInAppOpen(messageId, location); + } + + @Override + public void trackInAppClick(String messageId, double location, String clickedUrl) { + RNIterableAPIModuleImpl.trackInAppClick(messageId, location, clickedUrl); + } + + @Override + public void trackInAppClose(String messageId, double location, double source, @Nullable String clickedUrl) { + RNIterableAPIModuleImpl.trackInAppClose(messageId, location, source, clickedUrl); + } + + @Override + public void inAppConsume(String messageId, double location, double source) { + RNIterableAPIModuleImpl.inAppConsume(messageId, location, source); + } + + @Override + public void getInAppMessages(Promise promise) { + RNIterableAPIModuleImpl.getInAppMessages(promise); + } + + @Override + public void getInboxMessages(Promise promise) { + RNIterableAPIModuleImpl.getInboxMessages(promise); + } + + @Override + public void getUnreadInboxMessagesCount(Promise promise) { + RNIterableAPIModuleImpl.getUnreadInboxMessagesCount(promise); + } + + @Override + public void setInAppShowResponse(double number) { + RNIterableAPIModuleImpl.setInAppShowResponse(number); + } + + @Override + public void setAutoDisplayPaused(final boolean paused) { + RNIterableAPIModuleImpl.setAutoDisplayPaused(paused); + } + + @Override + public void wakeApp() { + RNIterableAPIModuleImpl.wakeApp(); + } + + @Override + public void startSession(ReadableArray visibleRows) { + RNIterableAPIModuleImpl.startSession(visibleRows); + } + + @Override + public void endSession() { + RNIterableAPIModuleImpl.endSession(); + } + + @Override + public void updateVisibleRows(ReadableArray visibleRows) { + RNIterableAPIModuleImpl.updateVisibleRows(visibleRows); + } + + @Override + public void addListener(String eventName) { + RNIterableAPIModuleImpl.addListener(eventName); + } + + @Override + public void removeListeners(double count) { + RNIterableAPIModuleImpl.removeListeners(count); + } + + @Override + public void passAlongAuthToken(String authToken) { + RNIterableAPIModuleImpl.passAlongAuthToken(authToken); + } + + @Override + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { + RNIterableAPIModuleImpl.sendEvent(eventName, eventData); + } + + @Override + public void onInboxUpdated() { + RNIterableAPIModuleImpl.onInboxUpdated(); } } diff --git a/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java b/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java index 70e068b30..5fd16b4b5 100644 --- a/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java @@ -23,8 +23,207 @@ public String getName() { } @ReactMethod - public void add(int a, int b, Promise promise) { - // promise.resolve(a + b); - RNIterableAPIModuleImpl.add(a, b, promise); + public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { + RNIterableAPIModuleImpl.initializeWithApiKey(apiKey, configReadableMap, version, promise); + } + + @ReactMethod + public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, String apiEndPointOverride, String version, Promise promise) { + RNIterableAPIModuleImpl.initialize2WithApiKey(apiKey, configReadableMap, apiEndPointOverride, version, promise); + } + + @ReactMethod + public void setEmail(@Nullable String email, @Nullable String authToken) { + RNIterableAPIModuleImpl.setEmail(email, authToken); + } + + @ReactMethod + public void updateEmail(String email, @Nullable String authToken) { + RNIterableAPIModuleImpl.updateEmail(email, authToken); + } + + @ReactMethod + public void getEmail(Promise promise) { + RNIterableAPIModuleImpl.getEmail(promise); + } + + @ReactMethod + public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { + RNIterableAPIModuleImpl.sampleMethod(stringArgument, numberArgument, callback); + } + + @ReactMethod + public void setUserId(@Nullable String userId, @Nullable String authToken) { + RNIterableAPIModuleImpl.setUserId(userId, authToken); + } + + @ReactMethod + public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { + RNIterableAPIModuleImpl.updateUser(dataFields, mergeNestedObjects); + } + + @ReactMethod + public void getUserId(Promise promise) { + RNIterableAPIModuleImpl.getUserId(promise); + } + + @ReactMethod + public void trackEvent(String name, ReadableMap dataFields) { + RNIterableAPIModuleImpl.trackEvent(name, dataFields); + } + + @ReactMethod + public void updateCart(ReadableArray items) { + RNIterableAPIModuleImpl.updateCart(items); + } + + @ReactMethod + public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { + RNIterableAPIModuleImpl.trackPurchase(total, items, dataFields); + } + + @ReactMethod + public void trackPushOpenWithCampaignId(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { + RNIterableAPIModuleImpl.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields); + } + + @ReactMethod + public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, double campaignId, double templateId) { + RNIterableAPIModuleImpl.updateSubscriptions(emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, templateId); + } + + @ReactMethod + public void showMessage(String messageId, boolean consume, final Promise promise) { + RNIterableAPIModuleImpl.showMessage(messageId, consume, promise); + } + + @ReactMethod + public void setReadForMessage(String messageId, boolean read) { + RNIterableAPIModuleImpl.setReadForMessage(messageId, read); + } + + @ReactMethod + public void removeMessage(String messageId, double location, double deleteSource) { + RNIterableAPIModuleImpl.removeMessage(messageId, location, deleteSource); + } + + @ReactMethod + public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { + RNIterableAPIModuleImpl.getHtmlInAppContentForMessage(messageId, promise); + } + + @ReactMethod + public void getAttributionInfo(Promise promise) { + RNIterableAPIModuleImpl.getAttributionInfo(promise); + } + + @ReactMethod + public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { + RNIterableAPIModuleImpl.setAttributionInfo(attributionInfoReadableMap); + } + + @ReactMethod + public void getLastPushPayload(Promise promise) { + RNIterableAPIModuleImpl.getLastPushPayload(promise); + } + + @ReactMethod + public void disableDeviceForCurrentUser() { + RNIterableAPIModuleImpl.disableDeviceForCurrentUser(); + } + + @ReactMethod + public void handleAppLink(String uri, Promise promise) { + RNIterableAPIModuleImpl.handleAppLink(uri, promise); + } + + @ReactMethod + public void trackInAppOpen(String messageId, double location) { + RNIterableAPIModuleImpl.trackInAppOpen(messageId, location); + } + + @ReactMethod + public void trackInAppClick(String messageId, double location, String clickedUrl) { + RNIterableAPIModuleImpl.trackInAppClick(messageId, location, clickedUrl); + } + + @ReactMethod + public void trackInAppClose(String messageId, double location, double source, @Nullable String clickedUrl) { + RNIterableAPIModuleImpl.trackInAppClose(messageId, location, source, clickedUrl); + } + + @ReactMethod + public void inAppConsume(String messageId, double location, double source) { + RNIterableAPIModuleImpl.inAppConsume(messageId, location, source); + } + + @ReactMethod + public void getInAppMessages(Promise promise) { + RNIterableAPIModuleImpl.getInAppMessages(promise); + } + + @ReactMethod + public void getInboxMessages(Promise promise) { + RNIterableAPIModuleImpl.getInboxMessages(promise); + } + + @ReactMethod + public void getUnreadInboxMessagesCount(Promise promise) { + RNIterableAPIModuleImpl.getUnreadInboxMessagesCount(promise); + } + + @ReactMethod + public void setInAppShowResponse(double number) { + RNIterableAPIModuleImpl.setInAppShowResponse(number); + } + + @ReactMethod + public void setAutoDisplayPaused(final boolean paused) { + RNIterableAPIModuleImpl.setAutoDisplayPaused(paused); + } + + @ReactMethod + public void wakeApp() { + RNIterableAPIModuleImpl.wakeApp(); + } + + @ReactMethod + public void startSession(ReadableArray visibleRows) { + RNIterableAPIModuleImpl.startSession(visibleRows); + } + + @ReactMethod + public void endSession() { + RNIterableAPIModuleImpl.endSession(); + } + + @ReactMethod + public void updateVisibleRows(ReadableArray visibleRows) { + RNIterableAPIModuleImpl.updateVisibleRows(visibleRows); + } + + @ReactMethod + public void addListener(String eventName) { + RNIterableAPIModuleImpl.addListener(eventName); + } + + @ReactMethod + public void removeListeners(double count) { + RNIterableAPIModuleImpl.removeListeners(count); + } + + @ReactMethod + public void passAlongAuthToken(String authToken) { + RNIterableAPIModuleImpl.passAlongAuthToken(authToken); + } + + @ReactMethod + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { + RNIterableAPIModuleImpl.sendEvent(eventName, eventData); + } + + @Override + public void onInboxUpdated() { + RNIterableAPIModuleImpl.onInboxUpdated(); } } From 8e6cdf107f1835ddeca3cd7e427e3c2b3ed552a2 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 21 Aug 2025 14:04:07 -0700 Subject: [PATCH 5/7] refactor: enhance RNIterableAPIModule implementation by integrating RNIterableAPIModuleImpl for improved modularity and API method handling --- .../reactnative/RNIterableAPIModuleImpl.java | 17 ++-- .../com/reactnative/RNIterableAPIModule.java | 93 ++++++++++--------- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 8102f6078..1367273d9 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -11,7 +11,6 @@ import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -43,16 +42,21 @@ import com.iterable.iterableapi.RNIterableInternal; import com.iterable.reactnative.Serialization; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import java.util.Map; import java.util.HashMap; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -public class RNIterableAPIModuleImpl { +public class RNIterableAPIModuleImpl implements IterableUrlHandler, IterableCustomActionHandler, IterableInAppHandler, IterableAuthHandler, IterableInAppManager.Listener { public static final String NAME = "RNIterableAPI"; private static String TAG = "RNIterableAPIModule"; + private final ReactApplicationContext reactContext; private IterableInAppHandler.InAppResponse inAppResponse = IterableInAppHandler.InAppResponse.SHOW; @@ -64,6 +68,9 @@ public class RNIterableAPIModuleImpl { private final InboxSessionManager sessionManager = new InboxSessionManager(); + public RNIterableAPIModuleImpl(ReactApplicationContext reactContext) { + this.reactContext = reactContext; + } public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { IterableLogger.d(TAG, "initializeWithApiKey: " + apiKey); @@ -592,12 +599,6 @@ public void removeListeners(double count) { // Keep: Required for RN built in Event Emitter Calls. } - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Misc Bridge Functions - public void passAlongAuthToken(String authToken) { passedAuthToken = authToken; diff --git a/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java b/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java index f6edd7d7b..f885b133a 100644 --- a/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/reactnative/RNIterableAPIModule.java @@ -1,21 +1,29 @@ package com.iterable.reactnative; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import java.util.Map; import java.util.HashMap; public class RNIterableAPIModule extends NativeRNIterableAPISpec { private final ReactApplicationContext reactContext; + private static RNIterableAPIModuleImpl moduleImpl; RNIterableAPIModule(ReactApplicationContext context) { super(context); this.reactContext = context; + if (moduleImpl == null) { + moduleImpl = new RNIterableAPIModuleImpl(reactContext); + } } @Override @@ -26,206 +34,203 @@ public String getName() { @Override public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { - RNIterableAPIModuleImpl.initializeWithApiKey(apiKey, configReadableMap, version, promise); + moduleImpl.initializeWithApiKey(apiKey, configReadableMap, version, promise); } @Override public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, String apiEndPointOverride, String version, Promise promise) { - RNIterableAPIModuleImpl.initialize2WithApiKey(apiKey, configReadableMap, apiEndPointOverride, version, promise); + moduleImpl.initialize2WithApiKey(apiKey, configReadableMap, apiEndPointOverride, version, promise); } @Override public void setEmail(@Nullable String email, @Nullable String authToken) { - RNIterableAPIModuleImpl.setEmail(email, authToken); + moduleImpl.setEmail(email, authToken); } @Override public void updateEmail(String email, @Nullable String authToken) { - RNIterableAPIModuleImpl.updateEmail(email, authToken); + moduleImpl.updateEmail(email, authToken); } @Override public void getEmail(Promise promise) { - RNIterableAPIModuleImpl.getEmail(promise); + moduleImpl.getEmail(promise); } - @Override public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { - RNIterableAPIModuleImpl.sampleMethod(stringArgument, numberArgument, callback); + moduleImpl.sampleMethod(stringArgument, numberArgument, callback); } @Override public void setUserId(@Nullable String userId, @Nullable String authToken) { - RNIterableAPIModuleImpl.setUserId(userId, authToken); + moduleImpl.setUserId(userId, authToken); } @Override public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { - RNIterableAPIModuleImpl.updateUser(dataFields, mergeNestedObjects); + moduleImpl.updateUser(dataFields, mergeNestedObjects); } @Override public void getUserId(Promise promise) { - RNIterableAPIModuleImpl.getUserId(promise); + moduleImpl.getUserId(promise); } @Override public void trackEvent(String name, ReadableMap dataFields) { - RNIterableAPIModuleImpl.trackEvent(name, dataFields); + moduleImpl.trackEvent(name, dataFields); } @Override public void updateCart(ReadableArray items) { - RNIterableAPIModuleImpl.updateCart(items); + moduleImpl.updateCart(items); } @Override public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { - RNIterableAPIModuleImpl.trackPurchase(total, items, dataFields); + moduleImpl.trackPurchase(total, items, dataFields); } @Override public void trackPushOpenWithCampaignId(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { - RNIterableAPIModuleImpl.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields); + moduleImpl.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields); } @Override public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, double campaignId, double templateId) { - RNIterableAPIModuleImpl.updateSubscriptions(emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, templateId); + moduleImpl.updateSubscriptions(emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, templateId); } @Override public void showMessage(String messageId, boolean consume, final Promise promise) { - RNIterableAPIModuleImpl.showMessage(messageId, consume, promise); + moduleImpl.showMessage(messageId, consume, promise); } @Override public void setReadForMessage(String messageId, boolean read) { - RNIterableAPIModuleImpl.setReadForMessage(messageId, read); + moduleImpl.setReadForMessage(messageId, read); } @Override public void removeMessage(String messageId, double location, double deleteSource) { - RNIterableAPIModuleImpl.removeMessage(messageId, location, deleteSource); + moduleImpl.removeMessage(messageId, location, deleteSource); } @Override public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { - RNIterableAPIModuleImpl.getHtmlInAppContentForMessage(messageId, promise); + moduleImpl.getHtmlInAppContentForMessage(messageId, promise); } @Override public void getAttributionInfo(Promise promise) { - RNIterableAPIModuleImpl.getAttributionInfo(promise); + moduleImpl.getAttributionInfo(promise); } @Override public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { - RNIterableAPIModuleImpl.setAttributionInfo(attributionInfoReadableMap); + moduleImpl.setAttributionInfo(attributionInfoReadableMap); } @Override public void getLastPushPayload(Promise promise) { - RNIterableAPIModuleImpl.getLastPushPayload(promise); + moduleImpl.getLastPushPayload(promise); } @Override public void disableDeviceForCurrentUser() { - RNIterableAPIModuleImpl.disableDeviceForCurrentUser(); + moduleImpl.disableDeviceForCurrentUser(); } @Override public void handleAppLink(String uri, Promise promise) { - RNIterableAPIModuleImpl.handleAppLink(uri, promise); + moduleImpl.handleAppLink(uri, promise); } @Override public void trackInAppOpen(String messageId, double location) { - RNIterableAPIModuleImpl.trackInAppOpen(messageId, location); + moduleImpl.trackInAppOpen(messageId, location); } @Override public void trackInAppClick(String messageId, double location, String clickedUrl) { - RNIterableAPIModuleImpl.trackInAppClick(messageId, location, clickedUrl); + moduleImpl.trackInAppClick(messageId, location, clickedUrl); } @Override public void trackInAppClose(String messageId, double location, double source, @Nullable String clickedUrl) { - RNIterableAPIModuleImpl.trackInAppClose(messageId, location, source, clickedUrl); + moduleImpl.trackInAppClose(messageId, location, source, clickedUrl); } @Override public void inAppConsume(String messageId, double location, double source) { - RNIterableAPIModuleImpl.inAppConsume(messageId, location, source); + moduleImpl.inAppConsume(messageId, location, source); } @Override public void getInAppMessages(Promise promise) { - RNIterableAPIModuleImpl.getInAppMessages(promise); + moduleImpl.getInAppMessages(promise); } @Override public void getInboxMessages(Promise promise) { - RNIterableAPIModuleImpl.getInboxMessages(promise); + moduleImpl.getInboxMessages(promise); } @Override public void getUnreadInboxMessagesCount(Promise promise) { - RNIterableAPIModuleImpl.getUnreadInboxMessagesCount(promise); + moduleImpl.getUnreadInboxMessagesCount(promise); } @Override public void setInAppShowResponse(double number) { - RNIterableAPIModuleImpl.setInAppShowResponse(number); + moduleImpl.setInAppShowResponse(number); } @Override public void setAutoDisplayPaused(final boolean paused) { - RNIterableAPIModuleImpl.setAutoDisplayPaused(paused); + moduleImpl.setAutoDisplayPaused(paused); } @Override public void wakeApp() { - RNIterableAPIModuleImpl.wakeApp(); + moduleImpl.wakeApp(); } @Override public void startSession(ReadableArray visibleRows) { - RNIterableAPIModuleImpl.startSession(visibleRows); + moduleImpl.startSession(visibleRows); } @Override public void endSession() { - RNIterableAPIModuleImpl.endSession(); + moduleImpl.endSession(); } @Override public void updateVisibleRows(ReadableArray visibleRows) { - RNIterableAPIModuleImpl.updateVisibleRows(visibleRows); + moduleImpl.updateVisibleRows(visibleRows); } @Override public void addListener(String eventName) { - RNIterableAPIModuleImpl.addListener(eventName); + moduleImpl.addListener(eventName); } @Override public void removeListeners(double count) { - RNIterableAPIModuleImpl.removeListeners(count); + moduleImpl.removeListeners(count); } @Override public void passAlongAuthToken(String authToken) { - RNIterableAPIModuleImpl.passAlongAuthToken(authToken); + moduleImpl.passAlongAuthToken(authToken); } - @Override public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { - RNIterableAPIModuleImpl.sendEvent(eventName, eventData); + moduleImpl.sendEvent(eventName, eventData); } - @Override public void onInboxUpdated() { - RNIterableAPIModuleImpl.onInboxUpdated(); + moduleImpl.onInboxUpdated(); } } From 0a050d8c7f9b93ff3977860c317740cc723e92e4 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 21 Aug 2025 14:06:24 -0700 Subject: [PATCH 6/7] chore: remove RNIterableAPIModule.java as part of the transition to a new modular architecture --- RNIterableAPIModule.java | 677 --------------------------------------- 1 file changed, 677 deletions(-) delete mode 100644 RNIterableAPIModule.java diff --git a/RNIterableAPIModule.java b/RNIterableAPIModule.java deleted file mode 100644 index cb550d0fb..000000000 --- a/RNIterableAPIModule.java +++ /dev/null @@ -1,677 +0,0 @@ -package com.iterable.reactnative; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.modules.core.RCTNativeAppEventEmitter; -import com.iterable.iterableapi.InboxSessionManager; -import com.iterable.iterableapi.IterableAction; -import com.iterable.iterableapi.IterableActionContext; -import com.iterable.iterableapi.IterableApi; -import com.iterable.iterableapi.IterableAuthHandler; -import com.iterable.iterableapi.IterableConfig; -import com.iterable.iterableapi.IterableCustomActionHandler; -import com.iterable.iterableapi.IterableAttributionInfo; -import com.iterable.iterableapi.IterableHelper; -import com.iterable.iterableapi.IterableInAppCloseAction; -import com.iterable.iterableapi.IterableInAppHandler; -import com.iterable.iterableapi.IterableInAppLocation; -import com.iterable.iterableapi.IterableInAppManager; -import com.iterable.iterableapi.IterableInAppMessage; -import com.iterable.iterableapi.IterableInboxSession; -import com.iterable.iterableapi.IterableLogger; -import com.iterable.iterableapi.IterableUrlHandler; -import com.iterable.iterableapi.RNIterableInternal; -import com.iterable.reactnative.Serialization; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@ReactModule(name = RNIterableAPIModule.NAME) -public class RNIterableAPIModule extends NativeRNIterableAPISpec implements IterableUrlHandler, IterableCustomActionHandler, IterableInAppHandler, IterableAuthHandler, IterableInAppManager.Listener { - private final ReactApplicationContext reactContext; - private static String TAG = "RNIterableAPIModule"; - - private IterableInAppHandler.InAppResponse inAppResponse = IterableInAppHandler.InAppResponse.SHOW; - - //A CountDownLatch. This helps decide whether to handle the in-app in Default way by waiting for JS to respond in runtime. - private CountDownLatch jsCallBackLatch; - - private CountDownLatch authHandlerCallbackLatch; - private String passedAuthToken = null; - - private final InboxSessionManager sessionManager = new InboxSessionManager(); - public RNIterableAPIModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } - - @Override - public String getName() { - return NAME; - } - - public static final String NAME = "RNIterableAPI"; - - @ReactMethod - public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { - IterableLogger.d(TAG, "initializeWithApiKey: " + apiKey); - IterableConfig.Builder configBuilder = Serialization.getConfigFromReadableMap(configReadableMap); - - if (configReadableMap.hasKey("urlHandlerPresent") && configReadableMap.getBoolean("urlHandlerPresent") == true) { - configBuilder.setUrlHandler(this); - } - - if (configReadableMap.hasKey("customActionHandlerPresent") && configReadableMap.getBoolean("customActionHandlerPresent") == true) { - configBuilder.setCustomActionHandler(this); - } - - if (configReadableMap.hasKey("inAppHandlerPresent") && configReadableMap.getBoolean("inAppHandlerPresent") == true) { - configBuilder.setInAppHandler(this); - } - - if (configReadableMap.hasKey("authHandlerPresent") && configReadableMap.getBoolean("authHandlerPresent") == true) { - configBuilder.setAuthHandler(this); - } - - IterableApi.initialize(reactContext, apiKey, configBuilder.build()); - IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); - - IterableApi.getInstance().getInAppManager().addListener(this); - - // MOB-10421: Figure out what the error cases are and handle them appropriately - // This is just here to match the TS types and let the JS thread know when we are done initializing - promise.resolve(true); - } - - @ReactMethod - public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, String apiEndPointOverride, String version, Promise promise) { - IterableLogger.d(TAG, "initialize2WithApiKey: " + apiKey); - IterableConfig.Builder configBuilder = Serialization.getConfigFromReadableMap(configReadableMap); - - if (configReadableMap.hasKey("urlHandlerPresent") && configReadableMap.getBoolean("urlHandlerPresent") == true) { - configBuilder.setUrlHandler(this); - } - - if (configReadableMap.hasKey("customActionHandlerPresent") && configReadableMap.getBoolean("customActionHandlerPresent") == true) { - configBuilder.setCustomActionHandler(this); - } - - if (configReadableMap.hasKey("inAppHandlerPresent") && configReadableMap.getBoolean("inAppHandlerPresent") == true) { - configBuilder.setInAppHandler(this); - } - - if (configReadableMap.hasKey("authHandlerPresent") && configReadableMap.getBoolean("authHandlerPresent") == true) { - configBuilder.setAuthHandler(this); - } - - // Set the API endpoint override if provided - // if (apiEndPointOverride != null && !apiEndPointOverride.isEmpty()) { - // configBuilder.setApiEndpoint(apiEndPointOverride); - // } - - IterableApi.initialize(reactContext, apiKey, configBuilder.build()); - IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); - - IterableApi.getInstance().getInAppManager().addListener(this); - - // MOB-10421: Figure out what the error cases are and handle them appropriately - // This is just here to match the TS types and let the JS thread know when we are done initializing - promise.resolve(true); - } - - @ReactMethod - public void setEmail(@Nullable String email, @Nullable String authToken) { - IterableLogger.d(TAG, "setEmail: " + email + " authToken: " + authToken); - - IterableApi.getInstance().setEmail(email, authToken); - } - - @ReactMethod - public void updateEmail(String email, @Nullable String authToken) { - IterableLogger.d(TAG, "updateEmail: " + email + " authToken: " + authToken); - - IterableApi.getInstance().updateEmail(email, authToken); - } - - @ReactMethod - public void getEmail(Promise promise) { - promise.resolve(RNIterableInternal.getEmail()); - } - - @ReactMethod - public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { - // TODO: Implement some actually useful functionality - callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument); - } - - @ReactMethod - public void setUserId(@Nullable String userId, @Nullable String authToken) { - IterableLogger.d(TAG, "setUserId: " + userId + " authToken: " + authToken); - - IterableApi.getInstance().setUserId(userId, authToken); - } - - @ReactMethod - public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { - IterableLogger.v(TAG, "updateUser"); - IterableApi.getInstance().updateUser(optSerializedDataFields(dataFields), mergeNestedObjects); - } - - @ReactMethod - public void getUserId(Promise promise) { - promise.resolve(RNIterableInternal.getUserId()); - } - - @ReactMethod - public void trackEvent(String name, ReadableMap dataFields) { - IterableLogger.v(TAG, "trackEvent"); - IterableApi.getInstance().track(name, optSerializedDataFields(dataFields)); - } - - @ReactMethod - public void updateCart(ReadableArray items) { - IterableLogger.v(TAG, "updateCart"); - IterableApi.getInstance().updateCart(Serialization.commerceItemsFromReadableArray(items)); - } - - @ReactMethod - public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { - IterableLogger.v(TAG, "trackPurchase"); - IterableApi.getInstance().trackPurchase(total, Serialization.commerceItemsFromReadableArray(items), optSerializedDataFields(dataFields)); - } - - @ReactMethod - public void trackPushOpenWithCampaignId(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { - RNIterableInternal.trackPushOpenWithCampaignId((int) campaignId, templateId != null ? templateId.intValue() : null, messageId, optSerializedDataFields(dataFields)); - } - - @ReactMethod - public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, double campaignId, double templateId) { - IterableLogger.v(TAG, "updateSubscriptions"); - Integer finalCampaignId = null, finalTemplateId = null; - if (campaignId > 0) { - finalCampaignId = (int) campaignId; - } - if (templateId > 0) { - finalTemplateId = (int) templateId; - } - IterableApi.getInstance().updateSubscriptions(readableArrayToIntegerArray(emailListIds), - readableArrayToIntegerArray(unsubscribedChannelIds), - readableArrayToIntegerArray(unsubscribedMessageTypeIds), - readableArrayToIntegerArray(subscribedMessageTypeIds), - finalCampaignId, - finalTemplateId - ); - } - - @ReactMethod - public void showMessage(String messageId, boolean consume, final Promise promise) { - if (messageId == null || messageId == "") { - promise.reject("", "messageId is null or empty"); - return; - } - IterableApi.getInstance().getInAppManager().showMessage(RNIterableInternal.getMessageById(messageId), consume, new IterableHelper.IterableUrlCallback() { - @Override - public void execute(@Nullable Uri url) { - promise.resolve(url.toString()); - } - }); - } - - @ReactMethod - public void setReadForMessage(String messageId, boolean read) { - IterableLogger.v(TAG, "setReadForMessage"); - IterableApi.getInstance().getInAppManager().setRead(RNIterableInternal.getMessageById(messageId), read); - } - - @ReactMethod - public void removeMessage(String messageId, double location, double deleteSource) { - IterableLogger.v(TAG, "removeMessage"); - IterableApi.getInstance().getInAppManager().removeMessage(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger((int) deleteSource), Serialization.getIterableInAppLocationFromInteger((int) location)); - } - - @ReactMethod - public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { - IterableLogger.printInfo(); - IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); - - if (message == null) { - promise.reject("", "Could not find message with id: " + messageId); - return; - } - - JSONObject messageContent = Serialization.messageContentToJsonObject(message.getContent()); - - if (messageContent == null) { - promise.reject("", "messageContent is null for message id: " + messageId); - return; - } - - try { - promise.resolve(Serialization.convertJsonToMap(messageContent)); - } catch (JSONException e) { - promise.reject("", "Failed to convert JSONObject to ReadableMap"); - } - } - - @ReactMethod - public void getAttributionInfo(Promise promise) { - IterableLogger.printInfo(); - IterableAttributionInfo attributionInfo = IterableApi.getInstance().getAttributionInfo(); - if (attributionInfo != null) { - try { - promise.resolve(Serialization.convertJsonToMap(attributionInfo.toJSONObject())); - } catch (JSONException e) { - IterableLogger.e(TAG, "Failed converting attribution info to JSONObject"); - promise.reject("", "Failed to convert AttributionInfo to ReadableMap"); - } - } else { - promise.resolve(null); - } - } - - @ReactMethod - public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { - IterableLogger.printInfo(); - try { - JSONObject attributionInfoJson = Serialization.convertMapToJson(attributionInfoReadableMap); - IterableAttributionInfo attributionInfo = IterableAttributionInfo.fromJSONObject(attributionInfoJson); - RNIterableInternal.setAttributionInfo(attributionInfo); - } catch (JSONException e) { - IterableLogger.e(TAG, "Failed converting ReadableMap to JSON"); - } - } - - @ReactMethod - public void getLastPushPayload(Promise promise) { - Bundle payloadData = IterableApi.getInstance().getPayloadData(); - if (payloadData != null) { - promise.resolve(Arguments.fromBundle(IterableApi.getInstance().getPayloadData())); - } else { - IterableLogger.d(TAG, "No payload data found"); - promise.resolve(null); - } - } - - @ReactMethod - public void disableDeviceForCurrentUser() { - IterableLogger.v(TAG, "disableDevice"); - IterableApi.getInstance().disablePush(); - } - - @ReactMethod - public void handleAppLink(String uri, Promise promise) { - IterableLogger.printInfo(); - promise.resolve(IterableApi.getInstance().handleAppLink(uri)); - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Track APIs - @ReactMethod - public void trackInAppOpen(String messageId, double location) { - IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); - - if (message == null) { - IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); - return; - } - - IterableApi.getInstance().trackInAppOpen(message, Serialization.getIterableInAppLocationFromInteger((int) location)); - } - - @ReactMethod - public void trackInAppClick(String messageId, double location, String clickedUrl) { - IterableInAppMessage message = RNIterableInternal.getMessageById(messageId); - IterableInAppLocation inAppOpenLocation = Serialization.getIterableInAppLocationFromInteger((int) location); - - if (message == null) { - IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); - return; - } - - if (clickedUrl == null) { - IterableLogger.d(TAG, "clickedUrl is null"); - return; - } - - if (inAppOpenLocation == null) { - IterableLogger.d(TAG, "in-app open location is null"); - return; - } - - IterableApi.getInstance().trackInAppClick(message, clickedUrl, inAppOpenLocation); - } - - @ReactMethod - public void trackInAppClose(String messageId, double location, double source, @Nullable String clickedUrl) { - IterableInAppMessage inAppMessage = RNIterableInternal.getMessageById(messageId); - IterableInAppLocation inAppCloseLocation = Serialization.getIterableInAppLocationFromInteger((int) location); - IterableInAppCloseAction closeAction = Serialization.getIterableInAppCloseSourceFromInteger((int) source); - - if (inAppMessage == null) { - IterableLogger.d(TAG, "Failed to get in-app for message ID: " + messageId); - return; - } - - if (inAppCloseLocation == null) { - IterableLogger.d(TAG, "in-app close location is null"); - return; - } - - if (closeAction == null) { - IterableLogger.d(TAG, "in-app close action is null"); - return; - } - - IterableApi.getInstance().trackInAppClose(inAppMessage, clickedUrl, closeAction, inAppCloseLocation); - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region In App APIs - - @ReactMethod - public void inAppConsume(String messageId, double location, double source) { - if (messageId != null) { - IterableLogger.v(TAG, "inAppConsume"); - IterableApi.getInstance().inAppConsume(RNIterableInternal.getMessageById(messageId), Serialization.getIterableDeleteActionTypeFromInteger((int) source), Serialization.getIterableInAppLocationFromInteger((int) location)); - } - } - - @ReactMethod - public void getInAppMessages(Promise promise) { - IterableLogger.d(TAG, "getMessages"); - try { - JSONArray inAppMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getMessages()); - promise.resolve(Serialization.convertJsonToArray(inAppMessageJsonArray)); - } catch (JSONException e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); - } - } - - @ReactMethod - public void getInboxMessages(Promise promise) { - IterableLogger.d(TAG, "getInboxMessages"); - try { - JSONArray inboxMessageJsonArray = Serialization.serializeInAppMessages(IterableApi.getInstance().getInAppManager().getInboxMessages()); - promise.resolve(Serialization.convertJsonToArray(inboxMessageJsonArray)); - } catch (JSONException e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); - } - } - - @ReactMethod - public void getUnreadInboxMessagesCount(Promise promise) { - IterableLogger.d(TAG, "getUnreadInboxMessagesCount"); - try { - int unreadCount = IterableApi.getInstance().getInAppManager().getUnreadInboxMessagesCount(); - promise.resolve(unreadCount); - } catch (Exception e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - promise.reject("", "Failed to get unread inbox messages count with error " + e.getLocalizedMessage()); - } - } - - @ReactMethod - public void setInAppShowResponse(double number) { - IterableLogger.printInfo(); - inAppResponse = Serialization.getInAppResponse((int) number); - if (jsCallBackLatch != null) { - jsCallBackLatch.countDown(); - } - } - - @ReactMethod - public void setAutoDisplayPaused(final boolean paused) { - IterableLogger.printInfo(); - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - IterableApi.getInstance().getInAppManager().setAutoDisplayPaused(paused); - } - }); - } - - @ReactMethod - public void wakeApp() { - Intent launcherIntent = getMainActivityIntent(reactContext); - launcherIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - if (launcherIntent.resolveActivity(reactContext.getPackageManager()) != null) { - reactContext.startActivity(launcherIntent); - } - } - - public Intent getMainActivityIntent(Context context) { - Context appContext = context.getApplicationContext(); - PackageManager packageManager = appContext.getPackageManager(); - Intent intent = packageManager.getLaunchIntentForPackage(appContext.getPackageName()); - if (intent == null) { - intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setPackage(appContext.getPackageName()); - } - return intent; - } - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Inbox In-App Session Tracking APIs - - @ReactMethod - public void startSession(ReadableArray visibleRows) { - List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); - - sessionManager.startSession(serializedRows); - } - - @ReactMethod - public void endSession() { - sessionManager.endSession(); - } - - @ReactMethod - public void updateVisibleRows(ReadableArray visibleRows) { - List serializedRows = Serialization.impressionsFromReadableArray(visibleRows); - - sessionManager.updateVisibleRows(serializedRows); - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Private Serialization Functions - - private static Integer[] readableArrayToIntegerArray(ReadableArray array) { - if (array == null) { - return null; - } - Integer[] integers = new Integer[array.size()]; - for (int i = 0; i < array.size(); i++) { - integers[i] = array.getInt(i); - } - return integers; - } - - @Nullable - private static JSONObject optSerializedDataFields(ReadableMap dataFields) { - JSONObject dataFieldsJson = null; - - if (dataFields != null) { - try { - dataFieldsJson = Serialization.convertMapToJson(dataFields); - } catch (JSONException e) { - IterableLogger.d(TAG, "Failed to convert dataFields to JSON"); - } - } - - return dataFieldsJson; - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region IterableSDK callbacks - - @Override - public boolean handleIterableCustomAction(@NonNull IterableAction action, @NonNull IterableActionContext actionContext) { - IterableLogger.printInfo(); - JSONObject actionJson = Serialization.actionToJson(action); - JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); - JSONObject eventDataJson = new JSONObject(); - try { - eventDataJson.put("action", actionJson); - eventDataJson.put("context", actionContextJson); - WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); - sendEvent(EventName.handleCustomActionCalled.name(), eventData); - } catch (JSONException e) { - IterableLogger.e(TAG, "Failed handling custom action"); - } - // The Android SDK will not bring the app into focus is this is `true`. It still respects the `openApp` bool flag. - return false; - } - - @NonNull - @Override - public IterableInAppHandler.InAppResponse onNewInApp(@NonNull IterableInAppMessage message) { - IterableLogger.printInfo(); - - JSONObject messageJson = RNIterableInternal.getInAppMessageJson(message); - - try { - WritableMap eventData = Serialization.convertJsonToMap(messageJson); - jsCallBackLatch = new CountDownLatch(1); - sendEvent(EventName.handleInAppCalled.name(), eventData); - jsCallBackLatch.await(2, TimeUnit.SECONDS); - jsCallBackLatch = null; - return inAppResponse; - } catch (InterruptedException | JSONException e) { - IterableLogger.e(TAG, "new in-app module failed"); - return IterableInAppHandler.InAppResponse.SHOW; - } - } - - @Override - public boolean handleIterableURL(@NonNull Uri uri, @NonNull IterableActionContext actionContext) { - IterableLogger.printInfo(); - - JSONObject actionContextJson = Serialization.actionContextToJson(actionContext); - JSONObject eventDataJson = new JSONObject(); - - try { - eventDataJson.put("url", uri.toString()); - eventDataJson.put("context", actionContextJson); - WritableMap eventData = Serialization.convertJsonToMap(eventDataJson); - sendEvent(EventName.handleUrlCalled.name(), eventData); - } catch (JSONException e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - } - return true; - } - - @Override - public String onAuthTokenRequested() { - IterableLogger.printInfo(); - - try { - authHandlerCallbackLatch = new CountDownLatch(1); - sendEvent(EventName.handleAuthCalled.name(), null); - authHandlerCallbackLatch.await(30, TimeUnit.SECONDS); - authHandlerCallbackLatch = null; - return passedAuthToken; - } catch (InterruptedException e) { - IterableLogger.e(TAG, "auth handler module failed"); - return null; - } - } - - @Override - public void onTokenRegistrationSuccessful(String authToken) { - IterableLogger.v(TAG, "authToken successfully set"); - // MOB-10422: Pass successhandler to event listener - sendEvent(EventName.handleAuthSuccessCalled.name(), null); - } - - @Override - public void onTokenRegistrationFailed(Throwable object) { - IterableLogger.v(TAG, "Failed to set authToken"); - sendEvent(EventName.handleAuthFailureCalled.name(), null); - } - - @ReactMethod - public void addListener(String eventName) { - // Keep: Required for RN built in Event Emitter Calls. - } - - @ReactMethod - public void removeListeners(double count) { - // Keep: Required for RN built in Event Emitter Calls. - } - - // --------------------------------------------------------------------------------------- - // endregion - - // --------------------------------------------------------------------------------------- - // region Misc Bridge Functions - - @ReactMethod - public void passAlongAuthToken(String authToken) { - passedAuthToken = authToken; - - if (authHandlerCallbackLatch != null) { - authHandlerCallbackLatch.countDown(); - } - } - - public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, eventData); - } - - @Override - public void onInboxUpdated() { - sendEvent(EventName.receivedIterableInboxChanged.name(), null); - } -} - -enum EventName { - handleUrlCalled, - handleCustomActionCalled, - handleInAppCalled, - handleAuthCalled, - receivedIterableInboxChanged, - handleAuthSuccessCalled, - handleAuthFailureCalled -} From 3f3668e53718fbc8940008e8141022a1c3e4144e Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 21 Aug 2025 14:33:38 -0700 Subject: [PATCH 7/7] refactor: streamline RNIterableAPIModule by utilizing a singleton instance of RNIterableAPIModuleImpl for API method calls --- .../com/reactnative/RNIterableAPIModule.java | 92 ++++++++++--------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java b/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java index 5fd16b4b5..589b37960 100644 --- a/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/reactnative/RNIterableAPIModule.java @@ -1,20 +1,29 @@ package com.iterable.reactnative; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import java.util.Map; import java.util.HashMap; public class RNIterableAPIModule extends ReactContextBaseJavaModule { private final ReactApplicationContext reactContext; + private static RNIterableAPIModuleImpl moduleImpl; RNIterableAPIModule(ReactApplicationContext context) { super(context); this.reactContext = context; + if (moduleImpl == null) { + moduleImpl = new RNIterableAPIModuleImpl(reactContext); + } } @Override @@ -24,206 +33,205 @@ public String getName() { @ReactMethod public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, String version, Promise promise) { - RNIterableAPIModuleImpl.initializeWithApiKey(apiKey, configReadableMap, version, promise); + moduleImpl.initializeWithApiKey(apiKey, configReadableMap, version, promise); } @ReactMethod public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, String apiEndPointOverride, String version, Promise promise) { - RNIterableAPIModuleImpl.initialize2WithApiKey(apiKey, configReadableMap, apiEndPointOverride, version, promise); + moduleImpl.initialize2WithApiKey(apiKey, configReadableMap, apiEndPointOverride, version, promise); } @ReactMethod public void setEmail(@Nullable String email, @Nullable String authToken) { - RNIterableAPIModuleImpl.setEmail(email, authToken); + moduleImpl.setEmail(email, authToken); } @ReactMethod public void updateEmail(String email, @Nullable String authToken) { - RNIterableAPIModuleImpl.updateEmail(email, authToken); + moduleImpl.updateEmail(email, authToken); } @ReactMethod public void getEmail(Promise promise) { - RNIterableAPIModuleImpl.getEmail(promise); + moduleImpl.getEmail(promise); } @ReactMethod public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { - RNIterableAPIModuleImpl.sampleMethod(stringArgument, numberArgument, callback); + moduleImpl.sampleMethod(stringArgument, numberArgument, callback); } @ReactMethod public void setUserId(@Nullable String userId, @Nullable String authToken) { - RNIterableAPIModuleImpl.setUserId(userId, authToken); + moduleImpl.setUserId(userId, authToken); } @ReactMethod public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { - RNIterableAPIModuleImpl.updateUser(dataFields, mergeNestedObjects); + moduleImpl.updateUser(dataFields, mergeNestedObjects); } @ReactMethod public void getUserId(Promise promise) { - RNIterableAPIModuleImpl.getUserId(promise); + moduleImpl.getUserId(promise); } @ReactMethod public void trackEvent(String name, ReadableMap dataFields) { - RNIterableAPIModuleImpl.trackEvent(name, dataFields); + moduleImpl.trackEvent(name, dataFields); } @ReactMethod public void updateCart(ReadableArray items) { - RNIterableAPIModuleImpl.updateCart(items); + moduleImpl.updateCart(items); } @ReactMethod public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { - RNIterableAPIModuleImpl.trackPurchase(total, items, dataFields); + moduleImpl.trackPurchase(total, items, dataFields); } @ReactMethod public void trackPushOpenWithCampaignId(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { - RNIterableAPIModuleImpl.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields); + moduleImpl.trackPushOpenWithCampaignId(campaignId, templateId, messageId, appAlreadyRunning, dataFields); } @ReactMethod public void updateSubscriptions(ReadableArray emailListIds, ReadableArray unsubscribedChannelIds, ReadableArray unsubscribedMessageTypeIds, ReadableArray subscribedMessageTypeIds, double campaignId, double templateId) { - RNIterableAPIModuleImpl.updateSubscriptions(emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, templateId); + moduleImpl.updateSubscriptions(emailListIds, unsubscribedChannelIds, unsubscribedMessageTypeIds, subscribedMessageTypeIds, campaignId, templateId); } @ReactMethod public void showMessage(String messageId, boolean consume, final Promise promise) { - RNIterableAPIModuleImpl.showMessage(messageId, consume, promise); + moduleImpl.showMessage(messageId, consume, promise); } @ReactMethod public void setReadForMessage(String messageId, boolean read) { - RNIterableAPIModuleImpl.setReadForMessage(messageId, read); + moduleImpl.setReadForMessage(messageId, read); } @ReactMethod public void removeMessage(String messageId, double location, double deleteSource) { - RNIterableAPIModuleImpl.removeMessage(messageId, location, deleteSource); + moduleImpl.removeMessage(messageId, location, deleteSource); } @ReactMethod public void getHtmlInAppContentForMessage(String messageId, final Promise promise) { - RNIterableAPIModuleImpl.getHtmlInAppContentForMessage(messageId, promise); + moduleImpl.getHtmlInAppContentForMessage(messageId, promise); } @ReactMethod public void getAttributionInfo(Promise promise) { - RNIterableAPIModuleImpl.getAttributionInfo(promise); + moduleImpl.getAttributionInfo(promise); } @ReactMethod public void setAttributionInfo(ReadableMap attributionInfoReadableMap) { - RNIterableAPIModuleImpl.setAttributionInfo(attributionInfoReadableMap); + moduleImpl.setAttributionInfo(attributionInfoReadableMap); } @ReactMethod public void getLastPushPayload(Promise promise) { - RNIterableAPIModuleImpl.getLastPushPayload(promise); + moduleImpl.getLastPushPayload(promise); } @ReactMethod public void disableDeviceForCurrentUser() { - RNIterableAPIModuleImpl.disableDeviceForCurrentUser(); + moduleImpl.disableDeviceForCurrentUser(); } @ReactMethod public void handleAppLink(String uri, Promise promise) { - RNIterableAPIModuleImpl.handleAppLink(uri, promise); + moduleImpl.handleAppLink(uri, promise); } @ReactMethod public void trackInAppOpen(String messageId, double location) { - RNIterableAPIModuleImpl.trackInAppOpen(messageId, location); + moduleImpl.trackInAppOpen(messageId, location); } @ReactMethod public void trackInAppClick(String messageId, double location, String clickedUrl) { - RNIterableAPIModuleImpl.trackInAppClick(messageId, location, clickedUrl); + moduleImpl.trackInAppClick(messageId, location, clickedUrl); } @ReactMethod public void trackInAppClose(String messageId, double location, double source, @Nullable String clickedUrl) { - RNIterableAPIModuleImpl.trackInAppClose(messageId, location, source, clickedUrl); + moduleImpl.trackInAppClose(messageId, location, source, clickedUrl); } @ReactMethod public void inAppConsume(String messageId, double location, double source) { - RNIterableAPIModuleImpl.inAppConsume(messageId, location, source); + moduleImpl.inAppConsume(messageId, location, source); } @ReactMethod public void getInAppMessages(Promise promise) { - RNIterableAPIModuleImpl.getInAppMessages(promise); + moduleImpl.getInAppMessages(promise); } @ReactMethod public void getInboxMessages(Promise promise) { - RNIterableAPIModuleImpl.getInboxMessages(promise); + moduleImpl.getInboxMessages(promise); } @ReactMethod public void getUnreadInboxMessagesCount(Promise promise) { - RNIterableAPIModuleImpl.getUnreadInboxMessagesCount(promise); + moduleImpl.getUnreadInboxMessagesCount(promise); } @ReactMethod public void setInAppShowResponse(double number) { - RNIterableAPIModuleImpl.setInAppShowResponse(number); + moduleImpl.setInAppShowResponse(number); } @ReactMethod public void setAutoDisplayPaused(final boolean paused) { - RNIterableAPIModuleImpl.setAutoDisplayPaused(paused); + moduleImpl.setAutoDisplayPaused(paused); } @ReactMethod public void wakeApp() { - RNIterableAPIModuleImpl.wakeApp(); + moduleImpl.wakeApp(); } @ReactMethod public void startSession(ReadableArray visibleRows) { - RNIterableAPIModuleImpl.startSession(visibleRows); + moduleImpl.startSession(visibleRows); } @ReactMethod public void endSession() { - RNIterableAPIModuleImpl.endSession(); + moduleImpl.endSession(); } @ReactMethod public void updateVisibleRows(ReadableArray visibleRows) { - RNIterableAPIModuleImpl.updateVisibleRows(visibleRows); + moduleImpl.updateVisibleRows(visibleRows); } @ReactMethod public void addListener(String eventName) { - RNIterableAPIModuleImpl.addListener(eventName); + moduleImpl.addListener(eventName); } @ReactMethod public void removeListeners(double count) { - RNIterableAPIModuleImpl.removeListeners(count); + moduleImpl.removeListeners(count); } @ReactMethod public void passAlongAuthToken(String authToken) { - RNIterableAPIModuleImpl.passAlongAuthToken(authToken); + moduleImpl.passAlongAuthToken(authToken); } @ReactMethod public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { - RNIterableAPIModuleImpl.sendEvent(eventName, eventData); + moduleImpl.sendEvent(eventName, eventData); } - @Override public void onInboxUpdated() { - RNIterableAPIModuleImpl.onInboxUpdated(); + moduleImpl.onInboxUpdated(); } }