diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c7ff3c96..5671708ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: - name: Run unit tests run: yarn test --maxWorkers=2 --coverage - build-library: + build-library-old-arch: runs-on: ubuntu-latest steps: - name: Checkout @@ -47,13 +47,90 @@ jobs: - name: Setup uses: ./.github/actions/setup - - name: Build package - run: yarn prepare + - name: Switch to old architecture + run: yarn switch-arch old - build-android: + - name: Build package with old architecture + run: yarn build:old-arch + + build-library-new-arch: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + + - name: Switch to new architecture + run: yarn switch-arch new + + - name: Build package with new architecture + run: yarn build:new-arch + + build-android-old-arch: + runs-on: ubuntu-latest + env: + TURBO_CACHE_DIR: .turbo/android-old + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + + - name: Switch to old architecture + run: yarn switch-arch old + + - name: Cache turborepo for Android + uses: actions/cache@v3 + with: + path: ${{ env.TURBO_CACHE_DIR }} + key: ${{ runner.os }}-turborepo-android-old-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-turborepo-android-old- + + - name: Check turborepo cache for Android + run: | + TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") + + if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + echo "turbo_cache_hit=1" >> $GITHUB_ENV + fi + + - name: Install JDK + if: env.turbo_cache_hit != 1 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + + - name: Finalize Android SDK + if: env.turbo_cache_hit != 1 + run: | + /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" + + - name: Cache Gradle + if: env.turbo_cache_hit != 1 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches + key: ${{ runner.os }}-gradle-old-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle-old- + + - name: Build example for Android (Old Architecture) + env: + JAVA_OPTS: '-XX:MaxHeapSize=6g' + run: | + yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" + + build-android-new-arch: runs-on: ubuntu-latest env: - TURBO_CACHE_DIR: .turbo/android + TURBO_CACHE_DIR: .turbo/android-new steps: - name: Checkout uses: actions/checkout@v3 @@ -61,13 +138,16 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: Switch to new architecture + run: yarn switch-arch new + - name: Cache turborepo for Android uses: actions/cache@v3 with: path: ${{ env.TURBO_CACHE_DIR }} - key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-turborepo-android-new-${{ hashFiles('yarn.lock') }} restore-keys: | - ${{ runner.os }}-turborepo-android- + ${{ runner.os }}-turborepo-android-new- - name: Check turborepo cache for Android run: | @@ -96,20 +176,20 @@ jobs: path: | ~/.gradle/wrapper ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} + key: ${{ runner.os }}-gradle-new-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} restore-keys: | - ${{ runner.os }}-gradle- + ${{ runner.os }}-gradle-new- - - name: Build example for Android + - name: Build example for Android (New Architecture) env: - JAVA_OPTS: "-XX:MaxHeapSize=6g" + JAVA_OPTS: '-XX:MaxHeapSize=6g' run: | yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" - build-ios: + build-ios-old-arch: runs-on: macos-14 env: - TURBO_CACHE_DIR: .turbo/ios + TURBO_CACHE_DIR: .turbo/ios-old steps: - name: Checkout uses: actions/checkout@v3 @@ -117,13 +197,69 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: Switch to old architecture + run: yarn switch-arch old + + - name: Cache turborepo for iOS + uses: actions/cache@v3 + with: + path: ${{ env.TURBO_CACHE_DIR }} + key: ${{ runner.os }}-turborepo-ios-old-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-turborepo-ios-old- + + - name: Check turborepo cache for iOS + run: | + TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") + + if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + echo "turbo_cache_hit=1" >> $GITHUB_ENV + fi + + - name: Cache cocoapods + if: env.turbo_cache_hit != 1 + id: cocoapods-cache + uses: actions/cache@v3 + with: + path: | + **/ios/Pods + key: ${{ runner.os }}-cocoapods-old-${{ hashFiles('example/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-cocoapods-old- + + - name: Install cocoapods + if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true' + run: | + cd example/ios + pod install + env: + NO_FLIPPER: 1 + + - name: Build example for iOS (Old Architecture) + run: | + yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" + + build-ios-new-arch: + runs-on: macos-14 + env: + TURBO_CACHE_DIR: .turbo/ios-new + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup + + - name: Switch to new architecture + run: yarn switch-arch new + - name: Cache turborepo for iOS uses: actions/cache@v3 with: path: ${{ env.TURBO_CACHE_DIR }} - key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-turborepo-ios-new-${{ hashFiles('yarn.lock') }} restore-keys: | - ${{ runner.os }}-turborepo-ios- + ${{ runner.os }}-turborepo-ios-new- - name: Check turborepo cache for iOS run: | @@ -140,9 +276,9 @@ jobs: with: path: | **/ios/Pods - key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }} + key: ${{ runner.os }}-cocoapods-new-${{ hashFiles('example/ios/Podfile.lock') }} restore-keys: | - ${{ runner.os }}-cocoapods- + ${{ runner.os }}-cocoapods-new- - name: Install cocoapods if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true' @@ -152,6 +288,6 @@ jobs: env: NO_FLIPPER: 1 - - name: Build example for iOS + - name: Build example for iOS (New Architecture) run: | yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" diff --git a/Iterable-React-Native-SDK.podspec b/Iterable-React-Native-SDK.podspec index 14c6d5966..eb342e8a5 100644 --- a/Iterable-React-Native-SDK.podspec +++ b/Iterable-React-Native-SDK.podspec @@ -16,6 +16,8 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,swift}" + s.dependency "Iterable-iOS-SDK", "6.5.4" + # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. if respond_to?(:install_modules_dependencies, true) @@ -36,9 +38,8 @@ Pod::Spec.new do |s| s.dependency "RCTRequired" s.dependency "RCTTypeSafety" s.dependency "ReactCommon/turbomodule/core" + s.dependency "React-RCTFabric" # This is for Fabric support end end - s.dependency "Iterable-iOS-SDK", "6.5.4" - end diff --git a/README.md b/README.md index a0ccacaef..f1fe46cf8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,69 @@ Iterable's React Native SDK relies on: ## Architecture Support -**Important**: Iterable's React Native SDK currently does not support [React Native's New Architecture](https://reactnative.dev/architecture/landing-page) (Fabric, TurboModules, and Codegen). The SDK is designed to work with the **legacy architecture only**. If you're using the New Architecture in your React Native application, you may encounter compatibility issues. +Iterable's React Native SDK supports both the legacy architecture and React Native's New Architecture (Fabric, TurboModules, and Codegen). The SDK is designed to work seamlessly with both architectures, providing flexibility for your React Native application. + +### New Architecture Support + +Starting from version 2.0.0-beta.1, the SDK supports React Native's New Architecture. To use the SDK with the New Architecture: + +1. Enable the New Architecture in your project: + ```bash + yarn switch-arch new + ``` + +2. Clean and rebuild your project: + ```bash + yarn clean + yarn build:new-arch + ``` + +3. For Android, ensure your `android/gradle.properties` has: + ```properties + newArchEnabled=true + ``` + +4. For iOS, the New Architecture will be automatically enabled when building with the new architecture flag. + +### Legacy Architecture Support + +To use the SDK with the legacy architecture: + +1. Ensure you're using the legacy architecture: + ```bash + yarn switch-arch old + ``` + +2. Clean and rebuild your project: + ```bash + yarn clean + yarn build:old-arch + ``` + +3. For Android, ensure your `android/gradle.properties` has: + ```properties + newArchEnabled=false + ``` + +### Building for Both Architectures + +If you need to support both architectures in your project: + +1. Build for both architectures: + ```bash + yarn build:all + ``` + +This will create builds for both the old and new architecture, allowing you to test and deploy your app with either architecture. + +### CI/CD Integration + +The SDK includes CI/CD configurations that test both architectures. The CI pipeline will: +- Build the library with both old and new architecture +- Build Android example app with both architectures +- Build iOS example app with both architectures + +This ensures that your SDK works correctly with both architectures in your CI/CD pipeline. ## Installation diff --git a/android/build.gradle b/android/build.gradle index fd26bc186..5d7aae957 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,4 +1,13 @@ buildscript { + ext { + buildToolsVersion = "33.0.0" + minSdkVersion = 21 + compileSdkVersion = 33 + targetSdkVersion = 33 + ndkVersion = "23.1.7779620" + kotlinVersion = "1.8.0" + gradleVersion = "7.5.1" + } // 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"] @@ -8,9 +17,9 @@ buildscript { } dependencies { - classpath "com.android.tools.build:gradle:7.2.1" - // noinspection DifferentKotlinGradleVersion - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath("com.android.tools.build:gradle:${gradleVersion}") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") } } @@ -23,12 +32,13 @@ def isNewArchitectureEnabled() { return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" } +def isFabricEnabled() { + return rootProject.hasProperty("fabricEnabled") && rootProject.getProperty("fabricEnabled") == "true" +} + apply plugin: "com.android.library" apply plugin: "kotlin-android" - -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} +apply plugin: "com.facebook.react" def getExtOrDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["RNIterable_" + name] @@ -58,28 +68,69 @@ android { } } - compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { - minSdkVersion getExtOrIntegerDefault("minSdkVersion") - targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") - + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + + if (isNewArchitectureEnabled()) { + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true") + if (isFabricEnabled()) { + buildConfigField("boolean", "IS_FABRIC_ENABLED", "true") + } else { + buildConfigField("boolean", "IS_FABRIC_ENABLED", "false") + } + } else { + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "false") + buildConfigField("boolean", "IS_FABRIC_ENABLED", "false") + } } buildTypes { release { minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { - disable "GradleCompatible" + abortOnError false } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + buildFeatures { + buildConfig true + } + + // Configure build variants for old and new architecture + flavorDimensions "architecture" + productFlavors { + oldArch { + dimension "architecture" + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "false" + } + newArch { + dimension "architecture" + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true" + } + } + + // Configure source sets for each architecture + sourceSets { + oldArch { + java.srcDirs = ['src/main/java', 'src/oldArch/java'] + } + newArch { + java.srcDirs = ['src/main/java', 'src/newArch/java'] + } + } } repositories { @@ -94,8 +145,59 @@ dependencies { // 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 + // api project(":iterableapi") // links to local android SDK repo rather than by release + + // Architecture-specific dependencies + oldArchImplementation "com.facebook.react:react-native-gradle-plugin" + newArchImplementation "com.facebook.react:react-native-gradle-plugin" + newArchImplementation "com.facebook.react:react-native-turbomodule-core" + newArchImplementation "com.facebook.react:react-native-turbomodule-interface" +} + +// Configure React Native +react { + // Enable Hermes + hermesCommand = "../../node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc" + // Enable new architecture + newArchEnabled = true +} + +// Configure publishing +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + groupId = 'com.iterable' + artifactId = 'react-native-sdk' + version = '2.0.0' + } + } + } +} + +allprojects { + repositories { + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url("$rootDir/../node_modules/react-native/android") + } + maven { + // Android JSC is installed from npm + url("$rootDir/../node_modules/jsc-android/dist") + } + mavenCentral { + // We don't want to fetch react-native from Maven Central as there are + // older versions over there. + content { + excludeGroup "com.facebook.react" + } + } + google() + maven { url 'https://www.jitpack.io' } + } } diff --git a/android/gradle.properties b/android/gradle.properties index a46c61ab2..b57aface4 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -4,4 +4,40 @@ RNIterable_targetSdkVersion=31 RNIterable_compileSdkVersion=31 RNIterable_ndkversion=21.4.7075529 android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true + +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + +# Version of flipper SDK to use with React Native +FLIPPER_VERSION=0.182.0 + +# Use this property to specify which architecture you want to build. +# Options are "old" or "new" +newArchEnabled=false + +# Use this property to enable the Hermes VM. +# If set to false, you will be using JSC instead. +hermesEnabled=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7cf08140d..7b8745f75 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists diff --git a/android/src/main/java/com/iterable/reactnative/IterableCustomActionHandler.java b/android/src/main/java/com/iterable/reactnative/IterableCustomActionHandler.java new file mode 100644 index 000000000..5ad82c4be --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/IterableCustomActionHandler.java @@ -0,0 +1,33 @@ +package com.iterable.reactnative; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.iterable.iterableapi.IterableAction; +import com.iterable.iterableapi.IterableActionContext; +import com.iterable.iterableapi.IterableCustomActionHandler; + +public class IterableCustomActionHandler implements IterableCustomActionHandler { + private final ReactApplicationContext reactContext; + private final ReadableMap config; + + public IterableCustomActionHandler(ReadableMap config) { + this.config = config; + this.reactContext = null; + } + + public IterableCustomActionHandler(ReadableMap config, ReactApplicationContext reactContext) { + this.config = config; + this.reactContext = reactContext; + } + + @Override + public boolean handleIterableCustomAction(IterableAction action, IterableActionContext actionContext) { + if (reactContext != null) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("customActionHandler", action.getType()); + } + return config.getBoolean("shouldHandleCustomAction"); + } +} diff --git a/android/src/main/java/com/iterable/reactnative/IterableInAppHandler.java b/android/src/main/java/com/iterable/reactnative/IterableInAppHandler.java new file mode 100644 index 000000000..16d5c445b --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/IterableInAppHandler.java @@ -0,0 +1,59 @@ +package com.iterable.reactnative; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.iterable.iterableapi.IterableInAppMessage; +import com.iterable.iterableapi.IterableInAppHandler; + +public class IterableInAppHandler implements IterableInAppHandler { + private final ReactApplicationContext reactContext; + private final ReadableMap config; + + public IterableInAppHandler(ReadableMap config) { + this.config = config; + this.reactContext = null; + } + + public IterableInAppHandler(ReadableMap config, ReactApplicationContext reactContext) { + this.config = config; + this.reactContext = reactContext; + } + + @Override + public boolean onInAppReceived(IterableInAppMessage message) { + if (reactContext != null) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("inAppReceived", message.getMessageId()); + } + return config.getBoolean("shouldShowInApp"); + } + + @Override + public void onInAppDisplayed(IterableInAppMessage message) { + if (reactContext != null) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("inAppDisplayed", message.getMessageId()); + } + } + + @Override + public void onInAppDismissed(IterableInAppMessage message) { + if (reactContext != null) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("inAppDismissed", message.getMessageId()); + } + } + + @Override + public void onInAppClicked(IterableInAppMessage message, String clickedUrl) { + if (reactContext != null) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("inAppClicked", message.getMessageId()); + } + } +} diff --git a/android/src/main/java/com/iterable/reactnative/IterableUrlHandler.java b/android/src/main/java/com/iterable/reactnative/IterableUrlHandler.java new file mode 100644 index 000000000..d43abfc97 --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/IterableUrlHandler.java @@ -0,0 +1,32 @@ +package com.iterable.reactnative; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.iterable.iterableapi.IterableUrlCallback; + +public class IterableUrlHandler implements IterableUrlCallback { + private final ReactApplicationContext reactContext; + private final ReadableMap config; + + public IterableUrlHandler(ReadableMap config) { + this.config = config; + this.reactContext = null; + } + + public IterableUrlHandler(ReadableMap config, ReactApplicationContext reactContext) { + this.config = config; + this.reactContext = reactContext; + } + + @Override + public boolean handleIterableURL(String url) { + if (reactContext != null) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("urlHandler", url); + } + return config.getBoolean("shouldOpenInNewWindow"); + } +} diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIImpl.java new file mode 100644 index 000000000..4ef8b5da2 --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIImpl.java @@ -0,0 +1,374 @@ +package com.iterable.reactnative; + +import androidx.annotation.NonNull; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import com.iterable.reactnative.bridge.IterableBridge; +import com.iterable.reactnative.bridge.IterableConfig; +import com.iterable.reactnative.bridge.IterableInAppMessage; +import com.iterable.reactnative.bridge.IterableInAppLocation; +import com.iterable.reactnative.bridge.IterableInAppCloseSource; +import com.iterable.reactnative.bridge.IterableInAppDeleteSource; +import com.iterable.reactnative.bridge.IterableCommerceItem; +import com.iterable.reactnative.bridge.IterableAttributionInfo; +import java.util.ArrayList; +import java.util.List; + +public class RNIterableAPIImpl implements RNIterableAPITurboModule { + private final ReactApplicationContext reactContext; + private final IterableBridge iterableBridge; + + public RNIterableAPIImpl(ReactApplicationContext reactContext) { + this.reactContext = reactContext; + this.iterableBridge = new IterableBridge(reactContext); + } + + @Override + public void initializeWithApiKey(String apiKey, ReadableMap config, String version, Promise promise) { + try { + IterableConfig iterableConfig = getIterableConfigFromMap(config); + iterableBridge.initialize(apiKey, iterableConfig); + iterableBridge.setDeviceAttribute("reactNativeSDKVersion", version); + promise.resolve(true); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void initialize2WithApiKey(String apiKey, ReadableMap config, String apiEndPoint, String version, Promise promise) { + try { + IterableConfig iterableConfig = getIterableConfigFromMap(config); + iterableBridge.initialize(apiKey, iterableConfig, apiEndPoint); + iterableBridge.setDeviceAttribute("reactNativeSDKVersion", version); + promise.resolve(true); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void setEmail(String email, String authToken) { + iterableBridge.setEmail(email, authToken); + } + + @Override + public void getEmail(Promise promise) { + try { + String email = iterableBridge.getEmail(); + promise.resolve(email); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void setUserId(String userId, String authToken) { + iterableBridge.setUserId(userId, authToken); + } + + @Override + public void getUserId(Promise promise) { + try { + String userId = iterableBridge.getUserId(); + promise.resolve(userId); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void disableDeviceForCurrentUser() { + iterableBridge.disableDeviceForCurrentUser(); + } + + @Override + public void getLastPushPayload(Promise promise) { + try { + WritableMap payload = iterableBridge.getLastPushPayload(); + promise.resolve(payload); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void trackPushOpen(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { + iterableBridge.trackPushOpen(messageId, (int) campaignId, templateId, appAlreadyRunning, dataFields); + } + + @Override + public void getInAppMessages(Promise promise) { + try { + List messages = iterableBridge.getInAppMessages(); + WritableArray array = Arguments.createArray(); + for (IterableInAppMessage message : messages) { + array.pushMap(messageToMap(message)); + } + promise.resolve(array); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void getInboxMessages(Promise promise) { + try { + List messages = iterableBridge.getInboxMessages(); + WritableArray array = Arguments.createArray(); + for (IterableInAppMessage message : messages) { + array.pushMap(messageToMap(message)); + } + promise.resolve(array); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void getUnreadInboxMessagesCount(Promise promise) { + try { + int count = iterableBridge.getUnreadInboxMessagesCount(); + promise.resolve(count); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void showMessage(String messageId, boolean consume, Promise promise) { + try { + WritableMap data = iterableBridge.showMessage(messageId, consume); + promise.resolve(data); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void setReadForMessage(String messageId, boolean read) { + iterableBridge.setReadForMessage(messageId, read); + } + + @Override + public void removeMessage(String messageId, double location, double deleteSource) { + iterableBridge.removeMessage(messageId, + IterableInAppLocation.fromInt((int) location), + IterableInAppDeleteSource.fromInt((int) deleteSource)); + } + + @Override + public void trackEvent(String name, ReadableMap dataFields) { + iterableBridge.trackEvent(name, dataFields); + } + + @Override + public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { + iterableBridge.updateUser(dataFields, mergeNestedObjects); + } + + @Override + public void updateEmail(String email, String authToken) { + iterableBridge.updateEmail(email, authToken); + } + + @Override + public void getAttributionInfo(Promise promise) { + try { + IterableAttributionInfo info = iterableBridge.getAttributionInfo(); + promise.resolve(attributionInfoToMap(info)); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + @Override + public void setAttributionInfo(ReadableMap attributionInfo) { + iterableBridge.setAttributionInfo(mapToAttributionInfo(attributionInfo)); + } + + @Override + public void updateCart(ReadableArray items) { + List commerceItems = new ArrayList<>(); + for (int i = 0; i < items.size(); i++) { + ReadableMap item = items.getMap(i); + commerceItems.add(new IterableCommerceItem( + item.getString("id"), + item.getString("name"), + item.getDouble("price"), + item.getInt("quantity") + )); + } + iterableBridge.updateCart(commerceItems); + } + + @Override + public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { + List commerceItems = new ArrayList<>(); + for (int i = 0; i < items.size(); i++) { + ReadableMap item = items.getMap(i); + commerceItems.add(new IterableCommerceItem( + item.getString("id"), + item.getString("name"), + item.getDouble("price"), + item.getInt("quantity") + )); + } + iterableBridge.trackPurchase(total, commerceItems, dataFields); + } + + @Override + public void trackInAppOpen(String messageId, double location) { + iterableBridge.trackInAppOpen(messageId, IterableInAppLocation.fromInt((int) location)); + } + + @Override + public void trackInAppClick(String messageId, double location, String clickedUrl) { + iterableBridge.trackInAppClick(messageId, IterableInAppLocation.fromInt((int) location), clickedUrl); + } + + @Override + public void trackInAppClose(String messageId, double location, double source, String clickedUrl) { + iterableBridge.trackInAppClose(messageId, + IterableInAppLocation.fromInt((int) location), + IterableInAppCloseSource.fromInt((int) source), + clickedUrl); + } + + @Override + public void inAppConsume(String messageId, double location, double source) { + iterableBridge.removeMessage(messageId, + IterableInAppLocation.fromInt((int) location), + IterableInAppDeleteSource.fromInt((int) source)); + } + + @Override + public void handleAppLink(String appLink, Promise promise) { + try { + WritableMap data = iterableBridge.handleAppLink(appLink); + promise.resolve(data); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + + private IterableConfig getIterableConfigFromMap(ReadableMap config) { + IterableConfig.Builder builder = new IterableConfig.Builder(); + + if (config.hasKey("pushIntegrationName")) { + builder.setPushIntegrationName(config.getString("pushIntegrationName")); + } + + if (config.hasKey("autoPushRegistration")) { + builder.setAutoPushRegistration(config.getBoolean("autoPushRegistration")); + } + + if (config.hasKey("logLevel")) { + builder.setLogLevel(config.getInt("logLevel")); + } + + if (config.hasKey("inAppDisplayInterval")) { + builder.setInAppDisplayInterval(config.getDouble("inAppDisplayInterval")); + } + + if (config.hasKey("urlHandler")) { + ReadableMap urlHandler = config.getMap("urlHandler"); + builder.setUrlHandler(url -> { + sendEvent("urlHandler", url); + return urlHandler.getBoolean("shouldOpenInNewWindow"); + }); + } + + if (config.hasKey("customActionHandler")) { + ReadableMap customActionHandler = config.getMap("customActionHandler"); + builder.setCustomActionHandler((action, context) -> { + sendEvent("customActionHandler", action.getType()); + return customActionHandler.getBoolean("shouldHandleCustomAction"); + }); + } + + if (config.hasKey("inAppHandler")) { + ReadableMap inAppHandler = config.getMap("inAppHandler"); + builder.setInAppHandler(new IterableConfig.InAppHandler() { + @Override + public boolean onInAppReceived(IterableInAppMessage message) { + sendEvent("inAppReceived", message.getMessageId()); + return inAppHandler.getBoolean("shouldShowInApp"); + } + + @Override + public void onInAppDisplayed(IterableInAppMessage message) { + sendEvent("inAppDisplayed", message.getMessageId()); + } + + @Override + public void onInAppDismissed(IterableInAppMessage message) { + sendEvent("inAppDismissed", message.getMessageId()); + } + + @Override + public void onInAppClicked(IterableInAppMessage message, String clickedUrl) { + sendEvent("inAppClicked", message.getMessageId()); + } + }); + } + + return builder.build(); + } + + private WritableMap messageToMap(IterableInAppMessage message) { + WritableMap map = Arguments.createMap(); + map.putString("messageId", message.getMessageId()); + map.putInt("campaignId", message.getCampaignId()); + + WritableMap content = Arguments.createMap(); + content.putString("html", message.getContent().getHtml()); + + WritableMap edgeInsets = Arguments.createMap(); + edgeInsets.putDouble("top", message.getContent().getPadding().top); + edgeInsets.putDouble("left", message.getContent().getPadding().left); + edgeInsets.putDouble("bottom", message.getContent().getPadding().bottom); + edgeInsets.putDouble("right", message.getContent().getPadding().right); + content.putMap("edgeInsets", edgeInsets); + + map.putMap("content", content); + return map; + } + + private WritableMap attributionInfoToMap(IterableAttributionInfo info) { + if (info == null) { + return null; + } + + WritableMap map = Arguments.createMap(); + map.putString("campaignId", info.getCampaignId()); + map.putString("templateId", info.getTemplateId()); + map.putString("messageId", info.getMessageId()); + return map; + } + + private IterableAttributionInfo mapToAttributionInfo(ReadableMap map) { + if (map == null) { + return null; + } + + return new IterableAttributionInfo( + map.getString("campaignId"), + map.getString("templateId"), + map.getString("messageId") + ); + } + + private void sendEvent(String eventName, String body) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, body); + } +} diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java index 9b0bed33a..75e46b651 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java @@ -48,6 +48,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import com.facebook.react.module.annotations.ReactModule; + +@ReactModule(name = RNIterableAPISpec.NAME) public class RNIterableAPIModule extends ReactContextBaseJavaModule implements IterableUrlHandler, IterableCustomActionHandler, IterableInAppHandler, IterableAuthHandler, IterableInAppManager.Listener { private final ReactApplicationContext reactContext; private static String TAG = "RNIterableAPIModule"; @@ -62,258 +65,154 @@ public class RNIterableAPIModule extends ReactContextBaseJavaModule implements I private final InboxSessionManager sessionManager = new InboxSessionManager(); + private final RNIterableAPITurboModule turboModule; + public RNIterableAPIModule(ReactApplicationContext reactContext) { super(reactContext); this.reactContext = reactContext; + this.turboModule = new RNIterableAPIImpl(reactContext); } - // --------------------------------------------------------------------------------------- - // region IterableSDK calls - @Override + @NonNull public String getName() { - return "RNIterableAPI"; + return RNIterableAPISpec.NAME; } @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); + public void initializeWithApiKey(String apiKey, ReadableMap config, String version, Promise promise) { + turboModule.initializeWithApiKey(apiKey, config, version, promise); } @ReactMethod - public void setEmail(@Nullable String email) { - IterableLogger.d(TAG, "setEmail: " + email); - - IterableApi.getInstance().setEmail(email); + public void initialize2WithApiKey(String apiKey, ReadableMap config, String apiEndPoint, String version, Promise promise) { + turboModule.initialize2WithApiKey(apiKey, config, apiEndPoint, version, promise); } @ReactMethod - public void setEmail(@Nullable String email, @Nullable String authToken) { - IterableLogger.d(TAG, "setEmail: " + email + " authToken: " + authToken); - - IterableApi.getInstance().setEmail(email, authToken); + public void setEmail(String email, String authToken) { + turboModule.setEmail(email, authToken); } @ReactMethod - public void updateEmail(String email) { - IterableLogger.d(TAG, "updateEmail: " + email); - - IterableApi.getInstance().updateEmail(email); + public void getEmail(Promise promise) { + turboModule.getEmail(promise); } @ReactMethod - public void updateEmail(String email, @Nullable String authToken) { - IterableLogger.d(TAG, "updateEmail: " + email + " authToken: " + authToken); - - IterableApi.getInstance().updateEmail(email, authToken); + public void setUserId(String userId, String authToken) { + turboModule.setUserId(userId, authToken); } @ReactMethod - public void getEmail(Promise promise) { - promise.resolve(RNIterableInternal.getEmail()); + public void getUserId(Promise promise) { + turboModule.getUserId(promise); } @ReactMethod - public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { - // TODO: Implement some actually useful functionality - callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument); + public void disableDeviceForCurrentUser() { + turboModule.disableDeviceForCurrentUser(); } @ReactMethod - public void setUserId(@Nullable String userId) { - IterableLogger.d(TAG, "setUserId: " + userId); - IterableApi.getInstance().setUserId(userId); + public void getLastPushPayload(Promise promise) { + turboModule.getLastPushPayload(promise); } @ReactMethod - public void setUserId(@Nullable String userId, @Nullable String authToken) { - IterableLogger.d(TAG, "setUserId: " + userId + " authToken: " + authToken); - - IterableApi.getInstance().setUserId(userId, authToken); + public void trackPushOpen(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { + turboModule.trackPushOpen(campaignId, templateId, messageId, appAlreadyRunning, dataFields); } @ReactMethod - public void updateUser(ReadableMap dataFields, Boolean mergeNestedObjects) { - IterableLogger.v(TAG, "updateUser"); - IterableApi.getInstance().updateUser(optSerializedDataFields(dataFields), mergeNestedObjects); + public void getInAppMessages(Promise promise) { + turboModule.getInAppMessages(promise); } @ReactMethod - public void getUserId(Promise promise) { - promise.resolve(RNIterableInternal.getUserId()); + public void getInboxMessages(Promise promise) { + turboModule.getInboxMessages(promise); } @ReactMethod - public void trackEvent(String name, ReadableMap dataFields) { - IterableLogger.v(TAG, "trackEvent"); - IterableApi.getInstance().track(name, optSerializedDataFields(dataFields)); + public void getUnreadInboxMessagesCount(Promise promise) { + turboModule.getUnreadInboxMessagesCount(promise); } @ReactMethod - public void updateCart(ReadableArray items) { - IterableLogger.v(TAG, "updateCart"); - IterableApi.getInstance().updateCart(Serialization.commerceItemsFromReadableArray(items)); + public void showMessage(String messageId, boolean consume, Promise promise) { + turboModule.showMessage(messageId, consume, promise); } @ReactMethod - public void trackPurchase(Double total, ReadableArray items, ReadableMap dataFields) { - IterableLogger.v(TAG, "trackPurchase"); - IterableApi.getInstance().trackPurchase(total, Serialization.commerceItemsFromReadableArray(items), optSerializedDataFields(dataFields)); + public void setReadForMessage(String messageId, boolean read) { + turboModule.setReadForMessage(messageId, read); } @ReactMethod - public void trackPushOpenWithCampaignId(Integer campaignId, Integer templateId, String messageId, Boolean appAlreadyRunning, ReadableMap dataFields) { - RNIterableInternal.trackPushOpenWithCampaignId(campaignId, templateId, messageId, optSerializedDataFields(dataFields)); + public void removeMessage(String messageId, double location, double deleteSource) { + turboModule.removeMessage(messageId, location, deleteSource); } @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 - ); + public void trackEvent(String name, ReadableMap dataFields) { + turboModule.trackEvent(name, dataFields); } @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()); - } - }); + public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { + turboModule.updateUser(dataFields, mergeNestedObjects); } @ReactMethod - public void setReadForMessage(String messageId, boolean read) { - IterableLogger.v(TAG, "setReadForMessage"); - IterableApi.getInstance().getInAppManager().setRead(RNIterableInternal.getMessageById(messageId), read); + public void updateEmail(String email, String authToken) { + turboModule.updateEmail(email, authToken); } @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)); + public void getAttributionInfo(Promise promise) { + turboModule.getAttributionInfo(promise); } @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; - } + public void setAttributionInfo(ReadableMap attributionInfo) { + turboModule.setAttributionInfo(attributionInfo); + } - try { - promise.resolve(Serialization.convertJsonToMap(messageContent)); - } catch (JSONException e) { - promise.reject("", "Failed to convert JSONObject to ReadableMap"); - } + @ReactMethod + public void updateCart(ReadableArray items) { + turboModule.updateCart(items); } @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); - } + public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { + turboModule.trackPurchase(total, items, dataFields); } @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"); - } + public void trackInAppOpen(String messageId, double location) { + turboModule.trackInAppOpen(messageId, location); } @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); - } + public void trackInAppClick(String messageId, double location, String clickedUrl) { + turboModule.trackInAppClick(messageId, location, clickedUrl); } @ReactMethod - public void disableDeviceForCurrentUser() { - IterableLogger.v(TAG, "disableDevice"); - IterableApi.getInstance().disablePush(); + public void trackInAppClose(String messageId, double location, double source, String clickedUrl) { + turboModule.trackInAppClose(messageId, location, source, clickedUrl); } @ReactMethod - public void handleAppLink(String uri, Promise promise) { - IterableLogger.printInfo(); - promise.resolve(IterableApi.getInstance().handleAppLink(uri)); + public void inAppConsume(String messageId, double location, double source) { + turboModule.inAppConsume(messageId, location, source); } - // --------------------------------------------------------------------------------------- - // endregion + @ReactMethod + public void handleAppLink(String appLink, Promise promise) { + turboModule.handleAppLink(appLink, promise); + } // --------------------------------------------------------------------------------------- // region Track APIs 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..eeb36b037 --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -0,0 +1,214 @@ +package com.iterable.reactnative; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import com.iterable.iterableapi.IterableApi; +import com.iterable.iterableapi.IterableConfig; +import com.iterable.iterableapi.IterableInAppManager; +import com.iterable.iterableapi.IterableLogger; + +public class RNIterableAPIModuleImpl extends RNIterableAPIModuleSpec { + private static final String TAG = "RNIterableAPIModuleImpl"; + + public RNIterableAPIModuleImpl(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public void initializeWithApiKey(String apiKey, ReadableMap config, String version, Promise promise) { + try { + IterableConfig iterableConfig = Serialization.getIterableConfigFromReadableMap(config); + IterableApi.initialize(getReactApplicationContext(), apiKey, iterableConfig); + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); + promise.resolve(true); + } catch (Exception e) { + promise.reject("", e.getMessage()); + } + } + + @Override + public void initialize2WithApiKey(String apiKey, ReadableMap config, String apiEndPoint, String version, Promise promise) { + try { + IterableConfig iterableConfig = Serialization.getIterableConfigFromReadableMap(config); + IterableApi.initialize(getReactApplicationContext(), apiKey, iterableConfig, apiEndPoint); + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); + promise.resolve(true); + } catch (Exception e) { + promise.reject("", e.getMessage()); + } + } + + @Override + public void setEmail(String email, String authToken) { + IterableApi.getInstance().setEmail(email, authToken); + } + + @Override + public void getEmail(Promise promise) { + promise.resolve(IterableApi.getInstance().getEmail()); + } + + @Override + public void setUserId(String userId, String authToken) { + IterableApi.getInstance().setUserId(userId, authToken); + } + + @Override + public void getUserId(Promise promise) { + promise.resolve(IterableApi.getInstance().getUserId()); + } + + @Override + public void disableDeviceForCurrentUser() { + IterableApi.getInstance().disableDeviceForCurrentUser(); + } + + @Override + public void setInAppShowResponse(double inAppShowResponse) { + // Implementation for in-app show response + } + + @Override + public void getLastPushPayload(Promise promise) { + promise.resolve(IterableApi.getInstance().getLastPushPayload()); + } + + @Override + public void getAttributionInfo(Promise promise) { + promise.resolve(IterableApi.getInstance().getAttributionInfo()); + } + + @Override + public void setAttributionInfo(ReadableMap attributionInfo) { + IterableApi.getInstance().setAttributionInfo(Serialization.getAttributionInfoFromReadableMap(attributionInfo)); + } + + @Override + public void trackPushOpen(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields) { + IterableApi.getInstance().trackPushOpen( + (long)campaignId, + templateId != null ? templateId.longValue() : null, + messageId, + appAlreadyRunning, + Serialization.getMapFromReadableMap(dataFields) + ); + } + + @Override + public void updateCart(ReadableArray items) { + IterableApi.getInstance().updateCart(Serialization.getCommerceItemsFromReadableArray(items)); + } + + @Override + public void trackPurchase(double total, ReadableArray items, ReadableMap dataFields) { + IterableApi.getInstance().trackPurchase( + total, + Serialization.getCommerceItemsFromReadableArray(items), + Serialization.getMapFromReadableMap(dataFields) + ); + } + + @Override + public void trackInAppOpen(String messageId, double location) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.trackInAppOpen(messageId, Serialization.getInAppLocationFromInteger((int)location)); + } + + @Override + public void trackInAppClick(String messageId, double location, String clickedUrl) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.trackInAppClick(messageId, Serialization.getInAppLocationFromInteger((int)location), clickedUrl); + } + + @Override + public void trackInAppClose(String messageId, double location, double source, String clickedUrl) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.trackInAppClose( + messageId, + Serialization.getInAppLocationFromInteger((int)location), + Serialization.getInAppCloseSourceFromInteger((int)source), + clickedUrl + ); + } + + @Override + public void inAppConsume(String messageId, double location, double source) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.removeMessage( + messageId, + Serialization.getInAppLocationFromInteger((int)location), + Serialization.getInAppDeleteSourceFromInteger((int)source) + ); + } + + @Override + public void getHtmlInAppContentForMessage(String messageId, Promise promise) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.getHtmlInAppContentForMessage(messageId, promise::resolve, promise::reject); + } + + @Override + public void trackEvent(String name, ReadableMap dataFields) { + IterableApi.getInstance().trackEvent(name, Serialization.getMapFromReadableMap(dataFields)); + } + + @Override + public void updateUser(ReadableMap dataFields, boolean mergeNestedObjects) { + IterableApi.getInstance().updateUser(Serialization.getMapFromReadableMap(dataFields), mergeNestedObjects); + } + + @Override + public void updateEmail(String email, String authToken) { + IterableApi.getInstance().updateEmail(email, authToken); + } + + @Override + public void handleAppLink(String appLink, Promise promise) { + IterableApi.getInstance().handleAppLink(appLink, promise::resolve, promise::reject); + } + + @Override + public void getInAppMessages(Promise promise) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.getMessages(promise::resolve, promise::reject); + } + + @Override + public void getInboxMessages(Promise promise) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.getInboxMessages(promise::resolve, promise::reject); + } + + @Override + public void getUnreadInboxMessagesCount(Promise promise) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.getUnreadInboxMessagesCount(promise::resolve, promise::reject); + } + + @Override + public void showMessage(String messageId, boolean consume, Promise promise) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.showMessage(messageId, consume, promise::resolve, promise::reject); + } + + @Override + public void setReadForMessage(String messageId, boolean read) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.setRead(messageId, read); + } + + @Override + public void removeMessage(String messageId, double location, double deleteSource) { + IterableInAppManager inAppManager = IterableApi.getInstance().getInAppManager(); + inAppManager.removeMessage( + messageId, + Serialization.getInAppLocationFromInteger((int)location), + Serialization.getInAppDeleteSourceFromInteger((int)deleteSource) + ); + } +} diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleProvider.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleProvider.java new file mode 100644 index 000000000..b6a1a4c9c --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleProvider.java @@ -0,0 +1,31 @@ +package com.iterable.reactnative; + +import androidx.annotation.NonNull; +import com.facebook.react.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import com.facebook.react.turbomodule.core.interfaces.TurboModuleProvider; + +@ReactModule(name = RNIterableAPISpec.NAME) +public class RNIterableAPIModuleProvider implements TurboModuleProvider { + private final ReactApplicationContext reactContext; + + public RNIterableAPIModuleProvider(ReactApplicationContext reactContext) { + this.reactContext = reactContext; + } + + @Override + public String getName() { + return RNIterableAPISpec.NAME; + } + + @Override + public TurboModule getModule() { + return new RNIterableAPIImpl(reactContext); + } +} diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleSpec.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleSpec.java new file mode 100644 index 000000000..b139de83d --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleSpec.java @@ -0,0 +1,114 @@ +package com.iterable.reactnative; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; + +@ReactModule(name = RNIterableAPIModuleSpec.NAME) +public abstract class RNIterableAPIModuleSpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = "RNIterableAPI"; + + public RNIterableAPIModuleSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return NAME; + } + + // Native SDK Functions + @ReactMethod + public abstract void initializeWithApiKey(String apiKey, ReadableMap config, String version, Promise promise); + + @ReactMethod + public abstract void initialize2WithApiKey(String apiKey, ReadableMap config, String apiEndPoint, String version, Promise promise); + + @ReactMethod + public abstract void setEmail(String email, String authToken); + + @ReactMethod + public abstract void getEmail(Promise promise); + + @ReactMethod + public abstract void setUserId(String userId, String authToken); + + @ReactMethod + public abstract void getUserId(Promise promise); + + // Iterable API Request Functions + @ReactMethod + public abstract void disableDeviceForCurrentUser(); + + @ReactMethod + public abstract void setInAppShowResponse(double inAppShowResponse); + + @ReactMethod + public abstract void getLastPushPayload(Promise promise); + + @ReactMethod + public abstract void getAttributionInfo(Promise promise); + + @ReactMethod + public abstract void setAttributionInfo(ReadableMap attributionInfo); + + @ReactMethod + public abstract void trackPushOpen(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields); + + @ReactMethod + public abstract void updateCart(ReadableArray items); + + @ReactMethod + public abstract void trackPurchase(double total, ReadableArray items, ReadableMap dataFields); + + @ReactMethod + public abstract void trackInAppOpen(String messageId, double location); + + @ReactMethod + public abstract void trackInAppClick(String messageId, double location, String clickedUrl); + + @ReactMethod + public abstract void trackInAppClose(String messageId, double location, double source, String clickedUrl); + + @ReactMethod + public abstract void inAppConsume(String messageId, double location, double source); + + @ReactMethod + public abstract void getHtmlInAppContentForMessage(String messageId, Promise promise); + + @ReactMethod + public abstract void trackEvent(String name, ReadableMap dataFields); + + @ReactMethod + public abstract void updateUser(ReadableMap dataFields, boolean mergeNestedObjects); + + @ReactMethod + public abstract void updateEmail(String email, String authToken); + + @ReactMethod + public abstract void handleAppLink(String appLink, Promise promise); + + // SDK In-App Manager Functions + @ReactMethod + public abstract void getInAppMessages(Promise promise); + + @ReactMethod + public abstract void getInboxMessages(Promise promise); + + @ReactMethod + public abstract void getUnreadInboxMessagesCount(Promise promise); + + @ReactMethod + public abstract void showMessage(String messageId, boolean consume, Promise promise); + + @ReactMethod + public abstract void setReadForMessage(String messageId, boolean read); + + @ReactMethod + public abstract void removeMessage(String messageId, double location, double deleteSource); +} diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java index 2b04e447c..b9c5c79c5 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIPackage.java @@ -1,29 +1,26 @@ package com.iterable.reactnative; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - +import androidx.annotation.NonNull; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; public class RNIterableAPIPackage implements ReactPackage { + @NonNull @Override - public List createNativeModules( - ReactApplicationContext reactContext) { + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - modules.add(new RNIterableAPIModule(reactContext)); - return modules; } + @NonNull @Override - public List createViewManagers(ReactApplicationContext reactContext) { + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { return Collections.emptyList(); } } diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPISpec.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPISpec.java new file mode 100644 index 000000000..86081177b --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPISpec.java @@ -0,0 +1,117 @@ +package com.iterable.reactnative; + +import androidx.annotation.NonNull; +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.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import com.iterable.reactnative.bridge.IterableBridge; +import com.iterable.reactnative.bridge.IterableConfig; +import com.iterable.reactnative.bridge.IterableInAppMessage; +import com.iterable.reactnative.bridge.IterableInAppLocation; +import com.iterable.reactnative.bridge.IterableInAppCloseSource; +import com.iterable.reactnative.bridge.IterableInAppDeleteSource; +import com.iterable.reactnative.bridge.IterableCommerceItem; +import com.iterable.reactnative.bridge.IterableAttributionInfo; + +@ReactModule(name = RNIterableAPISpec.NAME) +public abstract class RNIterableAPISpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = "RNIterableAPI"; + + public RNIterableAPISpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + @NonNull + public String getName() { + return NAME; + } + + @ReactMethod + public abstract void initializeWithApiKey(String apiKey, ReadableMap config, String version, Promise promise); + + @ReactMethod + public abstract void initialize2WithApiKey(String apiKey, ReadableMap config, String apiEndPoint, String version, Promise promise); + + @ReactMethod + public abstract void setEmail(String email, String authToken); + + @ReactMethod + public abstract void getEmail(Promise promise); + + @ReactMethod + public abstract void setUserId(String userId, String authToken); + + @ReactMethod + public abstract void getUserId(Promise promise); + + @ReactMethod + public abstract void disableDeviceForCurrentUser(); + + @ReactMethod + public abstract void getLastPushPayload(Promise promise); + + @ReactMethod + public abstract void trackPushOpen(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields); + + @ReactMethod + public abstract void getInAppMessages(Promise promise); + + @ReactMethod + public abstract void getInboxMessages(Promise promise); + + @ReactMethod + public abstract void getUnreadInboxMessagesCount(Promise promise); + + @ReactMethod + public abstract void showMessage(String messageId, boolean consume, Promise promise); + + @ReactMethod + public abstract void setReadForMessage(String messageId, boolean read); + + @ReactMethod + public abstract void removeMessage(String messageId, double location, double deleteSource); + + @ReactMethod + public abstract void trackEvent(String name, ReadableMap dataFields); + + @ReactMethod + public abstract void updateUser(ReadableMap dataFields, boolean mergeNestedObjects); + + @ReactMethod + public abstract void updateEmail(String email, String authToken); + + @ReactMethod + public abstract void getAttributionInfo(Promise promise); + + @ReactMethod + public abstract void setAttributionInfo(ReadableMap attributionInfo); + + @ReactMethod + public abstract void updateCart(ReadableArray items); + + @ReactMethod + public abstract void trackPurchase(double total, ReadableArray items, ReadableMap dataFields); + + @ReactMethod + public abstract void trackInAppOpen(String messageId, double location); + + @ReactMethod + public abstract void trackInAppClick(String messageId, double location, String clickedUrl); + + @ReactMethod + public abstract void trackInAppClose(String messageId, double location, double source, String clickedUrl); + + @ReactMethod + public abstract void inAppConsume(String messageId, double location, double source); + + @ReactMethod + public abstract void handleAppLink(String appLink, Promise promise); +} diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPITurboModule.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPITurboModule.java new file mode 100644 index 000000000..00decf919 --- /dev/null +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPITurboModule.java @@ -0,0 +1,37 @@ +package com.iterable.reactnative; + +import androidx.annotation.NonNull; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; + +public interface RNIterableAPITurboModule extends TurboModule { + void initializeWithApiKey(String apiKey, ReadableMap config, String version, Promise promise); + void initialize2WithApiKey(String apiKey, ReadableMap config, String apiEndPoint, String version, Promise promise); + void setEmail(String email, String authToken); + void getEmail(Promise promise); + void setUserId(String userId, String authToken); + void getUserId(Promise promise); + void disableDeviceForCurrentUser(); + void getLastPushPayload(Promise promise); + void trackPushOpen(double campaignId, Double templateId, String messageId, boolean appAlreadyRunning, ReadableMap dataFields); + void getInAppMessages(Promise promise); + void getInboxMessages(Promise promise); + void getUnreadInboxMessagesCount(Promise promise); + void showMessage(String messageId, boolean consume, Promise promise); + void setReadForMessage(String messageId, boolean read); + void removeMessage(String messageId, double location, double deleteSource); + void trackEvent(String name, ReadableMap dataFields); + void updateUser(ReadableMap dataFields, boolean mergeNestedObjects); + void updateEmail(String email, String authToken); + void getAttributionInfo(Promise promise); + void setAttributionInfo(ReadableMap attributionInfo); + void updateCart(ReadableArray items); + void trackPurchase(double total, ReadableArray items, ReadableMap dataFields); + void trackInAppOpen(String messageId, double location); + void trackInAppClick(String messageId, double location, String clickedUrl); + void trackInAppClose(String messageId, double location, double source, String clickedUrl); + void inAppConsume(String messageId, double location, double source); + void handleAppLink(String appLink, Promise promise); +} diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 3a1f536a6..b38848b24 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -24,6 +24,10 @@ import com.iterable.iterableapi.IterableInboxSession; import com.iterable.iterableapi.IterableLogger; import com.iterable.iterableapi.RNIterableInternal; +import com.iterable.iterableapi.IterableAttributionInfo; +import com.iterable.iterableapi.IterableCommerceItem; +import com.iterable.iterableapi.IterableInAppCloseSource; +import com.iterable.iterableapi.IterableInAppDeleteSource; import org.json.JSONArray; import org.json.JSONException; @@ -32,8 +36,9 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; -class Serialization { +public class Serialization { static String TAG = "Serialization"; static IterableInAppLocation getIterableInAppLocationFromInteger(@Nullable Integer location) { @@ -94,7 +99,7 @@ static CommerceItem commerceItemFromMap(JSONObject itemMap) throws JSONException categories[i] = categoriesArray.getString(i); } } - + return new CommerceItem(itemMap.getString("id"), itemMap.getString("name"), itemMap.getDouble("price"), @@ -216,7 +221,7 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte configBuilder.setDataRegion(iterableDataRegion); } - + if (iterableContextJSON.has("encryptionEnforced")) { configBuilder.setEncryptionEnforced(iterableContextJSON.optBoolean("encryptionEnforced")); } @@ -281,12 +286,10 @@ static List impressionsFromReadableArray(Readab return list; } - - // --------------------------------------------------------------------------------------- // region React Native JSON conversion methods // obtained from https://gist.github.com/viperwarp/2beb6bbefcc268dee7ad - + static WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONException { WritableMap map = new WritableNativeMap(); @@ -393,4 +396,171 @@ static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONExce } // --------------------------------------------------------------------------------------- // endregion + + public static Map getMapFromReadableMap(ReadableMap readableMap) { + if (readableMap == null) { + return null; + } + + Map map = new HashMap<>(); + for (String key : readableMap.toHashMap().keySet()) { + ReadableType type = readableMap.getType(key); + switch (type) { + case Null: + map.put(key, null); + break; + case Boolean: + map.put(key, readableMap.getBoolean(key)); + break; + case Number: + map.put(key, readableMap.getDouble(key)); + break; + case String: + map.put(key, readableMap.getString(key)); + break; + case Map: + map.put(key, getMapFromReadableMap(readableMap.getMap(key))); + break; + case Array: + map.put(key, getListFromReadableArray(readableMap.getArray(key))); + break; + } + } + return map; + } + + public static List getListFromReadableArray(ReadableArray readableArray) { + if (readableArray == null) { + return null; + } + + List list = new ArrayList<>(); + for (int i = 0; i < readableArray.size(); i++) { + ReadableType type = readableArray.getType(i); + switch (type) { + case Null: + list.add(null); + break; + case Boolean: + list.add(readableArray.getBoolean(i)); + break; + case Number: + list.add(readableArray.getDouble(i)); + break; + case String: + list.add(readableArray.getString(i)); + break; + case Map: + list.add(getMapFromReadableMap(readableArray.getMap(i))); + break; + case Array: + list.add(getListFromReadableArray(readableArray.getArray(i))); + break; + } + } + return list; + } + + public static IterableConfig getIterableConfigFromReadableMap(ReadableMap config) { + if (config == null) { + return new IterableConfig.Builder().build(); + } + + IterableConfig.Builder builder = new IterableConfig.Builder(); + + if (config.hasKey("pushIntegrationName")) { + builder.setPushIntegrationName(config.getString("pushIntegrationName")); + } + + if (config.hasKey("autoPushRegistration")) { + builder.setAutoPushRegistration(config.getBoolean("autoPushRegistration")); + } + + if (config.hasKey("logLevel")) { + builder.setLogLevel(config.getInt("logLevel")); + } + + if (config.hasKey("inAppDisplayInterval")) { + builder.setInAppDisplayInterval(config.getDouble("inAppDisplayInterval")); + } + + if (config.hasKey("urlHandler")) { + builder.setUrlHandler(new IterableUrlHandler(config.getMap("urlHandler"))); + } + + if (config.hasKey("customActionHandler")) { + builder.setCustomActionHandler(new IterableCustomActionHandler(config.getMap("customActionHandler"))); + } + + if (config.hasKey("inAppHandler")) { + builder.setInAppHandler(new IterableInAppHandler(config.getMap("inAppHandler"))); + } + + return builder.build(); + } + + public static IterableAttributionInfo getAttributionInfoFromReadableMap(ReadableMap attributionInfo) { + if (attributionInfo == null) { + return null; + } + + return new IterableAttributionInfo( + attributionInfo.getString("campaignId"), + attributionInfo.getString("templateId"), + attributionInfo.getString("messageId") + ); + } + + public static List getCommerceItemsFromReadableArray(ReadableArray items) { + if (items == null) { + return null; + } + + List commerceItems = new ArrayList<>(); + for (int i = 0; i < items.size(); i++) { + ReadableMap item = items.getMap(i); + commerceItems.add(new IterableCommerceItem( + item.getString("id"), + item.getString("name"), + item.getDouble("price"), + item.getInt("quantity") + )); + } + return commerceItems; + } + + public static IterableInAppLocation getInAppLocationFromInteger(int location) { + switch (location) { + case 0: + return IterableInAppLocation.IN_APP; + case 1: + return IterableInAppLocation.INBOX; + default: + return IterableInAppLocation.IN_APP; + } + } + + public static IterableInAppCloseSource getInAppCloseSourceFromInteger(int source) { + switch (source) { + case 0: + return IterableInAppCloseSource.BACK; + case 1: + return IterableInAppCloseSource.LINK; + case 2: + return IterableInAppCloseSource.CLOSE; + default: + return IterableInAppCloseSource.BACK; + } + } + + public static IterableInAppDeleteSource getInAppDeleteSourceFromInteger(int source) { + switch (source) { + case 0: + return IterableInAppDeleteSource.INBOX_SWIPE; + case 1: + return IterableInAppDeleteSource.DELETE_BUTTON; + default: + return IterableInAppDeleteSource.INBOX_SWIPE; + } + } } diff --git a/example/Gemfile.lock b/example/Gemfile.lock new file mode 100644 index 000000000..1b4d501e5 --- /dev/null +++ b/example/Gemfile.lock @@ -0,0 +1,124 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.2.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + base64 (0.2.0) + benchmark (0.4.0) + bigdecimal (3.2.0) + claide (1.1.0) + cocoapods (1.15.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.15.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.3.3) + connection_pool (2.5.3) + drb (2.2.3) + escape (0.0.4) + ethon (0.16.0) + ffi (>= 1.15.0) + ffi (1.17.2) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) + concurrent-ruby (~> 1.0) + json (2.12.2) + logger (1.7.0) + minitest (5.25.5) + molinillo (0.8.0) + mutex_m (0.3.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + nkf (0.2.0) + public_suffix (4.0.7) + rexml (3.4.1) + ruby-macho (2.5.1) + securerandom (0.4.1) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + xcodeproj (1.25.1) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (>= 3.3.6, < 4.0) + +PLATFORMS + ruby + +DEPENDENCIES + activesupport (>= 6.1.7.5, != 7.1.0) + benchmark + bigdecimal + cocoapods (>= 1.13, != 1.15.1, != 1.15.0) + concurrent-ruby (< 1.3.4) + logger + mutex_m + xcodeproj (< 1.26.0) + +RUBY VERSION + ruby 3.3.6p108 + +BUNDLED WITH + 2.5.23 diff --git a/example/README.md b/example/README.md index 3b19f26e9..c60b69d21 100644 --- a/example/README.md +++ b/example/README.md @@ -1,275 +1,163 @@ -This is the Iterable React Native Example App, bootstrapped using -[`@react-native-community/cli`](https://github.com/react-native-community/cli). +# Iterable React Native SDK Example -Use this to understand how you can use Iterables React Native SDK in your own -React Native application. +This example app demonstrates how to use the Iterable React Native SDK with both the old and new architectures. -# Getting Started +## Architecture Support ->**Note**: Make sure you have completed the [React Native - Environment ->Setup](https://reactnative.dev/docs/set-up-your-environment) instructions till ->"Creating a new application" step, before proceeding. +The SDK supports both the old architecture (Paper) and the new architecture (Fabric/TurboModules): -## Step 1: Install dependencies -To install the app dependencies, run the following command from the -_example app directory_ (the directory in which this document resides): +### Old Architecture (Paper) +- Uses the traditional React Native bridge +- Compatible with all React Native versions +- No additional configuration needed -```bash -yarn install -``` - -Once this is done, you will need to install the pods in the _ios_ folder in the -_example app directory_. To do so, run the following: - -```bash -cd ios -pod install -``` +### New Architecture (Fabric/TurboModules) +- iOS: Uses Fabric for native components +- Android: Uses TurboModules for native modules +- Requires React Native 0.70.0 or later +- Requires additional configuration -Once this is done, `cd` back into the _example app directory_: +## Setup +1. Install dependencies: ```bash -cd .. +yarn install ``` -## Step 2: Add your environment variables -In the _example app directory_, there is a file called **.env.example**. Make a -copy of this file, name it **.env** and place it in the example app directory. - -In it, you will find: +2. Configure the SDK: +```typescript +// Initialize with your API key +Iterable.initialize('YOUR_API_KEY'); -```shell -ITBL_API_KEY=replace_this_with_your_iterable_api_key -ITBL_ID=replace_this_with_your_user_id_or_email +// Set up event listeners +Iterable.onInAppReceived((message) => { + console.log('In-app message received:', message); +}); ``` -Replace `replace_this_with_your_iterable_api_key` with your _mobile_ Iterable API key, -and replace `replace_this_with_your_user_id_or_email` with the email or user id -that you use to log into Iterable. - -Follow the steps below if you do not have a mobile Iterable API key. - -### Adding an API Key -To add an API key, do the following: - 1. Sign into your Iterable account - 2. Go to [Integrations > API Keys](https://app.iterable.com/settings/apiKeys) - 3. Click "New API Key" in the top right corner - 4. Fill in the followsing fields: - - Name: A descriptive name for the API key - - Type: Mobile - - JWT authentication: Leave **unchecked** (IMPORTANT) - 5. Click "Create API Key" - 6. Copy the generated API key - - -## Step 3: Start the Metro Server - -First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. - -To start Metro, run the following command from the _example app directory_: +3. Build for specific architecture: +For old architecture: ```bash -yarn start -``` - -## Step 4: Start your Application - -Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _example app directory_. Run the following command to start your _Android_ or _iOS_ app: - -### For Android +# iOS +yarn ios -```bash +# Android yarn android ``` -### For iOS - +For new architecture: ```bash -yarn ios -``` - -**NOTE**: If you are getting an error when running ios, make sure that *Xcode > Project Navigator > ReactNativeSdkExample > Build Settings > User - Script Sandboxing* is set to **No** +# iOS +yarn ios --new-arch -If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. - -This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. - - -## Congratulations! :tada: - -You've successfully run the Iterable Example App. :partying_face: - - -# Troubleshooting - -## Error: `Could not find gem 'cocoapods (>= 1.13, != 1.15.0, != 1.15.1)' in locally installed gems` - -To fix, run the following in the _example app directory_: - -```bash -bundle install +# Android +yarn android --new-arch ``` -## Error: `Signing for 'ReactNativeSdkExample' requires a development team. Select a development team in the Signing & Capabilities Editor` - -- Open XCode -- Go to 'Signing & Capabilities' -- Choose a team -- Stop your application, then rerun - -If you are still experiencing issues, try deleting `ios/.xcode.env.local` - -## Error: `/Library/Ruby/Gems/XYZ does not have write permissions` or `/usr/local/lib does not have write permissions` +## Key Features -This is a common issue with using ruby on Macs. You can modify the read/write -access of the computers Ruby folder, but a better (and safer) way is to use -`rbenv` and [`homebrew`](https://brew.sh/) by doing the following: +### User Management +```typescript +// Set user email +await Iterable.setEmail('user@example.com'); -1. **Install/update homebrew** -If you have homebrew, update it by running: `brew update && brew upgrade`. -If you do not have homebrew, follow the [installation -instructions](https://brew.sh/). +// Get current email +const email = await Iterable.getEmail(); -2. **Install `rbenv` and `ruby-build`** -```bash -# Uninstall ruby (you can try skipping this step if you have concerns) -brew uninstall --ignore-dependencies ruby -# Install `rbenv` and `ruby-build` -brew install rbenv ruby-build -# Install the correct ruby version, eg: 3.3.6 -rbenv install 3.3.6 -# Default to using this ruby version -rbenv global 3.3.6 +// Update user data +Iterable.updateUser({ + firstName: 'John', + lastName: 'Doe', + customField: 'customValue' +}); ``` -3. **Tell your computer to use `rbenv`** -Add the following to the top of your `.zshrc` or `.bash_profile`: -```zsh -eval "$(rbenv init -)" +### Event Tracking +```typescript +// Track custom event +Iterable.trackEvent('button_clicked', { + buttonName: 'track_event', + timestamp: new Date().toISOString() +}); ``` -4. **Reload `.zshrc` or `.bash_profile`** -Run the following in your terminal: -```bash -# If using zsh -source ~/.zshrc -# If using bash -source ~/.bash_profile -``` +### In-App Messages +```typescript +// Get in-app messages +const messages = await Iterable.getInAppMessages(); -5. **Check that the correct ruby version is loading** -Run the following in your terminal: -```bash -ruby --version +// Show in-app message +await Iterable.showMessage(messageId, true); ``` -If working, it should say `3.3.6` -## Error: `bad interpreter: No such file or directory` on `pod install` -Reinstall cocoapods by doing the following: -```bash -# Uninstall current version of cocoapods -brew uninstall cocoapods -# Install a fresh version of cocoapods -brew install cocoapods -# Recreate link to cocoapods -brew unlink cocoapods && brew link cocoapods +### Inbox Messages +```typescript +// Get inbox messages +const messages = await Iterable.getInboxMessages(); + +// Mark message as read +Iterable.setReadForMessage(messageId, true); + +// Remove message +Iterable.removeMessage(messageId, location, deleteSource); ``` -Run `pod install` again, and it should work. +## Architecture-Specific Features -## Error: `com.android.builder.errors.EvalIssueException: SDK location not found. Define a valid SDK location with an ANDROID_HOME environment variable or by setting the sdk.dir path in your projects local properties file` +### New Architecture (Fabric/TurboModules) -This means that the project cannot find the location of your Android SDK. +1. Better Performance: + - Direct communication between JS and native + - Reduced bridge overhead + - Improved startup time -There are two ways to fix this: +2. Type Safety: + - Better TypeScript support + - Compile-time type checking + - Improved developer experience -### 1. Add `ANDROID_HOME` to your *.zshrc* or *.bashrc* file. -1. Open your *.zshrc* or *.bashrc* -2. Add the following to the file: - ```bash - ANDROID_HOME=/path/to/Android/SDK # EG: ANDROID_HOME=/Users/My.Name/Library/Android/sdk - ``` +3. Native Components: + - iOS: Fabric-powered native components + - Android: TurboModule-powered native modules -### 2. Add a *local.properties* file to *example/android*. -1. Go to *example/android* -2. Create a file called *local.properties* -3. In *local.properties*, add: - ```bash - sdk.dir=/path/to/Android/SDK # EG: sdk.dir=/Users/My.Name/Library/Android/sdk - ``` +### Old Architecture (Paper) -## Error: `bundler: failed to load command: pod` -Run `bundle install` in the _example app directory_. You can also try running -it in _ios_ in the _example app directory_. +1. Wider Compatibility: + - Works with all React Native versions + - No additional configuration needed + - Stable and well-tested -## Error: `uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger` +2. Simpler Setup: + - No architecture-specific code + - Standard React Native bridge + - Familiar development experience -This is a known issue with Ruby 3.4.0. You can fix it by running the following: -```bash -gem install xcodeproj -v '< 1.26.0' -gem install concurrent-ruby -v '< 1.3.4' -``` +## Troubleshooting -## Unable to build on Xcode 16.3 +1. Build Issues: + - Clean build folders: `yarn clean` + - Rebuild: `yarn build:all` + - Check architecture flags in `package.json` -There is a [known issue](https://github.com/facebook/react-native/issues/50411) -with Xcode 16.3 and react-native@0.75.3. +2. Runtime Issues: + - Check console logs + - Verify API key + - Ensure proper initialization -Until New Architecture is supporter by Iterable, we cannot upgrade to 0.76. -Therefore, to fix it we need to downgrade Xcode to 16.2. -- [Download Xcode 16.2](https://download.developer.apple.com/Developer_Tools/Xcode_16.2/Xcode_16.2.xip) +3. Architecture-Specific Issues: + - Verify architecture flags + - Check native module registration + - Review build configuration -## Other -If things are not working and you are stumped as to why, try running the -following in the _example app directory_: +## Contributing -```bash -npx react-native doctor -``` +1. Fork the repository +2. Create your feature branch +3. Commit your changes +4. Push to the branch +5. Create a Pull Request -This will give you information about what react native needs in order to run, -and whether it is accessible to the app. - -Take a look at the OS you are trying to run. Make sure that everything has been -installed and that the necessary items have been added to your `PATH`. Below -are example items that are commonly added to the *.zshrc* or *.bashrc*: - -```zsh -# Load rbenv if using (suggested) -if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi - -# Load nvm if using (suggested) -export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm -[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion - -# General paths -export PATH=$HOME/bin:/usr/local/bin:$PATH -export PATH=$PATH:$(pwd)/bin - -# Homebrew setup -if [ -d "/opt/homebrew/bin" ]; then - export PATH="/opt/homebrew/bin:$PATH" -fi - -# Android paths and variables -export ANDROID_HOME=$HOME/Library/Android/sdk -export ANDROID_SDK_ROOT=$ANDROID_HOME -export PATH=$PATH:$ANDROID_HOME -export PATH=$PATH:$ANDROID_HOME/emulator -export PATH=$PATH:$ANDROID_HOME/platform-tools -export PATH=$PATH:/opt/homebrew/bin/gradle -export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin - -# Java variables -export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home - -# Node variables and settings -export NODE_BINARY=node -export NODE_OPTIONS=--openssl-legacy-provider -``` +## License -You should also look through the [React Native environment setup -docs](https://reactnative.dev/docs/set-up-your-environment) and make sure that -you did not miss anything. \ No newline at end of file +MIT diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 373c949fd..76962162d 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -85,6 +85,40 @@ android { versionCode 1 versionName "1.0" } + + packagingOptions { + exclude '**/libc++_shared.so' + exclude '**/libfbjni.so' + exclude '**/libfolly_runtime.so' + exclude '**/libglog.so' + exclude '**/libhermes.so' + exclude '**/libjsi.so' + exclude '**/libreact_codegen_rngesturehandler_codegen.so' + exclude '**/libreact_debug.so' + exclude '**/libreact_nativemodule_core.so' + exclude '**/libreact_render_debug.so' + exclude '**/libreact_render_graphics.so' + exclude '**/librrc_view.so' + exclude '**/libruntimeexecutor.so' + exclude '**/libturbomodulejsijni.so' + exclude '**/libyoga.so' + pickFirst '**/libc++_shared.so' + pickFirst '**/libfbjni.so' + pickFirst '**/libfolly_runtime.so' + pickFirst '**/libglog.so' + pickFirst '**/libhermes.so' + pickFirst '**/libjsi.so' + pickFirst '**/libreact_codegen_rngesturehandler_codegen.so' + pickFirst '**/libreact_debug.so' + pickFirst '**/libreact_nativemodule_core.so' + pickFirst '**/libreact_render_debug.so' + pickFirst '**/libreact_render_graphics.so' + pickFirst '**/librrc_view.so' + pickFirst '**/libruntimeexecutor.so' + pickFirst '**/libturbomodulejsijni.so' + pickFirst '**/libyoga.so' + } + signingConfigs { debug { storeFile file('debug.keystore') diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 289375fe5..4af51c485 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -27,12 +27,7 @@ android.useAndroidX=true # ./gradlew -PreactNativeArchitectures=x86_64 reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 -# Use this property to enable support to the new architecture. -# This will allow you to use TurboModules and the Fabric render in -# 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 + # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. @@ -40,4 +35,17 @@ 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 + +######################################################## +# New Architecture flags +######################################################## +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# 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 +fabricEnabled=true + +kotlin.compiler.allWarningsAsErrors=false diff --git a/example/ios/Podfile b/example/ios/Podfile index 1b747c965..4a119accf 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -8,11 +8,11 @@ require Pod::Executable.execute_command('node', ['-p', platform :ios, min_ios_version_supported prepare_react_native_project! -linkage = ENV['USE_FRAMEWORKS'] -if linkage != nil - Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green - use_frameworks! :linkage => linkage.to_sym -end +# Enable new architecture +ENV['RCT_NEW_ARCH_ENABLED'] = '1' + +# Use frameworks for all pods +use_frameworks! :linkage => :static target 'ReactNativeSdkExample' do config = use_native_modules! @@ -20,9 +20,15 @@ target 'ReactNativeSdkExample' do use_react_native!( :path => config[:reactNativePath], # An absolute path to your application root. - :app_path => "#{Pod::Config.instance.installation_root}/.." + :app_path => "#{Pod::Config.instance.installation_root}/..", + # Enable new architecture + :fabric_enabled => true, + # Enable Hermes + :hermes_enabled => true ) + pod 'IterableSDK' + target 'ReactNativeSdkExampleTests' do inherit! :complete # Pods for testing @@ -33,8 +39,16 @@ target 'ReactNativeSdkExample' do react_native_post_install( installer, config[:reactNativePath], - :mac_catalyst_enabled => false, - # :ccache_enabled => true + :mac_catalyst_enabled => false ) + + # Fix for Xcode 15 and new architecture + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.2' + config.build_settings['ENABLE_BITCODE'] = 'NO' + config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' + end + end end end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 66f2bd0cb..0db7cca84 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -8,7 +8,7 @@ PODS: - hermes-engine/Pre-built (= 0.75.3) - hermes-engine/Pre-built (0.75.3) - Iterable-iOS-SDK (6.5.4) - - Iterable-React-Native-SDK (2.0.0-alpha): + - Iterable-React-Native-SDK (2.0.0-beta.1): - DoubleConversion - glog - hermes-engine @@ -30,6 +30,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - IterableSDK (5.0.3) - RCT-Folly (2024.01.01.00): - boost - DoubleConversion @@ -1261,7 +1262,71 @@ PODS: - ReactCommon/turbomodule/core - Yoga - react-native-safe-area-context (4.11.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - react-native-safe-area-context/common (= 4.11.1) + - react-native-safe-area-context/fabric (= 4.11.1) + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - react-native-safe-area-context/common (4.11.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - react-native-safe-area-context/fabric (4.11.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - react-native-safe-area-context/common + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-webview (13.12.3): - DoubleConversion - glog @@ -1543,7 +1608,7 @@ PODS: - React-logger (= 0.75.3) - React-perflogger (= 0.75.3) - React-utils (= 0.75.3) - - RNGestureHandler (2.20.1): + - RNGestureHandler (2.25.0): - DoubleConversion - glog - hermes-engine @@ -1553,6 +1618,7 @@ PODS: - React-Core - React-debug - React-Fabric + - React-FabricComponents - React-featureflags - React-graphics - React-ImageManager @@ -1565,6 +1631,29 @@ PODS: - ReactCommon/turbomodule/core - Yoga - RNScreens (3.34.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNScreens/common (= 3.34.0) + - Yoga + - RNScreens/common (3.34.0): - DoubleConversion - glog - hermes-engine @@ -1618,6 +1707,7 @@ DEPENDENCIES: - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - Iterable-React-Native-SDK (from `../..`) + - IterableSDK - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) @@ -1685,6 +1775,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - Iterable-iOS-SDK + - IterableSDK - SocketRocket EXTERNAL SOURCES: @@ -1834,7 +1925,8 @@ SPEC CHECKSUMS: glog: 69ef571f3de08433d766d614c73a9838a06bf7eb hermes-engine: 8d2103d6c0176779aea4e25df6bb1410f9946680 Iterable-iOS-SDK: 710bf6dada7dae9c03a09db7f0bc6c9d9a671f40 - Iterable-React-Native-SDK: 704ac7fdc74f9a60621537f7724fe352f9d558c6 + Iterable-React-Native-SDK: 5c24de61a0453025d312532c813f83e9e595047c + IterableSDK: 37fbabbb86220d46c912d5aaff944f759c17f3a2 RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 RCTDeprecation: 4191f6e64b72d9743f6fe1a8a16e89e868f5e9e7 RCTRequired: 9bb589570f2bb3abc6518761e3fd1ad9b7f7f06c @@ -1844,60 +1936,60 @@ SPEC CHECKSUMS: React-Core: 2fc97900b68e7568233698c6113ca9d64ed8b520 React-CoreModules: 2d68c251bc4080028f2835fa47504e8f20669a21 React-cxxreact: 5f233f8ac7ea4772e49462e0ab2b0a15a4f80ab7 - React-debug: fd0ed8ecd5f8a23c7daf5ceaca8aa722a4d083fd - React-defaultsnativemodule: 10f0f8bc38d8dc7d2273572cd85ed0b71298ecdd - React-domnativemodule: bfef3dda59e7030b498d0d78628f4adf414ab8e4 - React-Fabric: 3d0f5e2735d2f77a897ee684edeff7bb0e061919 - React-FabricComponents: 68032a85a3c25c9c8d6ce676d8af9a85e2370f24 - React-FabricImage: f8ac2df576703097b5b2f8d972b162cdca855aa3 - React-featureflags: cf78861db9318ae29982fa8953c92d31b276c9ac - React-featureflagsnativemodule: d04eb5c3f0ac33fe70b060d97e8649bfd69c5f1e - React-graphics: 7572851bca7242416b648c45d6af87d93d29281e + React-debug: 16366fffc0cf8914d4e69a103b2b9d06b051daa7 + React-defaultsnativemodule: dde6cc8db974581a799bc066efce836ffa0d9005 + React-domnativemodule: 4d05bb76fed291db794fbe347b0b44716a11a184 + React-Fabric: 517472a46d326349902e33e63bcde2133178eb73 + React-FabricComponents: 70a60d6194db2d50b887a2c5dfb4144950242438 + React-FabricImage: 8400b691b0cf5728458a15f5d70a034e0e01c7f1 + React-featureflags: 3b80a592aec905c62bcea96f219f33a88f5a8ea3 + React-featureflagsnativemodule: 4b421eb1cb534c473567c9cbfa11e60c1745642c + React-graphics: 281e54a228ae0541b74e428394b4ac54d2210a2b React-hermes: 95c27801c60615345ee6256eafa6d597ce983b8b - React-idlecallbacksnativemodule: f5f0b760ec2739b30e315e1afee3dd3a5a93c3b6 - React-ImageManager: aedf54d34d4475c66f4c3da6b8359b95bee904e4 - React-jserrorhandler: 0c8949672a00f2a502c767350e591e3ec3d82fb3 + React-idlecallbacksnativemodule: e657563d73beca42333a7e5c21246fbc53cbb529 + React-ImageManager: a1ea46ce239f0fcd1df471c35dc559c8b26f31d8 + React-jserrorhandler: f5e00daad1d80feb67f3400cf8bf25f782b34b67 React-jsi: d77bb442a4b0849063f2bd22d3c1fa71918713b7 React-jsiexecutor: 3b9c6334b7b0f42d4c4aae950132766e63a7809f - React-jsinspector: e1bb5816869507527c30213cc1ed60eae9e3e9c4 - React-jsitracing: 3935b092f85bb1e53b8cf8a00f572413648af46b + React-jsinspector: 4579d72bf210a9028038d64099ece0261d691e01 + React-jsitracing: 11e3a39d0b47c8ba84aa94d137a7a11a6074a82b React-logger: 4072f39df335ca443932e0ccece41fbeb5ca8404 - React-Mapbuffer: 714f2fae68edcabfc332b754e9fbaa8cfc68fdd4 - React-microtasksnativemodule: 4943ad8f99be8ccf5a63329fa7d269816609df9e - react-native-safe-area-context: 5141f11858b033636f1788b14f32eaba92cee810 - react-native-webview: 926d2665cf3196e39c4449a72d136d0a53b9df8a - React-nativeconfig: 4a9543185905fe41014c06776bf126083795aed9 - React-NativeModulesApple: 0506da59fc40d2e1e6e12a233db5e81c46face27 + React-Mapbuffer: a90dc2b770956814f2f2ac4553063c5ce2618fe8 + React-microtasksnativemodule: 0b44bb6d632c420b02ec90a7375c208a0e7896ab + react-native-safe-area-context: 1194c10b089e866195f1e437cfd371e83c195589 + react-native-webview: b20d2755d69776b5e254822e8c8f452694f9d2b2 + React-nativeconfig: cd572c97f64676dcc2a1c88fc644b693b98408bf + React-NativeModulesApple: 249c7a23d8ad82f13778f4f525a8d19ccf4deb86 React-perflogger: 3bbb82f18e9ac29a1a6931568e99d6305ef4403b - React-performancetimeline: d15a723422ed500f47cb271f3175abbeb217f5ba + React-performancetimeline: 2274e28dc1bd9b22c70913fdf93203ea3988a126 React-RCTActionSheet: cb2b38a53d03ec22f1159c89667b86c2c490d92d React-RCTAnimation: 6836c87c7364f471e9077fda80b7349bc674be33 - React-RCTAppDelegate: 2f11edfa7302451c792591f9a7838ca86cdcec34 + React-RCTAppDelegate: 603240f6a7d7eefeeffe4e29dd0be70dc35208cf React-RCTBlob: 516dbbd38397f5013394fdd1cc65408cc82e37a1 - React-RCTFabric: b281a52c2b9726b0c64880e1535f2100013d5f7c + React-RCTFabric: 2389d69b7c6862c5a9494bc3e1200ea56c9c81b7 React-RCTImage: 1b2c2c1716db859ffff2d7a06a30b0ec5c677fc5 React-RCTLinking: 59c07577767e705b0ab95d11e5ad74c61bf2a022 React-RCTNetwork: f9a827e7d6bc428e0d99cd1fbe0427854354b8c1 React-RCTSettings: 614252fecc24840f61590c016aca1664a52cfb0f React-RCTText: 424549f68867265aa25969f50e7b9bf8bd70ae55 React-RCTVibration: c8d156e6cce18f00b0310db7670fa997c7cda407 - React-rendererconsistency: 993f54bb0df644df2922cd87ea55238d510d992b - React-rendererdebug: 7a8cbb632b68d666ad0fc01b3f9dc1a1bcc9a9f9 - React-rncore: 1df26fe0ae861c599f9f2896f45e8834ef4b85f9 - React-RuntimeApple: b5b14b09e3be4058f9fe7ab4925e1ee343f03310 - React-RuntimeCore: 2073fb33da2aec6ce6c1c9d3d53898ed1f1d806d + React-rendererconsistency: e5f034a362659ade5e7de13e41b5d77c0b9f5c1e + React-rendererdebug: 4b06fadff173e3cdc1cefa45fc4dfe5ca087415b + React-rncore: 639c8dde2af07944446a829dd784e19332890a38 + React-RuntimeApple: 2c958a446af933ea6542bd39893e0eef0ecb979e + React-RuntimeCore: 84296af62ff91b7c693771e09e09899ab6afe80b React-runtimeexecutor: 9a668b94ad5d93755443311715bd57680330286a - React-RuntimeHermes: b37c62718d6920ac2958a0052bdc1b01aca842b8 - React-runtimescheduler: e25750a18cbb7469e0513f1ace834d14e8c1a202 - React-utils: f2afa6acd905ca2ce7bb8ffb4a22f7f8a12534e8 - ReactCodegen: ff95a93d5ab5d9b2551571886271478eaa168565 - ReactCommon: 289214026502e6a93484f4a46bcc0efa4f3f2864 - RNGestureHandler: 1b7c5ed637e02f6898d08bffb701d378be5e091d - RNScreens: 19719a9c326e925498ac3b2d35c4e50fe87afc06 - RNVectorIcons: 6382277afab3c54658e9d555ee0faa7a37827136 + React-RuntimeHermes: 06edca92bc368d5529be75663741f76de7923b01 + React-runtimescheduler: 3d2c3ef1c7c20268a129651d0b85ca151e168a0f + React-utils: 211020f1a54caa72455ccf55853b064e590f564b + ReactCodegen: 4291c041b116b97f7203d961a3cc95adf2347064 + ReactCommon: d5e70af52fb05bd546fd7f002c549f57116c59e4 + RNGestureHandler: 11c5dd2f721813fe21291182e636e3b84eb90ded + RNScreens: 47d5b0b48402f5a2da5b2dd543deae3c9083a5ac + RNVectorIcons: 8c7af262a73890caabbac2bb081ddbaa1b8a768c SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d - Yoga: 4ef80d96a5534f0e01b3055f17d1e19a9fc61b63 + Yoga: 33604ac44957ebe3f30f15b4cd0a3f96634e624a -PODFILE CHECKSUM: aab4a30773612c4ffb73be13f5b169b8b156f374 +PODFILE CHECKSUM: 5f90f4d7c265f880f7fd67f1e8ebaca813846945 COCOAPODS: 1.16.2 diff --git a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj index 0284e8e1f..074199af7 100644 --- a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj @@ -8,11 +8,11 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* ReactNativeSdkExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */; }; - 0C80B921A6F3F58F76C31292 /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeSdkExample.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 7699B88040F8A987B510C191 /* libPods-ReactNativeSdkExample-ReactNativeSdkExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-ReactNativeSdkExample-ReactNativeSdkExampleTests.a */; }; + 2A3E6B2C5E705A7EDF686E84 /* Pods_ReactNativeSdkExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E876B5261C6746E1A77B06 /* Pods_ReactNativeSdkExample.framework */; }; + 75E0FCF4B434AB4B3BC1160B /* Pods_ReactNativeSdkExample_ReactNativeSdkExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7CA06A41BBE122C9FCB3514 /* Pods_ReactNativeSdkExample_ReactNativeSdkExampleTests.framework */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; A3A40C20801B8F02005FA4C0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -31,6 +31,7 @@ 00E356EE1AD99517003FC87E /* ReactNativeSdkExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactNativeSdkExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactNativeSdkExampleTests.m; sourceTree = ""; }; + 05E876B5261C6746E1A77B06 /* Pods_ReactNativeSdkExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReactNativeSdkExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNativeSdkExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = ReactNativeSdkExample/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = ReactNativeSdkExample/AppDelegate.mm; sourceTree = ""; }; @@ -38,14 +39,13 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeSdkExample/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ReactNativeSdkExample/main.m; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 19F6CBCC0A4E27FBF8BF4A61 /* libPods-ReactNativeSdkExample-ReactNativeSdkExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample-ReactNativeSdkExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14CAA8009BEAF5BC437B0010 /* 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 = ""; }; 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 3B4392A12AC88292D35C810B /* 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 = ""; }; - 5709B34CF0A7D63546082F79 /* 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 = ""; }; - 5B7EB9410499542E8C5724F5 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig"; sourceTree = ""; }; - 5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B9AD4590D711AC8FE0EDDC9 /* 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 = ""; }; + 3C67BBC613DBECB7B754E015 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig"; sourceTree = ""; }; + 6E8A570F33713FDF3AAC4551 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeSdkExample/LaunchScreen.storyboard; sourceTree = ""; }; - 89C6BE57DB24E9ADA2F236DE /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig"; sourceTree = ""; }; + A7CA06A41BBE122C9FCB3514 /* Pods_ReactNativeSdkExample_ReactNativeSdkExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReactNativeSdkExample_ReactNativeSdkExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -54,7 +54,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7699B88040F8A987B510C191 /* libPods-ReactNativeSdkExample-ReactNativeSdkExampleTests.a in Frameworks */, + 75E0FCF4B434AB4B3BC1160B /* Pods_ReactNativeSdkExample_ReactNativeSdkExampleTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -62,7 +62,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0C80B921A6F3F58F76C31292 /* libPods-ReactNativeSdkExample.a in Frameworks */, + 2A3E6B2C5E705A7EDF686E84 /* Pods_ReactNativeSdkExample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -105,8 +105,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeSdkExample.a */, - 19F6CBCC0A4E27FBF8BF4A61 /* libPods-ReactNativeSdkExample-ReactNativeSdkExampleTests.a */, + 05E876B5261C6746E1A77B06 /* Pods_ReactNativeSdkExample.framework */, + A7CA06A41BBE122C9FCB3514 /* Pods_ReactNativeSdkExample_ReactNativeSdkExampleTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -145,10 +145,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 3B4392A12AC88292D35C810B /* Pods-ReactNativeSdkExample.debug.xcconfig */, - 5709B34CF0A7D63546082F79 /* Pods-ReactNativeSdkExample.release.xcconfig */, - 5B7EB9410499542E8C5724F5 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig */, - 89C6BE57DB24E9ADA2F236DE /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig */, + 3B9AD4590D711AC8FE0EDDC9 /* Pods-ReactNativeSdkExample.debug.xcconfig */, + 14CAA8009BEAF5BC437B0010 /* Pods-ReactNativeSdkExample.release.xcconfig */, + 3C67BBC613DBECB7B754E015 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig */, + 6E8A570F33713FDF3AAC4551 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -160,12 +160,12 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ReactNativeSdkExampleTests" */; buildPhases = ( - A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */, + 6CEECA2297B65FEA3E3A0A5F /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */, - F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */, + B6F5333CD2C49B6E805773FC /* [CP] Embed Pods Frameworks */, + 1B6A03EDB91FE20BE49C8BD1 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -181,13 +181,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeSdkExample" */; buildPhases = ( - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, + 5BA701BAE553BB4F805F9754 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, + 45FCB159FC0EA01CA5688140 /* [CP] Embed Pods Frameworks */, + EA19910410B762283E74B168 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -271,7 +271,24 @@ 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"; }; - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { + 1B6A03EDB91FE20BE49C8BD1 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 45FCB159FC0EA01CA5688140 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -288,7 +305,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = { + 5BA701BAE553BB4F805F9754 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -303,14 +320,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-checkManifestLockResult.txt", + "$(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; }; - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { + 6CEECA2297B65FEA3E3A0A5F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -325,14 +342,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-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; }; - C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */ = { + B6F5333CD2C49B6E805773FC /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -349,7 +366,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { + EA19910410B762283E74B168 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -366,23 +383,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n"; showEnvVarsInLog = 0; }; - F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests/Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -416,7 +416,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig */; + baseConfigurationReference = 3C67BBC613DBECB7B754E015 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -443,7 +443,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig */; + baseConfigurationReference = 6E8A570F33713FDF3AAC4551 /* Pods-ReactNativeSdkExample-ReactNativeSdkExampleTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; @@ -467,7 +467,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-ReactNativeSdkExample.debug.xcconfig */; + baseConfigurationReference = 3B9AD4590D711AC8FE0EDDC9 /* Pods-ReactNativeSdkExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -494,7 +494,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-ReactNativeSdkExample.release.xcconfig */; + baseConfigurationReference = 14CAA8009BEAF5BC437B0010 /* Pods-ReactNativeSdkExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -568,6 +568,17 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", + ); IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD = ""; LDPLUSPLUS = ""; @@ -645,6 +656,17 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", + ); IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD = ""; LDPLUSPLUS = ""; diff --git a/example/package.json b/example/package.json index 4b74b0703..d25ddb9d0 100644 --- a/example/package.json +++ b/example/package.json @@ -16,7 +16,7 @@ "@react-navigation/stack": "^6.4.1", "react": "18.3.1", "react-native": "0.75.3", - "react-native-gesture-handler": "^2.20.1", + "react-native-gesture-handler": "^2.25.0", "react-native-safe-area-context": "^4.11.1", "react-native-screens": "^3.34.0", "react-native-vector-icons": "^10.2.0", diff --git a/example/react-native.config.js b/example/react-native.config.js index 0b9606d4a..59d969820 100644 --- a/example/react-native.config.js +++ b/example/react-native.config.js @@ -10,6 +10,12 @@ module.exports = { dependencies: { [pkg.name]: { root: path.join(__dirname, '..'), + platforms: { + // Codegen script incorrectly fails without this + // So we explicitly specify the platforms with empty object + ios: {}, + android: {}, + }, }, }, }; diff --git a/example/src/App.tsx b/example/src/App.tsx new file mode 100644 index 000000000..c6ed07941 --- /dev/null +++ b/example/src/App.tsx @@ -0,0 +1,184 @@ +import React, {useEffect, useState} from 'react'; +import { + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + View, + Button, + Platform, +} from 'react-native'; +import { + Iterable, + IterableInbox, + IterableInAppMessage, +} from '@iterable/react-native-sdk'; + +const App = () => { + const [inAppMessages, setInAppMessages] = useState([]); + const [email, setEmail] = useState(''); + + useEffect(() => { + // Initialize Iterable SDK + Iterable.initialize('YOUR_API_KEY'); + + // Set up event listeners + const unsubscribe = Iterable.onInAppReceived((message: IterableInAppMessage) => { + console.log('In-app message received:', message); + setInAppMessages(prev => [...prev, message]); + }); + + return () => { + unsubscribe(); + }; + }, []); + + const handleLogin = async () => { + try { + await Iterable.setEmail('user@example.com'); + const currentEmail = await Iterable.getEmail(); + if (currentEmail) { + setEmail(currentEmail); + } + } catch (error) { + console.error('Login error:', error); + } + }; + + const handleTrackEvent = () => { + Iterable.trackEvent('button_clicked', { + buttonName: 'track_event', + timestamp: new Date().toISOString(), + }); + }; + + const handleUpdateUser = () => { + Iterable.updateUser({ + firstName: 'John', + lastName: 'Doe', + customField: 'customValue', + }); + }; + + return ( + + + + + Iterable SDK Example + Architecture: {Platform.OS === 'ios' ? 'Fabric' : 'TurboModule'} + + +