diff --git a/android/build.gradle b/android/build.gradle index 2178160d..53eb7495 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,6 +15,7 @@ buildscript { apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: "com.facebook.react" def getExtOrDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['customerio.reactnative.' + name] @@ -24,32 +25,12 @@ def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['customerio.reactnative.' + name]).toInteger() } -def isNewArchitectureEnabled() { - // Allow customers to override new architecture setting specifically for Customer.io SDK. - // This is useful as a workaround for apps where the new architecture has issues. - // Customers can add 'customerioNewArchEnabled=false' in their gradle.properties - // to force the SDK to use old architecture even when their app has newArchEnabled=true. - // This can be helpful for customers who are using the new architecture in their app, but are on older - // versions of React Native than the one used by the SDK to generate the codegen files. - if (project.hasProperty("customerioNewArchEnabled")) { - return project.customerioNewArchEnabled == "true" - } - // Otherwise, use react-native's newArchEnabled property. - return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" -} - -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} - android { namespace = 'io.customer.reactnative.sdk' compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') defaultConfig { - def isNewArchEnabled = isNewArchitectureEnabled() minSdkVersion getExtOrIntegerDefault('minSdkVersion') targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') - buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchEnabled.toString()) } buildTypes { @@ -64,16 +45,6 @@ android { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } - sourceSets { - main { - if (isNewArchitectureEnabled()) { - // Include both new architecture source directories and codegen generated files - java.srcDirs += ['src/newarch'] - } else { - java.srcDirs += ['src/oldarch'] - } - } - } } repositories { diff --git a/android/gradle.properties b/android/gradle.properties index 8ae0d762..cfc2a417 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -2,4 +2,4 @@ customerio.reactnative.kotlinVersion=2.1.20 customerio.reactnative.compileSdkVersion=36 customerio.reactnative.targetSdkVersion=36 customerio.reactnative.minSdkVersion=21 -customerio.reactnative.cioSDKVersionAndroid=4.13.0 +customerio.reactnative.cioSDKVersionAndroid=4.14.0 diff --git a/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt b/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt new file mode 100644 index 00000000..569df330 --- /dev/null +++ b/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt @@ -0,0 +1,78 @@ +package io.customer.reactnative.sdk + +import com.facebook.react.BaseReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.uimanager.ViewManager +import io.customer.reactnative.sdk.logging.NativeCustomerIOLoggingModule +import io.customer.reactnative.sdk.messaginginapp.InlineInAppMessageViewManager +import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModule +import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModule +import io.customer.reactnative.sdk.util.assertNotNull + +/** + * React Native package for Customer.io SDK that registers all TurboModules and ViewManagers. + * Implements new architecture support for React Native. + */ +class CustomerIOReactNativePackage : BaseReactPackage() { + /** + * Creates the list of view managers for the Customer.io React Native SDK. + */ + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return listOf(InlineInAppMessageViewManager()) + } + + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + // Debugging reveals that this method is never called for ViewManagers. + // But since ReactNative docs recommend overriding it, we do so here for ViewManagers. + // See: https://reactnative.dev/docs/fabric-native-components-introduction?platforms=android#4-write-the-reactwebviewpackage + return when (name) { + InlineInAppMessageViewManager.NAME -> InlineInAppMessageViewManager() + NativeCustomerIOLoggingModule.NAME -> NativeCustomerIOLoggingModule(reactContext) + NativeCustomerIOModule.NAME -> NativeCustomerIOModule(reactContext = reactContext) + NativeMessagingInAppModule.NAME -> NativeMessagingInAppModule(reactContext) + NativeMessagingPushModule.NAME -> NativeMessagingPushModule(reactContext) + else -> assertNotNull(value = null) { "Unknown module name: $name" } + } + } + + /** + * Creates a ReactModuleInfo for React module registration with the given configuration. + * Using positional arguments instead of named arguments as named args break on RN 0.76. + */ + private fun createReactModuleInfo( + name: String, + className: String = name, + canOverrideExistingModule: Boolean = false, + needsEagerInit: Boolean = false, + isCxxModule: Boolean = false, + isTurboModule: Boolean = true, + ) = ReactModuleInfo( + name, + className, + canOverrideExistingModule, + needsEagerInit, + isCxxModule, + isTurboModule, + ) + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + // List of all Fabric ViewManagers and TurboModules registered in this package. + // Used by React Native to identify and instantiate the modules. + val moduleNames: List = listOf( + InlineInAppMessageViewManager.NAME, + NativeCustomerIOLoggingModule.NAME, + NativeCustomerIOModule.NAME, + NativeMessagingInAppModule.NAME, + NativeMessagingPushModule.NAME, + ) + return ReactModuleInfoProvider { + // Register all ViewManagers and TurboModules + moduleNames.associateWith { moduleName -> + createReactModuleInfo(name = moduleName) + } + } + } +} diff --git a/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackageImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackageImpl.kt deleted file mode 100644 index 03d85409..00000000 --- a/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackageImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ViewManager -import io.customer.reactnative.sdk.logging.NativeCustomerIOLoggingModule -import io.customer.reactnative.sdk.logging.NativeCustomerIOLoggingModuleImpl -import io.customer.reactnative.sdk.messaginginapp.InlineInAppMessageViewManager -import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModule -import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModuleImpl -import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModule -import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModuleImpl -import io.customer.reactnative.sdk.util.assertNotNull - -/** - * Registry and factory for Customer.io React Native SDK modules and view managers. - * Provides centralized module creation for both old and new architecture. - * - * This object serves as the single source of truth for all TurboModule registrations - * and their corresponding factory methods. - */ -internal object CustomerIOReactNativePackageImpl { - val turboModuleNames: List - get() = listOf( - NativeCustomerIOLoggingModuleImpl.NAME, - NativeCustomerIOModuleImpl.NAME, - NativeMessagingInAppModuleImpl.NAME, - NativeMessagingPushModuleImpl.NAME, - ) - - @Suppress("UNCHECKED_CAST") - fun createNativeModule( - reactContext: ReactApplicationContext, - name: String - ): M? = when (name) { - NativeCustomerIOLoggingModuleImpl.NAME -> NativeCustomerIOLoggingModule(reactContext) - NativeCustomerIOModuleImpl.NAME -> NativeCustomerIOModule(reactContext = reactContext) - NativeMessagingInAppModuleImpl.NAME -> NativeMessagingInAppModule(reactContext) - NativeMessagingPushModuleImpl.NAME -> NativeMessagingPushModule(reactContext) - else -> assertNotNull(value = null) { "Unknown module name: $name" } - } as? M - - /** - * Creates the list of view managers for the Customer.io React Native SDK. - */ - fun createViewManagers(reactContext: ReactApplicationContext): List> { - return listOf(InlineInAppMessageViewManager()) - } -} diff --git a/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt similarity index 78% rename from android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModuleImpl.kt rename to android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt index 1282c2d2..4e1cba09 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModuleImpl.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt @@ -8,8 +8,8 @@ import io.customer.datapipelines.config.ScreenView import io.customer.reactnative.sdk.constant.Keys import io.customer.reactnative.sdk.extension.getTypedValue import io.customer.reactnative.sdk.extension.toMap -import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModuleImpl -import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModuleImpl +import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModule +import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModule import io.customer.reactnative.sdk.util.assertNotNull import io.customer.sdk.CustomerIO import io.customer.sdk.CustomerIOBuilder @@ -22,13 +22,12 @@ import io.customer.sdk.events.TrackMetric import io.customer.sdk.events.serializedName /** - * Shared implementation logic for Customer.io Native SDK communication in React Native. - * Contains actual business logic used by both old and new architecture [NativeCustomerIOModule] classes. - * Handles SDK initialization, user identification, event tracking, and device management. + * React Native module implementation for Customer.io Native SDK + * using TurboModules with new architecture. */ -internal object NativeCustomerIOModuleImpl { - const val NAME = "NativeCustomerIO" - +class NativeCustomerIOModule( + private val reactContext: ReactApplicationContext, +) : NativeCustomerIOSpec(reactContext) { private val logger: Logger get() = SDKComponent.logger @@ -45,11 +44,8 @@ internal object NativeCustomerIOModuleImpl { logger.error("CustomerIO SDK is not initialized. Please call initialize() first.") } - fun initialize( - reactContext: ReactApplicationContext, - sdkConfig: ReadableMap?, - promise: Promise? - ) { + + override fun initialize(config: ReadableMap?, args: ReadableMap?, promise: Promise?) { // Skip initialization if already initialized if (getSDKInstanceOrNull() != null) { logger.info("CustomerIO SDK is already initialized. Skipping initialization.") @@ -58,7 +54,7 @@ internal object NativeCustomerIOModuleImpl { } try { - val packageConfig = sdkConfig.toMap() + val packageConfig = config.toMap() val cdpApiKey = packageConfig.getTypedValue( Keys.Config.CDP_API_KEY ) ?: throw IllegalArgumentException("CDP API Key is required to initialize Customer.io") @@ -90,14 +86,14 @@ internal object NativeCustomerIOModuleImpl { // Configure push messaging module based on config provided by customer app packageConfig.getTypedValue>(key = "push").let { pushConfig -> - NativeMessagingPushModuleImpl.addNativeModuleFromConfig( + NativeMessagingPushModule.addNativeModuleFromConfig( builder = this, config = pushConfig ?: emptyMap() ) } // Configure in-app messaging module based on config provided by customer app packageConfig.getTypedValue>(key = "inApp")?.let { inAppConfig -> - NativeMessagingInAppModuleImpl.addNativeModuleFromConfig( + NativeMessagingInAppModule.addNativeModuleFromConfig( builder = this, config = inAppConfig, region = region @@ -113,11 +109,7 @@ internal object NativeCustomerIOModuleImpl { } } - fun clearIdentify() { - requireSDKInstance()?.clearIdentify() - } - - fun identify(params: ReadableMap?) { + override fun identify(params: ReadableMap?) { val userId = params?.getString("userId") val traits = params?.getMap("traits") @@ -129,51 +121,55 @@ internal object NativeCustomerIOModuleImpl { userId?.let { requireSDKInstance()?.identify(userId, traits.toMap()) } ?: run { - requireSDKInstance()?.profileAttributes = traits.toMap() + requireSDKInstance()?.setProfileAttributes(traits.toMap()) } } - fun track(name: String?, properties: ReadableMap?) { + override fun clearIdentify() { + requireSDKInstance()?.clearIdentify() + } + + override fun track(name: String?, properties: ReadableMap?) { val eventName = assertNotNull(name) ?: return requireSDKInstance()?.track(eventName, properties.toMap()) } - fun setDeviceAttributes(attributes: ReadableMap?) { - requireSDKInstance()?.deviceAttributes = attributes.toMap() - } + override fun screen(title: String?, properties: ReadableMap?) { + val screenTitle = assertNotNull(title) ?: return - fun setProfileAttributes(attributes: ReadableMap?) { - requireSDKInstance()?.profileAttributes = attributes.toMap() + requireSDKInstance()?.screen(screenTitle, properties.toMap()) } - fun screen(title: String?, properties: ReadableMap?) { - val screenTitle = assertNotNull(title) ?: return + override fun setProfileAttributes(attributes: ReadableMap?) { + requireSDKInstance()?.setProfileAttributes(attributes.toMap()) + } - requireSDKInstance()?.screen(screenTitle, properties.toMap()) + override fun setDeviceAttributes(attributes: ReadableMap?) { + requireSDKInstance()?.setDeviceAttributes(attributes.toMap()) } - fun registerDeviceToken(token: String?) { + override fun registerDeviceToken(token: String?) { val deviceToken = assertNotNull(token) ?: return requireSDKInstance()?.registerDeviceToken(deviceToken) } - fun trackMetric(deliveryId: String?, deviceToken: String?, eventName: String?) { + override fun trackMetric(deliveryID: String?, deviceToken: String?, event: String?) { try { - if (deliveryId == null || deviceToken == null || eventName == null) { + if (deliveryID == null || deviceToken == null || event == null) { throw IllegalArgumentException("Missing required parameters") } - val event = Metric.values().find { - it.serializedName.equals(eventName, true) + val metric = Metric.values().find { + it.serializedName.equals(event, true) } ?: throw IllegalArgumentException("Invalid metric event name") requireSDKInstance()?.trackMetric( event = TrackMetric.Push( - deliveryId = deliveryId, + deliveryId = deliveryID, deviceToken = deviceToken, - metric = event + metric = metric ) ) } catch (e: Exception) { @@ -181,7 +177,11 @@ internal object NativeCustomerIOModuleImpl { } } - fun deleteDeviceToken() { + override fun deleteDeviceToken() { requireSDKInstance()?.deleteDeviceToken() } + + companion object { + internal const val NAME = "NativeCustomerIO" + } } diff --git a/android/src/newarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt b/android/src/main/java/io/customer/reactnative/sdk/extension/ViewExtensions.kt similarity index 100% rename from android/src/newarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt rename to android/src/main/java/io/customer/reactnative/sdk/extension/ViewExtensions.kt diff --git a/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt b/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt new file mode 100644 index 00000000..a2b4fe0d --- /dev/null +++ b/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt @@ -0,0 +1,90 @@ +package io.customer.reactnative.sdk.logging + +import android.util.Log +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import io.customer.reactnative.sdk.NativeCustomerIOLoggingSpec +import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.core.util.CioLogLevel + +/** + * React Native module implementation for Customer.io Logging Native SDK + * using TurboModules with new architecture. + */ +class NativeCustomerIOLoggingModule( + reactContext: ReactApplicationContext, +) : NativeCustomerIOLoggingSpec(reactContext) { + override fun getName(): String = NAME + + // Log event emitter function to send events to React Native layer + private var logEventEmitter: ((ReadableMap) -> Unit)? = null + + /** + * Executes the given block and logs any uncaught exceptions using Android logger to protect + * against unexpected crashes and failures. + */ + private fun runWithTryCatch(action: () -> Unit) { + try { + action() + } catch (ex: Exception) { + // Use Android logger to avoid cyclic calls from internal SDK logging + Log.e("[CIO]", "Error in NativeCustomerIOLoggingModule: ${ex.message}", ex) + } + } + + override fun initialize() { + runWithTryCatch { + super.initialize() + setLogEventEmitter { data -> + emitOnCioLogEvent(data) + } + } + } + + override fun invalidate() { + runWithTryCatch { + cleanupLogEventEmitter() + super.invalidate() + } + } + + private fun cleanupLogEventEmitter() { + runWithTryCatch { + // Clear log dispatcher to prevent memory leaks and further events + SDKComponent.logger.setLogDispatcher(null) + this.logEventEmitter = null + } + } + + // Sets the event emitter function used to send log events to React Native. + private fun setLogEventEmitter(emitter: ((ReadableMap) -> Unit)?) { + if (emitter == null) { + // Clear log dispatcher if emitter is null + cleanupLogEventEmitter() + return + } + + SDKComponent.logger.setLogDispatcher { level, message -> + emitLogEvent(level, message) + } + this.logEventEmitter = emitter + } + + // Converts native SDK log events to React Native compatible format and emits them. + private fun emitLogEvent(level: CioLogLevel, message: String) { + // Defensive check: only emit if emitter is available + val emitter = logEventEmitter ?: return + + val data = buildMap { + put("logLevel", level.name.lowercase()) + put("message", message) + } + + emitter.invoke(Arguments.makeNativeMap(data)) + } + + companion object { + internal const val NAME = "NativeCustomerIOLogging" + } +} diff --git a/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModuleImpl.kt deleted file mode 100644 index bc275be8..00000000 --- a/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModuleImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.customer.reactnative.sdk.logging - -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.ReadableMap -import io.customer.sdk.core.di.SDKComponent -import io.customer.sdk.core.util.CioLogLevel - -/** - * Shared implementation for Customer.io Logging Native SDK module. - * Handles log event dispatching from the native SDK to React Native layer. - * Contains the actual business logic used by both new and old architecture modules. - */ -object NativeCustomerIOLoggingModuleImpl { - const val NAME = "NativeCustomerIOLogging" - - // Log event emitter function to send events to React Native layer - private var logEventEmitter: ((ReadableMap) -> Unit)? = null - - // Sets the event emitter function used to send log events to React Native. - internal fun setLogEventEmitter(emitter: ((ReadableMap) -> Unit)?) { - // Set up log dispatcher only when first emitter is set - if (emitter != null && logEventEmitter == null) { - SDKComponent.logger.setLogDispatcher { level, message -> - emitLogEvent(level, message) - } - } - - this.logEventEmitter = emitter - } - - // Clears the event emitter, should be called during module cleanup - internal fun invalidate() { - // Clear log dispatcher to prevent memory leaks and further events - SDKComponent.logger.setLogDispatcher(null) - this.logEventEmitter = null - } - - // Converts native SDK log events to React Native compatible format and emits them. - private fun emitLogEvent(level: CioLogLevel, message: String) { - // Defensive check: only emit if emitter is available - val emitter = logEventEmitter ?: return - - val data = buildMap { - put("logLevel", level.name.lowercase()) - put("message", message) - } - - emitter.invoke(Arguments.makeNativeMap(data)) - } -} diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/BaseInlineInAppMessageViewManager.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt similarity index 59% rename from android/src/main/java/io/customer/reactnative/sdk/messaginginapp/BaseInlineInAppMessageViewManager.kt rename to android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt index d45511b3..c976e0e9 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/BaseInlineInAppMessageViewManager.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt @@ -1,8 +1,12 @@ package io.customer.reactnative.sdk.messaginginapp +import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.InlineMessageNativeManagerDelegate +import com.facebook.react.viewmanagers.InlineMessageNativeManagerInterface import io.customer.messaginginapp.ui.bridge.WrapperPlatformDelegate /** @@ -11,12 +15,17 @@ import io.customer.messaginginapp.ui.bridge.WrapperPlatformDelegate * Provides common functionality for both old and new React Native architecture * implementations, including view creation, event handling, and property management. */ -abstract class BaseInlineInAppMessageViewManager : +@ReactModule(name = InlineInAppMessageViewManager.NAME) +class InlineInAppMessageViewManager : + InlineMessageNativeManagerInterface, SimpleViewManager() { + private val delegate = InlineMessageNativeManagerDelegate(this) + override fun getName() = NAME + override fun getDelegate(): ViewManagerDelegate = delegate - override fun createViewInstance(context: ThemedReactContext): ReactInlineInAppMessageView { - return ReactInlineInAppMessageView(context) + override fun createViewInstance(reactContext: ThemedReactContext): ReactInlineInAppMessageView { + return ReactInlineInAppMessageView(reactContext) } /** @@ -25,7 +34,7 @@ abstract class BaseInlineInAppMessageViewManager : * - onSizeChange: Triggered when the size of the inline message changes. * - onStateChange: Triggered when the state of the inline message changes. */ - override fun getExportedCustomDirectEventTypeConstants(): MutableMap? { + override fun getExportedCustomDirectEventTypeConstants(): Map { val customEvents = super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf() val registerCustomEvent = { eventName: String -> customEvents.put(eventName, mapOf("registrationName" to eventName)) @@ -37,11 +46,11 @@ abstract class BaseInlineInAppMessageViewManager : } @ReactProp(name = "elementId") - fun setElementId(view: ReactInlineInAppMessageView, elementId: String?) { - view.elementId = elementId + override fun setElementId(view: ReactInlineInAppMessageView?, value: String?) { + view?.elementId = value } companion object { - const val NAME = "InlineMessageNative" + internal const val NAME = "InlineMessageNative" } } diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt new file mode 100644 index 00000000..fbd2cc85 --- /dev/null +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt @@ -0,0 +1,73 @@ +package io.customer.reactnative.sdk.messaginginapp + +import com.facebook.react.bridge.ReactApplicationContext +import io.customer.messaginginapp.MessagingInAppModuleConfig +import io.customer.messaginginapp.ModuleMessagingInApp +import io.customer.messaginginapp.di.inAppMessaging +import io.customer.reactnative.sdk.NativeCustomerIOMessagingInAppSpec +import io.customer.reactnative.sdk.constant.Keys +import io.customer.reactnative.sdk.extension.getTypedValue +import io.customer.sdk.CustomerIO +import io.customer.sdk.CustomerIOBuilder +import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.data.model.Region + +/** + * React Native module implementation for Customer.io In-App Messaging Native SDK + * using TurboModules with new architecture. + */ +class NativeMessagingInAppModule( + reactContext: ReactApplicationContext, +) : NativeCustomerIOMessagingInAppSpec(reactContext) { + private val inAppMessagingModule: ModuleMessagingInApp? + get() = kotlin.runCatching { CustomerIO.instance().inAppMessaging() }.getOrNull() + + private val inAppEventListener = ReactInAppEventListener.instance + + override fun initialize() { + super.initialize() + inAppEventListener.setEventEmitter { data -> + emitOnInAppEventReceived(data) + } + } + + override fun invalidate() { + inAppEventListener.clearEventEmitter() + super.invalidate() + } + + override fun dismissMessage() { + inAppMessagingModule?.dismissMessage() + } + + companion object { + internal const val NAME = "NativeCustomerIOMessagingInApp" + + /** + * Adds InAppMessaging module to native Android SDK based on configuration provided by customer + * app. + * + * @param builder CustomerIOBuilder instance to add InAppMessaging module + * @param config Configuration provided by customer app for InAppMessaging module + * @param region Region to be used for InAppMessaging module + */ + internal fun addNativeModuleFromConfig( + builder: CustomerIOBuilder, + config: Map, + region: Region + ) { + val siteId = config.getTypedValue(Keys.Config.SITE_ID) + if (siteId.isNullOrBlank()) { + SDKComponent.logger.error("Site ID is required to initialize InAppMessaging module") + return + } + + val module = ModuleMessagingInApp( + MessagingInAppModuleConfig.Builder(siteId = siteId, region = region).apply { + setEventListener(eventListener = ReactInAppEventListener.instance) + }.build(), + ) + builder.addCustomerIOModule(module) + } + } +} diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModuleImpl.kt deleted file mode 100644 index 2a6d0c65..00000000 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModuleImpl.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -import io.customer.messaginginapp.MessagingInAppModuleConfig -import io.customer.messaginginapp.ModuleMessagingInApp -import io.customer.messaginginapp.di.inAppMessaging -import io.customer.reactnative.sdk.constant.Keys -import io.customer.reactnative.sdk.extension.getTypedValue -import io.customer.sdk.CustomerIO -import io.customer.sdk.CustomerIOBuilder -import io.customer.sdk.core.di.SDKComponent -import io.customer.sdk.data.model.Region - -/** - * Shared implementation for Customer.io In-App Messaging Native SDK module. - * Contains the actual business logic used by both new and old architecture modules. - */ -object NativeMessagingInAppModuleImpl { - const val NAME = "NativeCustomerIOMessagingInApp" - - private val inAppMessagingModule: ModuleMessagingInApp? - get() = kotlin.runCatching { CustomerIO.instance().inAppMessaging() }.getOrNull() - val inAppEventListener = ReactInAppEventListener() - - /** - * Adds InAppMessaging module to native Android SDK based on configuration provided by customer - * app. - * - * @param builder CustomerIOBuilder instance to add InAppMessaging module - * @param config Configuration provided by customer app for InAppMessaging module - * @param region Region to be used for InAppMessaging module - */ - fun addNativeModuleFromConfig( - builder: CustomerIOBuilder, - config: Map, - region: Region - ) { - val siteId = config.getTypedValue(Keys.Config.SITE_ID) - if (siteId.isNullOrBlank()) { - SDKComponent.logger.error("Site ID is required to initialize InAppMessaging module") - return - } - - val module = ModuleMessagingInApp( - MessagingInAppModuleConfig.Builder(siteId = siteId, region = region).apply { - setEventListener(eventListener = inAppEventListener) - }.build(), - ) - builder.addCustomerIOModule(module) - } - - /** - * Dismisses any currently displayed in-app message - */ - fun dismissMessage() { - inAppMessagingModule?.dismissMessage() - } -} diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt index f38fed99..e6ee6451 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt @@ -9,7 +9,7 @@ import io.customer.messaginginapp.type.InAppMessage * React Native bridge for Customer.io in-app messaging events. * Converts native SDK events to JavaScript compatible format. */ -class ReactInAppEventListener : InAppEventListener { +class ReactInAppEventListener private constructor() : InAppEventListener { // Event emitter function to send events to React Native layer private var eventEmitter: ((ReadableMap) -> Unit)? = null @@ -30,6 +30,9 @@ class ReactInAppEventListener : InAppEventListener { actionValue: String? = null, actionName: String? = null, ) { + // Get the emitter, return early if not set + val emitter = eventEmitter ?: return + val data = buildMap { put("eventType", eventType) put("messageId", message.messageId) @@ -38,7 +41,7 @@ class ReactInAppEventListener : InAppEventListener { actionName?.let { put("actionName", it) } } - eventEmitter?.invoke(Arguments.makeNativeMap(data)) + emitter.invoke(Arguments.makeNativeMap(data)) } override fun errorWithMessage(message: InAppMessage) = emitInAppEvent( @@ -66,4 +69,9 @@ class ReactInAppEventListener : InAppEventListener { eventType = "messageShown", message = message, ) + + companion object { + // Singleton instance with public visibility for direct access by Expo plugin + val instance: ReactInAppEventListener by lazy { ReactInAppEventListener() } + } } diff --git a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt similarity index 66% rename from android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModuleImpl.kt rename to android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt index bc8398c8..9633511c 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModuleImpl.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt @@ -8,6 +8,7 @@ import android.os.Build import androidx.core.content.ContextCompat import com.facebook.react.bridge.ActivityEventListener import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReadableMap import com.facebook.react.modules.core.PermissionAwareActivity @@ -18,9 +19,11 @@ import io.customer.messagingpush.ModuleMessagingPushFCM import io.customer.messagingpush.config.PushClickBehavior import io.customer.messagingpush.di.pushModuleConfig import io.customer.messagingpush.di.pushTrackingUtil +import io.customer.reactnative.sdk.NativeCustomerIOMessagingPushSpec import io.customer.reactnative.sdk.constant.Keys import io.customer.reactnative.sdk.extension.getTypedValue import io.customer.reactnative.sdk.extension.takeIfNotBlank +import io.customer.reactnative.sdk.util.unsupportedOnAndroid import io.customer.sdk.CustomerIO import io.customer.sdk.CustomerIOBuilder import io.customer.sdk.core.di.SDKComponent @@ -28,23 +31,14 @@ import io.customer.sdk.core.util.Logger import java.util.UUID /** - * Shared implementation for Customer.io Push Messaging Native SDK module. - * Contains the actual business logic used by both new and old architecture modules. + * React Native module implementation for Customer.io Push Messaging Native SDK using + * TurboModules with new architecture. */ -object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener { - const val NAME = "NativeCustomerIOMessagingPush" - - /** - * Copying value os [Manifest.permission.POST_NOTIFICATIONS] as constant so we don't have to - * force newer compile sdk versions - */ - private const val POST_NOTIFICATIONS_PERMISSION_NAME = "android.permission.POST_NOTIFICATIONS" - private const val BUILD_VERSION_CODE_TIRAMISU = 33 - private const val POST_NOTIFICATIONS_PERMISSION_REQUEST = 24676 - - private val logger: Logger = SDKComponent.logger - private val pushModuleConfig: MessagingPushModuleConfig - get() = SDKComponent.pushModuleConfig +class NativeMessagingPushModule( + private val reactContext: ReactApplicationContext, +) : NativeCustomerIOMessagingPushSpec(reactContext), PermissionListener, ActivityEventListener { + private val logger: Logger + get() = SDKComponent.logger /** * Temporarily holds reference for notification request as the request is dependent on Android @@ -52,40 +46,17 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener */ private var notificationRequestPromise: Promise? = null - /** - * Adds push messaging module to native Android SDK based on the configuration provided by - * customer app. - * - * @param builder instance of CustomerIOBuilder to add push messaging module. - * @param config configuration provided by customer app for push messaging module. - */ - fun addNativeModuleFromConfig( - builder: CustomerIOBuilder, - config: Map - ) { - val androidConfig = config.getTypedValue>(key = "android") ?: emptyMap() - // Prefer `android` object for push configurations as it's more specific to Android - // For common push configurations, use `config` object instead of `android` - - // Default push click behavior is to prevent restart of activity in React Native apps - val pushClickBehavior = androidConfig.getTypedValue(Keys.Config.PUSH_CLICK_BEHAVIOR) - ?.takeIfNotBlank() - ?.let { value -> - runCatching { enumValueOf(value) }.getOrNull() - } ?: PushClickBehavior.ACTIVITY_PREVENT_RESTART + override fun initialize() { + super.initialize() + reactContext.addActivityEventListener(this) + } - val module = ModuleMessagingPushFCM( - moduleConfig = MessagingPushModuleConfig.Builder().apply { - setPushClickBehavior(pushClickBehavior = pushClickBehavior) - }.build(), - ) - builder.addCustomerIOModule(module) + override fun invalidate() { + reactContext.removeActivityEventListener(this) + super.invalidate() } - fun getPushPermissionStatus( - reactContext: ReactContext, - promise: Promise?, - ) { + override fun getPushPermissionStatus(promise: Promise?) { val result = checkPushPermissionStatus(reactContext).toReactNativeResult promise?.resolve(result) } @@ -96,15 +67,11 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener * For newer versions, the permission is requested and the promise is resolved after the request * has been completed. * - * @param pushConfigurationOptions configurations options for push notifications, required for - * iOS only, unused on Android. + * @param options configurations options for push notifications, required for iOS only, + * unused on Android. * @param promise to resolve and return the results. */ - fun showPromptForPushNotifications( - reactContext: ReactContext, - pushConfigurationOptions: ReadableMap?, - promise: Promise?, - ) { + override fun showPromptForPushNotifications(options: ReadableMap?, promise: Promise?) { // Skip requesting permissions when already granted if (checkPushPermissionStatus(reactContext) == PermissionStatus.Granted) { promise?.resolve(PermissionStatus.Granted.toReactNativeResult) @@ -141,8 +108,7 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener * @param message push payload received from FCM. * @param handleNotificationTrigger indicating if the local notification should be triggered. */ - fun onMessageReceived( - reactContext: ReactContext, + override fun onMessageReceived( message: ReadableMap?, handleNotificationTrigger: Boolean, promise: Promise?, @@ -168,12 +134,20 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener } } + override fun trackNotificationResponseReceived(payload: ReadableMap?) { + unsupportedOnAndroid(methodName = "trackNotificationResponseReceived") + } + + override fun trackNotificationReceived(payload: ReadableMap?) { + unsupportedOnAndroid(methodName = "trackNotificationReceived") + } + /** * Get the registered device token for the app. * @returns Promise with device token as a string, or error if no token is * registered or the method fails to fetch token. */ - fun getRegisteredDeviceToken(promise: Promise?) { + override fun getRegisteredDeviceToken(promise: Promise?) { try { // Get the device token from SDK val deviceToken: String? = CustomerIO.instance().registeredDeviceToken @@ -195,13 +169,14 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener /** * Checks current permission of push notification permission */ - private fun checkPushPermissionStatus(reactContext: ReactContext): PermissionStatus = + private fun checkPushPermissionStatus(reactContext: ReactContext): PermissionStatus { // Skip requesting permissions for older versions where not required - if (Build.VERSION.SDK_INT < BUILD_VERSION_CODE_TIRAMISU || ContextCompat.checkSelfPermission( + return if (Build.VERSION.SDK_INT < BUILD_VERSION_CODE_TIRAMISU || ContextCompat.checkSelfPermission( reactContext, POST_NOTIFICATIONS_PERMISSION_NAME, ) == PackageManager.PERMISSION_GRANTED ) PermissionStatus.Granted else PermissionStatus.Denied + } /** * Resolves and clears promise with the provided permission status @@ -212,7 +187,9 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener } override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray, + requestCode: Int, + permissions: Array, + grantResults: IntArray, ): Boolean = when (requestCode) { POST_NOTIFICATIONS_PERMISSION_REQUEST -> { // If request is cancelled, the result arrays are empty. @@ -256,9 +233,51 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener } } - /** - * Maps native class to react native supported type so the result can be passed on to JS/TS classes. - */ - private val PermissionStatus.toReactNativeResult: Any - get() = this.name.uppercase() + companion object { + const val NAME = "NativeCustomerIOMessagingPush" + + /** + * Copying value os [Manifest.permission.POST_NOTIFICATIONS] as constant so we don't have to + * force newer compile sdk versions + */ + private const val POST_NOTIFICATIONS_PERMISSION_NAME = + "android.permission.POST_NOTIFICATIONS" + private const val BUILD_VERSION_CODE_TIRAMISU = 33 + private const val POST_NOTIFICATIONS_PERMISSION_REQUEST = 24676 + + private val pushModuleConfig: MessagingPushModuleConfig + get() = SDKComponent.pushModuleConfig + + /** + * Adds push messaging module to native Android SDK based on the configuration provided by + * customer app. + * + * @param builder instance of CustomerIOBuilder to add push messaging module. + * @param config configuration provided by customer app for push messaging module. + */ + internal fun addNativeModuleFromConfig( + builder: CustomerIOBuilder, + config: Map + ) { + val androidConfig = + config.getTypedValue>(key = "android") ?: emptyMap() + // Prefer `android` object for push configurations as it's more specific to Android + // For common push configurations, use `config` object instead of `android` + + // Default push click behavior is to prevent restart of activity in React Native apps + val pushClickBehavior = + androidConfig.getTypedValue(Keys.Config.PUSH_CLICK_BEHAVIOR) + ?.takeIfNotBlank() + ?.let { value -> + runCatching { enumValueOf(value) }.getOrNull() + } ?: PushClickBehavior.ACTIVITY_PREVENT_RESTART + + val module = ModuleMessagingPushFCM( + moduleConfig = MessagingPushModuleConfig.Builder().apply { + setPushClickBehavior(pushClickBehavior = pushClickBehavior) + }.build(), + ) + builder.addCustomerIOModule(module) + } + } } diff --git a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt index ba886ae7..4bbae8d9 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt @@ -50,3 +50,9 @@ internal fun ReadableMap.toFCMRemoteMessage(destination: String): RemoteMessage build() } } + +/** + * Maps native class to react native supported type so the result can be passed on to JS/TS classes. + */ +internal val PermissionStatus.toReactNativeResult: Any + get() = this.name.uppercase() diff --git a/android/src/newarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt b/android/src/newarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt deleted file mode 100644 index 89bf2a09..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.BaseReactPackage -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.module.model.ReactModuleInfo -import com.facebook.react.module.model.ReactModuleInfoProvider -import com.facebook.react.uimanager.ViewManager -import io.customer.reactnative.sdk.messaginginapp.BaseInlineInAppMessageViewManager -import io.customer.reactnative.sdk.messaginginapp.InlineInAppMessageViewManager - -class CustomerIOReactNativePackage : BaseReactPackage() { - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return CustomerIOReactNativePackageImpl.createViewManagers(reactContext) - } - - override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { - // Debugging reveals that this method is never called for ViewManagers. - // But since ReactNative docs recommend overriding it, we do so here for ViewManagers. - // See: https://reactnative.dev/docs/fabric-native-components-introduction?platforms=android#4-write-the-reactwebviewpackage - return when (name) { - BaseInlineInAppMessageViewManager.NAME -> InlineInAppMessageViewManager() - else -> CustomerIOReactNativePackageImpl.createNativeModule(reactContext, name) - } - } - - /** - * Creates a ReactModuleInfo for React module registration with the given configuration. - * Using positional arguments instead of named arguments as named args break on RN 0.76. - */ - private fun createReactModuleInfo( - name: String, - className: String = name, - canOverrideExistingModule: Boolean = false, - needsEagerInit: Boolean = false, - isCxxModule: Boolean = false, - isTurboModule: Boolean = true, - ) = ReactModuleInfo( - name, - className, - canOverrideExistingModule, - needsEagerInit, - isCxxModule, - isTurboModule, - ) - - override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { - buildMap { - // Register TurboModules - CustomerIOReactNativePackageImpl.turboModuleNames.forEach { moduleName -> - put(moduleName, createReactModuleInfo(name = moduleName)) - } - - // Register ViewManagers - val viewManagerName = BaseInlineInAppMessageViewManager.NAME - val viewManagerInfo = createReactModuleInfo(name = viewManagerName) - put(viewManagerName, viewManagerInfo) - } - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt b/android/src/newarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt deleted file mode 100644 index 4503345a..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt +++ /dev/null @@ -1,58 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableMap - -/** - * React Native module implementation for Customer.io Native SDK using using - * Turbo Modules with new architecture. - */ -class NativeCustomerIOModule( - private val reactContext: ReactApplicationContext, -) : NativeCustomerIOSpec(reactContext) { - - override fun initialize(config: ReadableMap?, args: ReadableMap?, promise: Promise?) { - NativeCustomerIOModuleImpl.initialize( - reactContext = reactContext, - sdkConfig = config, - promise = promise, - ) - } - - override fun identify(params: ReadableMap?) { - NativeCustomerIOModuleImpl.identify(params) - } - - override fun clearIdentify() { - NativeCustomerIOModuleImpl.clearIdentify() - } - - override fun track(name: String?, properties: ReadableMap?) { - NativeCustomerIOModuleImpl.track(name, properties) - } - - override fun screen(title: String?, properties: ReadableMap?) { - NativeCustomerIOModuleImpl.screen(title, properties) - } - - override fun setProfileAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setProfileAttributes(attributes) - } - - override fun setDeviceAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setDeviceAttributes(attributes) - } - - override fun registerDeviceToken(token: String?) { - NativeCustomerIOModuleImpl.registerDeviceToken(token) - } - - override fun trackMetric(deliveryID: String?, deviceToken: String?, event: String?) { - NativeCustomerIOModuleImpl.trackMetric(deliveryID, deviceToken, event) - } - - override fun deleteDeviceToken() { - NativeCustomerIOModuleImpl.deleteDeviceToken() - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt b/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt deleted file mode 100644 index 73de430e..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt +++ /dev/null @@ -1,92 +0,0 @@ -package io.customer.reactnative.sdk.logging - -import android.os.Build -import android.util.Log -import com.facebook.react.bridge.ReactApplicationContext -import io.customer.reactnative.sdk.NativeCustomerIOLoggingSpec -import io.customer.reactnative.sdk.util.onlyForLegacyArch - -/** - * React Native module implementation for Customer.io Logging Native SDK - * using TurboModules with new architecture. - */ -class NativeCustomerIOLoggingModule( - reactContext: ReactApplicationContext, -) : NativeCustomerIOLoggingSpec(reactContext) { - override fun getName(): String = NativeCustomerIOLoggingModuleImpl.NAME - - // true if the app is currently running under armeabi/armeabi-v7a ABIs. - // We check only the first ABI in SUPPORTED_ABIS because the first one is most preferred ABI. - private val isABIArmeabi: Boolean by lazy { - Build.SUPPORTED_ABIS?.firstOrNull() - ?.lowercase() - ?.contains("armeabi") == true - } - - /** - * Executes the given block and logs any uncaught exceptions using Android logger to protect - * against unexpected crashes and failures. - */ - private fun runWithTryCatch(action: () -> Unit) { - try { - action() - } catch (ex: Exception) { - // Use Android logger to avoid cyclic calls from internal SDK logging - Log.e("[CIO]", "Error in NativeCustomerIOLoggingModule: ${ex.message}", ex) - } - } - - /** - * Executes the given action only if the current ABI supports it. - * Skips execution on armeabi/armeabi-v7a to prevent C++ crashes on unsupported architectures. - */ - private fun runOnSupportedAbi(action: () -> Unit) { - runWithTryCatch { - if (isABIArmeabi) { - // Skip execution on armeabi-v7a to avoid known native (C++) crashes on unsupported ABIs. - // This ensures stability on lower-end or legacy devices by preventing risky native calls. - return@runWithTryCatch - } - - action() - } - } - - override fun initialize() { - runWithTryCatch { - super.initialize() - if (isABIArmeabi) { - Log.i( - "[CIO]", - "Native logging is disabled on armeabi/armeabi-v7a ABI to avoid native crashes (Supported ABIs: ${Build.SUPPORTED_ABIS?.joinToString()})" - ) - } - runOnSupportedAbi { - NativeCustomerIOLoggingModuleImpl.setLogEventEmitter { data -> - emitOnCioLogEvent(data) - } - } - } - } - - override fun invalidate() { - runOnSupportedAbi { - NativeCustomerIOLoggingModuleImpl.invalidate() - } - runWithTryCatch { - super.invalidate() - } - } - - override fun addListener(eventName: String?) { - runOnSupportedAbi { - onlyForLegacyArch("addListener") - } - } - - override fun removeListeners(count: Double) { - runOnSupportedAbi { - onlyForLegacyArch("removeListeners") - } - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt b/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt deleted file mode 100644 index 6b835b24..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.ViewManagerDelegate -import com.facebook.react.viewmanagers.InlineMessageNativeManagerDelegate -import com.facebook.react.viewmanagers.InlineMessageNativeManagerInterface - -@ReactModule(name = BaseInlineInAppMessageViewManager.NAME) -class InlineInAppMessageViewManager : BaseInlineInAppMessageViewManager(), - InlineMessageNativeManagerInterface { - private val delegate = InlineMessageNativeManagerDelegate(this) - - override fun getDelegate(): ViewManagerDelegate = delegate -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt b/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt deleted file mode 100644 index c2624231..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import io.customer.reactnative.sdk.NativeCustomerIOMessagingInAppSpec -import io.customer.reactnative.sdk.util.onlyForLegacyArch - -/** - * React Native module implementation for Customer.io In-App Messaging Native SDK - * using TurboModules with new architecture. - */ -class NativeMessagingInAppModule( - reactContext: ReactApplicationContext, -) : NativeCustomerIOMessagingInAppSpec(reactContext) { - private val inAppEventListener: ReactInAppEventListener - get() = NativeMessagingInAppModuleImpl.inAppEventListener - - override fun initialize() { - super.initialize() - inAppEventListener.setEventEmitter { data -> - emitOnInAppEventReceived(data) - } - } - - override fun invalidate() { - inAppEventListener.clearEventEmitter() - super.invalidate() - } - - override fun dismissMessage() { - NativeMessagingInAppModuleImpl.dismissMessage() - } - - override fun addListener(eventName: String?) { - onlyForLegacyArch("addListener") - } - - override fun removeListeners(count: Double) { - onlyForLegacyArch("removeListeners") - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt b/android/src/newarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt deleted file mode 100644 index e230cdda..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt +++ /dev/null @@ -1,66 +0,0 @@ -package io.customer.reactnative.sdk.messagingpush - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableMap -import io.customer.reactnative.sdk.NativeCustomerIOMessagingPushSpec -import io.customer.reactnative.sdk.util.unsupportedOnAndroid - -/** - * React Native module implementation for Customer.io Push Messaging Native SDK using - * TurboModules with new architecture. - */ -class NativeMessagingPushModule( - private val reactContext: ReactApplicationContext, -) : NativeCustomerIOMessagingPushSpec(reactContext) { - - override fun initialize() { - super.initialize() - reactContext.addActivityEventListener(NativeMessagingPushModuleImpl) - } - - override fun invalidate() { - reactContext.removeActivityEventListener(NativeMessagingPushModuleImpl) - super.invalidate() - } - - override fun onMessageReceived( - message: ReadableMap?, - handleNotificationTrigger: Boolean, - promise: Promise?, - ) { - NativeMessagingPushModuleImpl.onMessageReceived( - reactContext = reactContext, - message = message, - handleNotificationTrigger = handleNotificationTrigger, - promise = promise, - ) - } - - override fun trackNotificationResponseReceived(payload: ReadableMap?) { - unsupportedOnAndroid(methodName = "trackNotificationResponseReceived") - } - - override fun trackNotificationReceived(payload: ReadableMap?) { - unsupportedOnAndroid(methodName = "trackNotificationReceived") - } - - override fun getRegisteredDeviceToken(promise: Promise?) { - NativeMessagingPushModuleImpl.getRegisteredDeviceToken(promise) - } - - override fun showPromptForPushNotifications(options: ReadableMap?, promise: Promise?) { - NativeMessagingPushModuleImpl.showPromptForPushNotifications( - reactContext = reactContext, - pushConfigurationOptions = options, - promise = promise, - ) - } - - override fun getPushPermissionStatus(promise: Promise?) { - NativeMessagingPushModuleImpl.getPushPermissionStatus( - reactContext = reactContext, - promise = promise, - ) - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/util/ArchUtil.kt b/android/src/newarch/io/customer/reactnative/sdk/util/ArchUtil.kt deleted file mode 100644 index a48dfe3e..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/util/ArchUtil.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.customer.reactnative.sdk.util - -import io.customer.reactnative.sdk.BuildConfig -import io.customer.sdk.core.di.SDKComponent - -/** - * Marks a method that's only implemented for legacy architecture support. - * Throws in debug; logs an error in release builds. - */ -fun onlyForLegacyArch(methodName: String) { - val message = "'$methodName' is not required in the New Architecture and should not be called." - - if (BuildConfig.DEBUG) { - error(message) - } else { - SDKComponent.logger.error(message) - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt b/android/src/oldarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt deleted file mode 100644 index 8e43a539..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.ReactPackage -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ViewManager - -class CustomerIOReactNativePackage : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - return CustomerIOReactNativePackageImpl.turboModuleNames.mapNotNull { name -> - CustomerIOReactNativePackageImpl.createNativeModule( - reactContext = reactContext, - name = name, - ) - } - } - - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return CustomerIOReactNativePackageImpl.createViewManagers(reactContext) - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt deleted file mode 100644 index 04b91daf..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt +++ /dev/null @@ -1,74 +0,0 @@ -package io.customer.reactnative.sdk - -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.ReadableMap - -/** - * React Native module implementation for Customer.io Native SDK using using old architecture. - */ -class NativeCustomerIOModule( - reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeCustomerIOModuleImpl.NAME - - @ReactMethod - fun initialize( - configJson: ReadableMap, - @Suppress("UNUSED_PARAMETER") sdkArgs: ReadableMap?, - promise: Promise?, - ) { - NativeCustomerIOModuleImpl.initialize( - reactContext = reactApplicationContext, - sdkConfig = configJson, - promise = promise, - ) - } - - @ReactMethod - fun clearIdentify() { - NativeCustomerIOModuleImpl.clearIdentify() - } - - @ReactMethod - fun identify(params: ReadableMap?) { - NativeCustomerIOModuleImpl.identify(params = params) - } - - @ReactMethod - fun track(name: String, attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.track(name, attributes) - } - - @ReactMethod - fun setDeviceAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setDeviceAttributes(attributes) - } - - @ReactMethod - fun setProfileAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setProfileAttributes(attributes) - } - - @ReactMethod - fun screen(name: String, attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.screen(name, attributes) - } - - @ReactMethod - fun registerDeviceToken(token: String) { - NativeCustomerIOModuleImpl.registerDeviceToken(token) - } - - @ReactMethod - fun trackMetric(deliveryID: String, deviceToken: String, event: String) { - NativeCustomerIOModuleImpl.trackMetric(deliveryID, deviceToken, event) - } - - @ReactMethod - fun deleteDeviceToken() { - NativeCustomerIOModuleImpl.deleteDeviceToken() - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt b/android/src/oldarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt deleted file mode 100644 index c0aed903..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.customer.reactnative.sdk.extension - -import android.view.View -import com.facebook.react.bridge.WritableMap -import com.facebook.react.uimanager.events.RCTEventEmitter - -/** - * Extension function to send UI events from native Android views to React JS. - * Old Architecture (Paper) compatible implementation using RCTEventEmitter. - * - * @param eventName The name of the event to be sent to React JS - * @param payload Optional data payload to include with the event - */ -internal fun View.sendUIEventToReactJS( - eventName: String, - payload: WritableMap? = null, -) { - reactContext - .getJSModule(RCTEventEmitter::class.java) - .receiveEvent(id, eventName, payload) -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt deleted file mode 100644 index ea5906af..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.customer.reactnative.sdk.logging - -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.modules.core.DeviceEventManagerModule - -/** - * React Native module implementation for Customer.io Logging Native SDK - * using legacy architecture. - */ -class NativeCustomerIOLoggingModule( - private val reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeCustomerIOLoggingModuleImpl.NAME - - private var listenerCount = 0 - - override fun initialize() { - super.initialize() - NativeCustomerIOLoggingModuleImpl.setLogEventEmitter { data -> - if (listenerCount <= 0) return@setLogEventEmitter - - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - .emit("CioLogEvent", data) - } - } - - override fun invalidate() { - NativeCustomerIOLoggingModuleImpl.invalidate() - super.invalidate() - } - - @ReactMethod - fun addListener(eventName: String?) { - listenerCount++ - } - - @ReactMethod - fun removeListeners(count: Double) { - listenerCount -= count.toInt() - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt b/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt deleted file mode 100644 index 382b83ba..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -class InlineInAppMessageViewManager : BaseInlineInAppMessageViewManager() diff --git a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt deleted file mode 100644 index 7c8b8dda..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -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.modules.core.DeviceEventManagerModule - -/** - * React Native module implementation for Customer.io In-App Messaging Native - * SDK using old architecture. - */ -class NativeMessagingInAppModule( - private val reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeMessagingInAppModuleImpl.NAME - - private var listenerCount = 0 - private val inAppEventListener: ReactInAppEventListener - get() = NativeMessagingInAppModuleImpl.inAppEventListener - - override fun initialize() { - super.initialize() - inAppEventListener.setEventEmitter { data -> - if (listenerCount <= 0) return@setEventEmitter - - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - .emit("InAppEventListener", data) - } - } - - override fun invalidate() { - inAppEventListener.clearEventEmitter() - super.invalidate() - } - - @ReactMethod - fun dismissMessage() { - NativeMessagingInAppModuleImpl.dismissMessage() - } - - @ReactMethod - fun addListener(eventName: String?) { - listenerCount++ - } - - @ReactMethod - fun removeListeners(count: Double) { - listenerCount -= count.toInt() - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt deleted file mode 100644 index 7e3db553..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt +++ /dev/null @@ -1,62 +0,0 @@ -package io.customer.reactnative.sdk.messagingpush - -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.ReadableMap - -/** - * React Native module implementation for Customer.io Push Messaging Native SDK using old architecture. - */ -class NativeMessagingPushModule( - private val reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeMessagingPushModuleImpl.NAME - - override fun initialize() { - super.initialize() - reactContext.addActivityEventListener(NativeMessagingPushModuleImpl) - } - - override fun invalidate() { - reactContext.removeActivityEventListener(NativeMessagingPushModuleImpl) - super.invalidate() - } - - @ReactMethod - fun onMessageReceived( - message: ReadableMap?, - handleNotificationTrigger: Boolean, - promise: Promise, - ) { - NativeMessagingPushModuleImpl.onMessageReceived( - reactContext = reactContext, - message = message, - handleNotificationTrigger = handleNotificationTrigger, - promise = promise, - ) - } - - @ReactMethod - fun getRegisteredDeviceToken(promise: Promise) { - NativeMessagingPushModuleImpl.getRegisteredDeviceToken(promise) - } - - @ReactMethod - fun showPromptForPushNotifications(options: ReadableMap?, promise: Promise) { - NativeMessagingPushModuleImpl.showPromptForPushNotifications( - reactContext = reactContext, - pushConfigurationOptions = options, - promise = promise, - ) - } - - @ReactMethod - fun getPushPermissionStatus(promise: Promise) { - NativeMessagingPushModuleImpl.getPushPermissionStatus( - reactContext = reactContext, - promise = promise, - ) - } -} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index f1827141..24ef29be 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -47,8 +47,3 @@ edgeToEdgeEnabled=false # This is useful for testing new features or bug fixes before they are released. # Set to 'local' to use the local version of the SDK. # cioSDKVersionAndroid=local - -# Customer.io SDK allows overriding new architecture setting. -# This is useful as a workaround for apps where the new architecture has issues. -# Set to 'false' to force the SDK to use old architecture even when newArchEnabled=true. -# customerioNewArchEnabled=false diff --git a/example/ios/Podfile b/example/ios/Podfile index 82987314..15e06ff3 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -6,8 +6,6 @@ load "/tmp/override_cio_sdk.rb" # end of internal Customer.io testing code # ------------- -# Uncomment the next line to disable react-native's new architecture. -# ENV['RCT_NEW_ARCH_ENABLED'] = '0' # Resolve react_native_pods.rb with node to allow for hoisting require Pod::Executable.execute_command("node", ["-p", 'require.resolve( diff --git a/example/ios/SampleApp.xcodeproj.tracked/project.pbxproj b/example/ios/SampleApp.xcodeproj.tracked/project.pbxproj index aa8177c3..90abffd9 100644 --- a/example/ios/SampleApp.xcodeproj.tracked/project.pbxproj +++ b/example/ios/SampleApp.xcodeproj.tracked/project.pbxproj @@ -260,7 +260,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + shellScript = "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"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/example/package-lock.json b/example/package-lock.json index a04905fe..b404baf4 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -14,8 +14,8 @@ "@react-navigation/native": "^7.1.14", "@react-navigation/native-stack": "^7.3.20", "customerio-reactnative": "file:../customerio-reactnative.tgz", - "react": "19.1.1", - "react-native": "^0.82.0", + "react": "19.2.0", + "react-native": "^0.83.0", "react-native-device-info": "^14.0.4", "react-native-flash-message": "^0.4.2", "react-native-get-random-values": "^1.11.0", @@ -33,15 +33,15 @@ "@react-native-community/cli-platform-android": "^20.0.0", "@react-native-community/cli-platform-apple": "^20.0.0", "@react-native-community/cli-platform-ios": "^20.0.0", - "@react-native/babel-preset": "^0.82.0", - "@react-native/eslint-config": "0.82.0", - "@react-native/metro-config": "^0.82.0", - "@react-native/typescript-config": "^0.82.0", - "@types/react": "^19.1.1", + "@react-native/babel-preset": "^0.83.0", + "@react-native/eslint-config": "^0.83.0", + "@react-native/metro-config": "^0.83.0", + "@react-native/typescript-config": "^0.83.0", + "@types/react": "^19.2.0", "@types/uuid": "^10.0.0", "babel-plugin-module-resolver": "^5.0.2", "react-native-builder-bob": "^0.40.6", - "react-test-renderer": "19.1.1", + "react-test-renderer": "19.2.0", "typescript": "^5.8.3" }, "engines": { @@ -2837,32 +2837,32 @@ } }, "node_modules/@react-native/assets-registry": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.82.1.tgz", - "integrity": "sha512-B1SRwpntaAcckiatxbjzylvNK562Ayza05gdJCjDQHTiDafa1OABmyB5LHt7qWDOpNkaluD+w11vHF7pBmTpzQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.0.tgz", + "integrity": "sha512-EmGSKDvmnEnBrTK75T+0Syt6gy/HACOTfziw5+392Kr1Bb28Rv26GyOIkvptnT+bb2VDHU0hx9G0vSy5/S3rmQ==", "license": "MIT", "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.82.1.tgz", - "integrity": "sha512-wzmEz/RlR4SekqmaqeQjdMVh4LsnL9e62mrOikOOkHDQ3QN0nrKLuUDzXyYptVbxQ0IRua4pTm3efJLymDBoEg==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.0.tgz", + "integrity": "sha512-H5K0hnv9EhcenojZb9nUMIKPvHZ7ba9vpCyQIeGJmUTDYwZqjmXXyH73+uZo+GHjCIq1n0eF/soC5HJQzalh/Q==", "devOptional": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.82.1" + "@react-native/codegen": "0.83.0" }, "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-preset": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.82.1.tgz", - "integrity": "sha512-Olj7p4XIsUWLKjlW46CqijaXt45PZT9Lbvv/Hz698FXTenPKk4k7sy6RGRGZPWO2TCBBfcb73dus1iNHRFSq7g==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.0.tgz", + "integrity": "sha512-v20aTae9+aergUgRQSiy3CLqRSJu5VrHLpPpyYcAkTJ2JWTbtTlKfYuEw0V/WMFpeYZnZ7IVtu/6gTISVV74UQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2907,7 +2907,7 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.82.1", + "@react-native/babel-plugin-codegen": "0.83.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" @@ -2947,9 +2947,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.82.1.tgz", - "integrity": "sha512-ezXTN70ygVm9l2m0i+pAlct0RntoV4afftWMGUIeAWLgaca9qItQ54uOt32I/9dBJvzBibT33luIR/pBG0dQvg==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.0.tgz", + "integrity": "sha512-3fvMi/pSJHhikjwMZQplU4Ar9ANoR2GSBxotbkKIMI6iNduh+ln1FTvB2me69FA68aHtVZOO+cO+QpGCcvgaMA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -2983,17 +2983,17 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.82.1.tgz", - "integrity": "sha512-H/eMdtOy9nEeX7YVeEG1N2vyCoifw3dr9OV8++xfUElNYV7LtSmJ6AqxZUUfxGJRDFPQvaU/8enmJlM/l11VxQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.0.tgz", + "integrity": "sha512-bJD5pLURgKY2YK0R6gUsFWHiblSAFt1Xyc2fsyCL8XBnB7kJfVhLAKGItk6j1QZbwm1Io41ekZxBmZdyQqIDrg==", "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.82.1", + "@react-native/dev-middleware": "0.83.0", "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.83.1", - "metro-config": "^0.83.1", - "metro-core": "^0.83.1", + "metro": "^0.83.3", + "metro-config": "^0.83.3", + "metro-core": "^0.83.3", "semver": "^7.1.3" }, "engines": { @@ -3025,18 +3025,18 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.82.1.tgz", - "integrity": "sha512-a2O6M7/OZ2V9rdavOHyCQ+10z54JX8+B+apYKCQ6a9zoEChGTxUMG2YzzJ8zZJVvYf1ByWSNxv9Se0dca1hO9A==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.0.tgz", + "integrity": "sha512-7XVbkH8nCjLKLe8z5DS37LNP62/QNNya/YuLlVoLfsiB54nR/kNZij5UU7rS0npAZ3WN7LR0anqLlYnzDd0JHA==", "license": "BSD-3-Clause", "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/debugger-shell": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.82.1.tgz", - "integrity": "sha512-fdRHAeqqPT93bSrxfX+JHPpCXHApfDUdrXMXhoxlPgSzgXQXJDykIViKhtpu0M6slX6xU/+duq+AtP/qWJRpBw==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.0.tgz", + "integrity": "sha512-rJJxRRLLsKW+cqd0ALSBoqwL5SQTmwpd5SGl6rq9sY+fInCUKfkLEIc5HWQ0ppqoPyDteQVWbQ3a5VN84aJaNg==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", @@ -3047,14 +3047,14 @@ } }, "node_modules/@react-native/dev-middleware": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.82.1.tgz", - "integrity": "sha512-wuOIzms/Qg5raBV6Ctf2LmgzEOCqdP3p1AYN4zdhMT110c39TVMbunpBaJxm0Kbt2HQ762MQViF9naxk7SBo4w==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.0.tgz", + "integrity": "sha512-HWn42tbp0h8RWttua6d6PjseaSr3IdwkaoqVxhiM9kVDY7Ro00eO7tdlVgSzZzhIibdVS2b2C3x+sFoWhag1fA==", "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.82.1", - "@react-native/debugger-shell": "0.82.1", + "@react-native/debugger-frontend": "0.83.0", + "@react-native/debugger-shell": "0.83.0", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", @@ -3063,7 +3063,7 @@ "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", - "ws": "^6.2.3" + "ws": "^7.5.10" }, "engines": { "node": ">= 20.19.4" @@ -3097,16 +3097,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@react-native/eslint-config": { - "version": "0.82.0", - "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.82.0.tgz", - "integrity": "sha512-a6O5sbI2FmFSgYIvXLrl+pjWMQHy+/uQaXBuwkfglVT5jBtP5y1ouA/3vfafYLJtnHBEoutJL9KW5o6yPlU/xQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.83.0.tgz", + "integrity": "sha512-HTJg5XGQSGkVqeTvO7kOm1a1fNZ0VyZqhaLKAdWNwry+cWLkSnk9uohztnEIIP33FbP0Aybc7JuZIQon9OI3+w==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", - "@react-native/eslint-plugin": "0.82.0", + "@react-native/eslint-plugin": "0.83.0", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", "eslint-config-prettier": "^8.5.0", @@ -3114,7 +3135,7 @@ "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^29.0.1", "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-native": "^4.0.0" }, "engines": { @@ -3126,9 +3147,9 @@ } }, "node_modules/@react-native/eslint-plugin": { - "version": "0.82.0", - "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.82.0.tgz", - "integrity": "sha512-kSZvt008PemdyDUDEYwTEM3ar1UcES74yEN74ogTnMThWeHx/SADOex10yqdzeHwVmjl+N9q0R96Rg49B4h6Vw==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.83.0.tgz", + "integrity": "sha512-a0lObGV1/1P6mrekSF+1KpRkdH2fefQ/8fm1kLTUNvR5mae8xXz+U+f+1lsgqqEHtoGHey5Ve5MUkjgj4WnqTQ==", "dev": true, "license": "MIT", "engines": { @@ -3136,32 +3157,32 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.82.1.tgz", - "integrity": "sha512-KkF/2T1NSn6EJ5ALNT/gx0MHlrntFHv8YdooH9OOGl9HQn5NM0ZmQSr86o5utJsGc7ME3R6p3SaQuzlsFDrn8Q==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.0.tgz", + "integrity": "sha512-BXZRmfsbgPhEPkrRPjk2njA2AzhSelBqhuoklnv3DdLTdxaRjKYW+LW0zpKo1k3qPKj7kG1YGI3miol6l1GB5g==", "license": "MIT", "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.82.1.tgz", - "integrity": "sha512-tf70X7pUodslOBdLN37J57JmDPB/yiZcNDzS2m+4bbQzo8fhx3eG9QEBv5n4fmzqfGAgSB4BWRHgDMXmmlDSVA==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.0.tgz", + "integrity": "sha512-cVB9BMqlfbQR0v4Wxi5M2yDhZoKiNqWgiEXpp7ChdZIXI0SEnj8WwLwE3bDkyOfF8tCHdytpInXyg/al2O+dLQ==", "license": "MIT", "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.82.1.tgz", - "integrity": "sha512-kVQyYxYe1Da7cr7uGK9c44O6vTzM8YY3KW9CSLhhV1CGw7jmohU1HfLaUxDEmYfFZMc4Kj3JsIEbdUlaHMtprQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.83.0.tgz", + "integrity": "sha512-hB5kpR5Ho9l9xKuh5uHZEIFqnuaW8T7rDYwqf1j0xvTZu88KwaHAXya2IpDZsjlWzVMCl50cAwPkVZOlEOfJvw==", "devOptional": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.82.1", + "@react-native/babel-preset": "0.83.0", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" }, @@ -3190,38 +3211,38 @@ } }, "node_modules/@react-native/metro-config": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.82.1.tgz", - "integrity": "sha512-mAY6R3xnDMlmDOrUCAtLTjIkli26DZt4LNVuAjDEdnlv5sHANOr5x4qpMn7ea1p9Q/tpfHLalPQUQeJ8CZH4gA==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.83.0.tgz", + "integrity": "sha512-7mWZNZOJJLMJ8adBrAgAXcwtyn8PtPjAGavK8k3/mtsWYm79ncf5PD8D9puh6wBHCYwPu2ff/l23nNV8JsqLyg==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native/js-polyfills": "0.82.1", - "@react-native/metro-babel-transformer": "0.82.1", - "metro-config": "^0.83.1", - "metro-runtime": "^0.83.1" + "@react-native/js-polyfills": "0.83.0", + "@react-native/metro-babel-transformer": "0.83.0", + "metro-config": "^0.83.3", + "metro-runtime": "^0.83.3" }, "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/normalize-colors": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.82.1.tgz", - "integrity": "sha512-CCfTR1uX+Z7zJTdt3DNX9LUXr2zWXsNOyLbwupW2wmRzrxlHRYfmLgTABzRL/cKhh0Ubuwn15o72MQChvCRaHw==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.0.tgz", + "integrity": "sha512-DG1ELOqQ6RS82R1zEUGTWa/pfSPOf+vwAnQB7Ao1vRuhW/xdd2OPQJyqx5a5QWMYpGrlkCb7ERxEVX6p2QODCA==", "license": "MIT" }, "node_modules/@react-native/typescript-config": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.82.1.tgz", - "integrity": "sha512-kCTjmBg44p0kqU4xEMg7l6SNJyHWTHuTqiT9MpHasEYcnVpBWyEQsSQAiVKONHwcUWcAktrGVLE1dYGfBmPJ3Q==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.83.0.tgz", + "integrity": "sha512-8IcgamT0qoBDL3D8Ho6YHkQrxUMf3fKHkRd6MYDjVKNamz0XtfXNLY/FNnUOolx1HbgMkkwKFcbP3AbIKFxirQ==", "dev": true, "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.82.1.tgz", - "integrity": "sha512-f5zpJg9gzh7JtCbsIwV+4kP3eI0QBuA93JGmwFRd4onQ3DnCjV2J5pYqdWtM95sjSKK1dyik59Gj01lLeKqs1Q==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.0.tgz", + "integrity": "sha512-AVnDppwPidQrPrzA4ETr4o9W+40yuijg3EVgFt2hnMldMZkqwPRrgJL2GSreQjCYe1NfM5Yn4Egyy4Kd0yp4Lw==", "license": "MIT", "dependencies": { "invariant": "^2.2.4", @@ -3231,7 +3252,7 @@ "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^19.1.1", + "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, @@ -4190,6 +4211,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "devOptional": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -5076,9 +5098,9 @@ "license": "MIT" }, "node_modules/customerio-reactnative": { - "version": "5.2.0", + "version": "5.3.0", "resolved": "file:../customerio-reactnative.tgz", - "integrity": "sha512-X7e18GTaOAtRySFo2b3yAMIlhHgyGOdaILt+9xKKNwoLE00FlG+tdRYIzRndKEsf9efBfMnsh8x0HEUzbtHsXg==", + "integrity": "sha512-3hCBvWfYXNcghDmu9MZQnwPt6ScWXZopJTDkHtNwHTqBZIndGGOL2G7GVpm9M53OhzRZGerO12YBADXCsNeiZA==", "hasInstallScript": true, "license": "MIT", "engines": { @@ -5820,18 +5842,42 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/eslint-plugin-react-native": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz", @@ -6765,9 +6811,9 @@ } }, "node_modules/hermes-compiler": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.0.0.tgz", - "integrity": "sha512-boVFutx6ME/Km2mB6vvsQcdnazEYYI/jV1pomx1wcFUG/EVqTkr5CU0CW9bKipOA/8Hyu3NYwW3THg2Q1kNCfA==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.0.tgz", + "integrity": "sha512-clxa193o+GYYwykWVFfpHduCATz8fR5jvU7ngXpfKHj+E9hr9vjLNtdLSEe8MUbObvVexV3wcyxQ00xTPIrB1Q==", "license": "MIT" }, "node_modules/hermes-estree": { @@ -7894,9 +7940,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -9454,9 +9500,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", "peer": true, @@ -9654,9 +9700,9 @@ } }, "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9712,19 +9758,19 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.82.1.tgz", - "integrity": "sha512-tFAqcU7Z4g49xf/KnyCEzI4nRTu1Opcx05Ov2helr8ZTg1z7AJR/3sr2rZ+AAVlAs2IXk+B0WOxXGmdD3+4czA==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.0.tgz", + "integrity": "sha512-a8wPjGfkktb1+Mjvzkky3d0u6j6zdWAzftZ2LdQtgRgqkMMfgQxD9S+ri3RNlfAFQpuCAOYUIyrNHiVkUQChxA==", "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.82.1", - "@react-native/codegen": "0.82.1", - "@react-native/community-cli-plugin": "0.82.1", - "@react-native/gradle-plugin": "0.82.1", - "@react-native/js-polyfills": "0.82.1", - "@react-native/normalize-colors": "0.82.1", - "@react-native/virtualized-lists": "0.82.1", + "@react-native/assets-registry": "0.83.0", + "@react-native/codegen": "0.83.0", + "@react-native/community-cli-plugin": "0.83.0", + "@react-native/gradle-plugin": "0.83.0", + "@react-native/js-polyfills": "0.83.0", + "@react-native/normalize-colors": "0.83.0", + "@react-native/virtualized-lists": "0.83.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -9734,23 +9780,23 @@ "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", - "hermes-compiler": "0.0.0", + "hermes-compiler": "0.14.0", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.83.1", - "metro-source-map": "^0.83.1", + "metro-runtime": "^0.83.3", + "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.26.0", + "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.3", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { @@ -9761,7 +9807,7 @@ }, "peerDependencies": { "@types/react": "^19.1.1", - "react": "^19.1.1" + "react": "^19.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -10056,6 +10102,27 @@ "node": ">=10" } }, + "node_modules/react-native/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -10066,17 +10133,17 @@ } }, "node_modules/react-test-renderer": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.1.1.tgz", - "integrity": "sha512-aGRXI+zcBTtg0diHofc7+Vy97nomBs9WHHFY1Csl3iV0x6xucjNYZZAkiVKGiNYUv23ecOex5jE67t8ZzqYObA==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.0.tgz", + "integrity": "sha512-zLCFMHFE9vy/w3AxO0zNxy6aAupnCuLSVOJYDe/Tp+ayGI1f2PLQsFVPANSD42gdSbmYx5oN+1VWDhcXtq7hAQ==", "dev": true, "license": "MIT", "dependencies": { - "react-is": "^19.1.1", - "scheduler": "^0.26.0" + "react-is": "^19.2.0", + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.1" + "react": "^19.2.0" } }, "node_modules/readable-stream": { @@ -10405,9 +10472,9 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/semver": { @@ -11716,6 +11783,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "devOptional": true, "license": "MIT", "dependencies": { "async-limiter": "~1.0.0" @@ -11787,6 +11855,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/example/package.json b/example/package.json index 275d7f38..9bf0ee74 100644 --- a/example/package.json +++ b/example/package.json @@ -22,8 +22,8 @@ "@react-navigation/native": "^7.1.14", "@react-navigation/native-stack": "^7.3.20", "customerio-reactnative": "file:../customerio-reactnative.tgz", - "react": "19.1.1", - "react-native": "^0.82.0", + "react": "19.2.0", + "react-native": "^0.83.0", "react-native-device-info": "^14.0.4", "react-native-flash-message": "^0.4.2", "react-native-get-random-values": "^1.11.0", @@ -41,15 +41,15 @@ "@react-native-community/cli-platform-android": "^20.0.0", "@react-native-community/cli-platform-apple": "^20.0.0", "@react-native-community/cli-platform-ios": "^20.0.0", - "@react-native/babel-preset": "^0.82.0", - "@react-native/eslint-config": "0.82.0", - "@react-native/metro-config": "^0.82.0", - "@react-native/typescript-config": "^0.82.0", - "@types/react": "^19.1.1", + "@react-native/babel-preset": "^0.83.0", + "@react-native/eslint-config": "^0.83.0", + "@react-native/metro-config": "^0.83.0", + "@react-native/typescript-config": "^0.83.0", + "@types/react": "^19.2.0", "@types/uuid": "^10.0.0", "babel-plugin-module-resolver": "^5.0.2", "react-native-builder-bob": "^0.40.6", - "react-test-renderer": "19.1.1", + "react-test-renderer": "19.2.0", "typescript": "^5.8.3" }, "engines": { diff --git a/example/tsconfig.json b/example/tsconfig.json index f958f62f..e31d35db 100644 --- a/example/tsconfig.json +++ b/example/tsconfig.json @@ -5,6 +5,7 @@ "exclude": ["**/node_modules", "**/Pods"], "compilerOptions": { "rootDir": ".", + "types": ["jest"], "paths": { "@assets/*": ["./src/assets/*"], "@components": ["./src/components"], diff --git a/ios/wrappers/NativeCustomerIO.mm b/ios/wrappers/NativeCustomerIO.mm index 8626de06..8ff7170a 100644 --- a/ios/wrappers/NativeCustomerIO.mm +++ b/ios/wrappers/NativeCustomerIO.mm @@ -1,8 +1,5 @@ #import "utils/RCTCustomerIOUtils.h" #import - -#ifdef RCT_NEW_ARCH_ENABLED - #import // Objective-C wrapper for new architecture TurboModule implementation @@ -101,34 +98,3 @@ - (void)deleteDeviceToken { Class NativeCustomerIOCls(void) { return RCTNativeCustomerIO.class; } @end - -#else - -// Old Architecture: Bridge methods exposed via RCT_EXTERN macros -// Maps to Swift implementation without TurboModule overhead - -@interface RCT_EXTERN_REMAP_MODULE (NativeCustomerIO, NativeCustomerIO, NSObject) - -RCT_EXTERN_METHOD(initialize - : (NSDictionary *)config args - : (NSDictionary *)args resolve - : (RCTPromiseResolveBlock)resolve reject - : (RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(identify : (NSDictionary *)params) -RCT_EXTERN_METHOD(clearIdentify) -RCT_EXTERN_METHOD(track : (NSString *)name properties : (NSDictionary *)properties) -RCT_EXTERN_METHOD(screen : (NSString *)title properties : (NSDictionary *)properties) -RCT_EXTERN_METHOD(setProfileAttributes : (NSDictionary *)attributes) -RCT_EXTERN_METHOD(setDeviceAttributes : (NSDictionary *)attributes) -RCT_EXTERN_METHOD(registerDeviceToken : (NSString *)token) -RCT_EXTERN_METHOD(trackMetric : (NSString *)deliveryID deviceToken : (NSString *)deviceToken event : (NSString *)event) -RCT_EXTERN_METHOD(deleteDeviceToken) - -// Module initialization can happen on background thread -+ (BOOL)requiresMainQueueSetup { - return NO; -} - -@end - -#endif diff --git a/ios/wrappers/NativeCustomerIO.swift b/ios/wrappers/NativeCustomerIO.swift index 48b72128..41bd25c0 100644 --- a/ios/wrappers/NativeCustomerIO.swift +++ b/ios/wrappers/NativeCustomerIO.swift @@ -111,13 +111,13 @@ public class NativeCustomerIO: NSObject { @objc func setProfileAttributes(_ attributes: [String: Any]) { guard ensureInitialized() else { return } - CustomerIO.shared.profileAttributes = attributes + CustomerIO.shared.setProfileAttributes(attributes) } @objc func setDeviceAttributes(_ attributes: [String: Any]) { guard ensureInitialized() else { return } - CustomerIO.shared.deviceAttributes = attributes + CustomerIO.shared.setDeviceAttributes(attributes) } @objc diff --git a/ios/wrappers/inapp/NativeMessagingInApp.mm b/ios/wrappers/inapp/NativeMessagingInApp.mm index 9790ff6b..df95c546 100644 --- a/ios/wrappers/inapp/NativeMessagingInApp.mm +++ b/ios/wrappers/inapp/NativeMessagingInApp.mm @@ -3,9 +3,6 @@ #import #import #import - -#ifdef RCT_NEW_ARCH_ENABLED - #import // Protocol that extends the spec with setEventEmitter method @@ -24,13 +21,11 @@ @implementation RCTNativeMessagingInApp RCT_EXPORT_MODULE() -#ifdef RCT_NEW_ARCH_ENABLED // Create TurboModule instance for new architecture JSI integration - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } -#endif // Validates Swift bridge is available before method calls - (void)assertBridgeAvailable:(NSString *)context { @@ -89,23 +84,3 @@ - (void)emitOnInAppEventReceived:(NSDictionary *)value { } @end - -#else - -// Old Architecture: Bridge methods exposed via RCT_EXTERN macros -// Maps to Swift implementation without TurboModule overhead - -@interface RCT_EXTERN_REMAP_MODULE (NativeCustomerIOMessagingInApp, NativeMessagingInAppLegacy, - RCTEventEmitter) - -RCT_EXTERN_METHOD(supportedEvents) -RCT_EXTERN_METHOD(dismissMessage) - -// Module initialization can happen on background thread -+ (BOOL)requiresMainQueueSetup { - return NO; -} - -@end - -#endif diff --git a/ios/wrappers/inapp/NativeMessagingInApp.swift b/ios/wrappers/inapp/NativeMessagingInApp.swift index 0abb5733..86d7cd4c 100644 --- a/ios/wrappers/inapp/NativeMessagingInApp.swift +++ b/ios/wrappers/inapp/NativeMessagingInApp.swift @@ -2,54 +2,13 @@ import CioMessagingInApp import Foundation import React -// Core in-app messaging implementation shared between new and old architecture -class NativeMessagingInAppImplementation { - private let inAppEventCallback: (_ body: [String: Any]) -> Void - - init(inAppEventCallback: @escaping (_ body: [String: Any]) -> Void) { - self.inAppEventCallback = inAppEventCallback - } - - // Initialize in-app event listener - called by React Native - func initialize() { - ReactInAppEventListener.shared.setEventEmitter { [weak self] data in - guard let self else { return } - - // Filter out nil values to convert [String: Any?] to [String: Any] - let body = data.compactMapValues { $0 } - inAppEventCallback(body) - } - // If MessagingInApp module has already been initialized, this sets the listener directly. - // If this method is called early, accessing ReactInAppEventListener.shared will also register - // it into the DI graph, making it available for access in Expo during auto-initialization. - MessagingInApp.shared.setEventListener(ReactInAppEventListener.shared) - } - - // Clears the in-app event listener to prevent leaks when module is deallocated or invalidated - func clearInAppEventListener() { - ReactInAppEventListener.shared.clearEventEmitter() - } - - // Clear in-app event listener to prevent leaks - func invalidate() { - clearInAppEventListener() - } -} - // In-app messaging module for new React Native architecture (TurboModule) @objc(NativeMessagingInApp) public class NativeMessagingInApp: NSObject { - private var implementation: NativeMessagingInAppImplementation! // Reference to the ObjC event emitter for new architecture (TurboModule) private weak var objcEventEmitter: AnyObject? - - @objc - override public init() { - super.init() - - self.implementation = .init(inAppEventCallback: { [weak self] body in - self?.sendEvent(body: body) - }) + private lazy var inAppEventCallback: (_ body: [String: Any]) -> Void = { [weak self] body in + self?.sendEvent(body: body) } // Set ObjC event emitter reference for new architecture @@ -61,13 +20,25 @@ public class NativeMessagingInApp: NSObject { // Initialize in-app event listener - called by React Native @objc public func initialize() { - implementation.initialize() + // Initialize in-app event listener - called by React Native + ReactInAppEventListener.shared.setEventEmitter { [weak self] data in + guard let self else { return } + + // Filter out nil values to convert [String: Any?] to [String: Any] + let body = data.compactMapValues { $0 } + inAppEventCallback(body) + } + // If MessagingInApp module has already been initialized, this sets the listener directly. + // If this method is called early, accessing ReactInAppEventListener.shared will also register + // it into the DI graph, making it available for access in Expo during auto-initialization. + MessagingInApp.shared.setEventListener(ReactInAppEventListener.shared) } // Clear in-app event listener to prevent memory leaks - called by React Native @objc public func invalidate() { - implementation.invalidate() + // Clear in-app event listener to prevent leaks + clearInAppEventListener() } /** @@ -78,6 +49,11 @@ public class NativeMessagingInApp: NSObject { MessagingInApp.shared.dismissMessage() } + // Clears the in-app event listener to prevent leaks when module is deallocated or invalidated + func clearInAppEventListener() { + ReactInAppEventListener.shared.clearEventEmitter() + } + // Send in-app event to React Native layer using ObjC event emitter private func sendEvent(body: [String: Any]) { guard let emitter = objcEventEmitter else { @@ -94,52 +70,3 @@ public class NativeMessagingInApp: NSObject { _ = emitter.perform(selector, with: body as NSDictionary) } } - -// Legacy in-app messaging module for old React Native architecture (pre-TurboModule) -@objc(NativeMessagingInAppLegacy) -public class NativeMessagingInAppLegacy: RCTEventEmitter { - private var implementation: NativeMessagingInAppImplementation! - - @objc - override public init() { - super.init() - - self.implementation = .init(inAppEventCallback: { [weak self] body in - guard let self else { return } - - // Old architecture: use self as RCTEventEmitter - sendEvent(withName: CustomerioConstants.inAppEventListener, body: body) - }) - initialize() - } - - deinit { - invalidate() - } - - // Initialize in-app event listener - called by React Native - @objc - public func initialize() { - implementation.initialize() - } - - @objc - override public func invalidate() { - implementation.invalidate() - super.invalidate() - } - - // Returns array of supported event names for RCTEventEmitter - // All in-app events are combined under a single event name - override public func supportedEvents() -> [String]! { - [CustomerioConstants.inAppEventListener] - } - - /** - * Dismisses any currently displayed in-app message - */ - @objc - public func dismissMessage() { - MessagingInApp.shared.dismissMessage() - } -} diff --git a/ios/wrappers/inapp/inline/RCTInlineMessageNative.h b/ios/wrappers/inapp/inline/RCTInlineMessageNative.h index d7d21ce6..59400972 100644 --- a/ios/wrappers/inapp/inline/RCTInlineMessageNative.h +++ b/ios/wrappers/inapp/inline/RCTInlineMessageNative.h @@ -1,18 +1,18 @@ -#ifdef RCT_NEW_ARCH_ENABLED - +// RCTViewComponentView.h transitively includes C++ code from React Native's Fabric renderer. +// When this header is imported from an Objective-C (.h) file, certain build phases +// (e.g., Clang dependency scanning, Swift bridging, and module indexing) parse it in +// pure Objective-C mode, where C++ constructs are not allowed. +// +// Wrapping the import in `#ifdef __cplusplus` ensures it is only visible when the file +// is compiled in Objective-C++ mode (.mm), preventing build failures. +#ifdef __cplusplus #import #import -#ifndef RCTInlineMessageNative_h -#define RCTInlineMessageNative_h - NS_ASSUME_NONNULL_BEGIN @interface RCTInlineMessageNative : RCTViewComponentView @end NS_ASSUME_NONNULL_END - -#endif /* RCTInlineMessageNative_h */ - #endif diff --git a/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm b/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm index 765d0daa..34336ba8 100644 --- a/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm +++ b/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm @@ -1,5 +1,3 @@ -#ifdef RCT_NEW_ARCH_ENABLED - #import "RCTInlineMessageNative.h" #import "ReactInlineMessageView.h" @@ -13,7 +11,7 @@ using namespace facebook::react; /// New architecture React Native view for inline messages -@interface RCTInlineMessageNative () +@interface RCTInlineMessageNative () /// Bridge to Swift ReactInlineMessageView for platform-agnostic implementation @property(nonatomic, strong) id bridge; @end @@ -155,5 +153,3 @@ + (ComponentDescriptorProvider)componentDescriptorProvider { Class InlineMessageNativeCls(void) { return RCTInlineMessageNative.class; } - -#endif diff --git a/ios/wrappers/inapp/inline/RCTInlineMessageNativeViewManager.m b/ios/wrappers/inapp/inline/RCTInlineMessageNativeViewManager.m deleted file mode 100644 index 9eb44589..00000000 --- a/ios/wrappers/inapp/inline/RCTInlineMessageNativeViewManager.m +++ /dev/null @@ -1,96 +0,0 @@ -#ifndef RCT_NEW_ARCH_ENABLED - -#import "ReactInlineMessageView.h" - -#import -#import -#import - -/// Old architecture React Native view for inline messages -@interface RCTInlineMessageNativeView : UIView -/// Bridge to Swift ReactInlineMessageView for platform-agnostic implementation -@property(nonatomic, strong) id bridge; -@property(nonatomic, copy) NSString *elementId; -@property(nonatomic, copy) RCTDirectEventBlock onSizeChange; -@property(nonatomic, copy) RCTDirectEventBlock onStateChange; -@property(nonatomic, copy) RCTDirectEventBlock onActionClick; -@end - -@implementation RCTInlineMessageNativeView - -- (void)assertBridgeAvailable:(NSString *)context { - NSAssert(self.bridge != nil, @"Bridge is nil when %@", context); -} - -- (instancetype)init { - if (self = [super init]) { - // Create Swift bridge using runtime class resolution - Class bridgeClass = NSClassFromString(@"ReactInlineMessageView"); - if (bridgeClass) { - self.bridge = [[bridgeClass alloc] initWithContainerView:self]; - [self assertBridgeAvailable:@"creating ReactInlineMessageView bridge instance"]; - [self.bridge setEventEmitter:self]; - } - } - return self; -} - -- (void)setElementId:(NSString *)elementId { - _elementId = elementId; - [self assertBridgeAvailable:@"setting elementId"]; - [self.bridge setElementId:elementId ?: @""]; -} - -- (void)setOnSizeChange:(RCTDirectEventBlock)onSizeChange { - _onSizeChange = onSizeChange; -} - -- (void)setOnStateChange:(RCTDirectEventBlock)onStateChange { - _onStateChange = onStateChange; -} - -- (void)setOnActionClick:(RCTDirectEventBlock)onActionClick { - _onActionClick = onActionClick; -} - -// MARK: - Event Emitter Methods -- (void)emitOnSizeChangeEvent:(NSDictionary *)event { - if (self.onSizeChange) { - self.onSizeChange(event); - } -} - -- (void)emitOnStateChangeEvent:(NSDictionary *)event { - if (self.onStateChange) { - self.onStateChange(event); - } -} - -- (void)emitOnActionClickEvent:(NSDictionary *)event { - if (self.onActionClick) { - self.onActionClick(event); - } -} - -@end - -/// React Native view manager for old architecture inline message views -@interface RCTInlineMessageNativeViewManager : RCTViewManager -@end - -@implementation RCTInlineMessageNativeViewManager - -RCT_EXPORT_MODULE(InlineMessageNative) - -- (UIView *)view { - return [[RCTInlineMessageNativeView alloc] init]; -} - -RCT_EXPORT_VIEW_PROPERTY(elementId, NSString) -RCT_EXPORT_VIEW_PROPERTY(onSizeChange, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onStateChange, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onActionClick, RCTDirectEventBlock) - -@end - -#endif diff --git a/ios/wrappers/logging/NativeCustomerIOLogging.mm b/ios/wrappers/logging/NativeCustomerIOLogging.mm index e118035f..c0e2baf5 100644 --- a/ios/wrappers/logging/NativeCustomerIOLogging.mm +++ b/ios/wrappers/logging/NativeCustomerIOLogging.mm @@ -3,9 +3,6 @@ #import #import #import - -#ifdef RCT_NEW_ARCH_ENABLED - #import // Protocol that extends the spec with event emitter methods @@ -24,13 +21,11 @@ @implementation RCTNativeCustomerIOLogging RCT_EXPORT_MODULE() -#ifdef RCT_NEW_ARCH_ENABLED // Create TurboModule instance for new architecture JSI integration - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } -#endif // Validates Swift bridge is available before method calls - (void)assertBridgeAvailable:(NSString *)context { @@ -84,22 +79,3 @@ - (void)emitOnCioLogEvent:(NSDictionary *)value { } @end - -#else - -// Old Architecture: Bridge methods exposed via RCT_EXTERN macros -// Maps to Swift implementation without TurboModule overhead - -@interface RCT_EXTERN_REMAP_MODULE (NativeCustomerIOLogging, NativeCustomerIOLoggingLegacy, - RCTEventEmitter) - -RCT_EXTERN_METHOD(supportedEvents) - -// Background initialization is fine since logs are ignored until listeners are attached -+ (BOOL)requiresMainQueueSetup { - return NO; -} - -@end - -#endif diff --git a/ios/wrappers/logging/NativeCustomerIOLogging.swift b/ios/wrappers/logging/NativeCustomerIOLogging.swift index 34adb12f..bce4db45 100644 --- a/ios/wrappers/logging/NativeCustomerIOLogging.swift +++ b/ios/wrappers/logging/NativeCustomerIOLogging.swift @@ -2,52 +2,13 @@ import CioInternalCommon import Foundation import React -// Core logging implementation shared between new and old architecture -private class NativeCustomerIOLoggingImplementation { - private let logEventCallback: (_ body: [String: Any]) -> Void - - init(logEventCallback: @escaping (_ body: [String: Any]) -> Void) { - self.logEventCallback = logEventCallback - } - - // Initialize log dispatcher when module is ready - func initialize() { - DIGraphShared.shared.logger.setLogDispatcher { [weak self] level, message in - guard let self else { return } - - let body = [ - "logLevel": level.rawValue, - "message": message - ] - logEventCallback(body) - } - } - - // Clears the log dispatcher to prevent leaks when module is deallocated or invalidated - func clearLogEventListener() { - DIGraphShared.shared.logger.setLogDispatcher(nil) - } - - // Clear log dispatcher to prevent leaks - func invalidate() { - clearLogEventListener() - } -} - // Logging module for new React Native architecture (TurboModule) @objc(NativeCustomerIOLogging) public class NativeCustomerIOLogging: NSObject { - private var implementation: NativeCustomerIOLoggingImplementation! // Reference to the ObjC event emitter for new architecture (TurboModule) private weak var objcEventEmitter: AnyObject? - - @objc - override public init() { - super.init() - - self.implementation = .init(logEventCallback: { [weak self] body in - self?.sendEvent(body: body) - }) + private lazy var logEventCallback: (_ body: [String: Any]) -> Void = { [weak self] body in + self?.sendEvent(body: body) } // Set ObjC event emitter reference for new architecture @@ -59,13 +20,28 @@ public class NativeCustomerIOLogging: NSObject { // Initialize log dispatcher - called by React Native @objc public func initialize() { - implementation.initialize() + // Initialize log dispatcher when module is ready + DIGraphShared.shared.logger.setLogDispatcher { [weak self] level, message in + guard let self else { return } + + let body = [ + "logLevel": level.rawValue, + "message": message + ] + logEventCallback(body) + } } // Clear log dispatcher to prevent leaks - called by React Native @objc public func invalidate() { - implementation.invalidate() + // Clear log dispatcher to prevent leaks + clearLogEventListener() + } + + // Clears the log dispatcher to prevent leaks when module is deallocated or invalidated + private func clearLogEventListener() { + DIGraphShared.shared.logger.setLogDispatcher(nil) } // Send log event to React Native layer using ObjC event emitter @@ -84,50 +60,3 @@ public class NativeCustomerIOLogging: NSObject { _ = emitter.perform(selector, with: body as NSDictionary) } } - -// Legacy logging module for old React Native architecture (pre-TurboModule) -@objc(NativeCustomerIOLoggingLegacy) -public class NativeCustomerIOLoggingLegacy: RCTEventEmitter { - // Event name constant for log events - private static var eventName = "CioLogEvent" - - private var implementation: NativeCustomerIOLoggingImplementation! - - @objc - override public init() { - super.init() - - self.implementation = .init(logEventCallback: { [weak self] body in - guard let self else { return } - - // Old architecture: use self as RCTEventEmitter - sendEvent(withName: Self.eventName, body: body) - }) - initialize() - } - - deinit { - invalidate() - } - - @objc - public func initialize() { - implementation.initialize() - } - - @objc - override public func invalidate() { - implementation.invalidate() - super.invalidate() - } - - // Returns array of supported event names for RCTEventEmitter - override public func supportedEvents() -> [String]! { - [Self.eventName] - } - - // Custom dispatch queue for logging operations - override public var methodQueue: dispatch_queue_t! { - DispatchQueue(label: Self.moduleName()) - } -} diff --git a/ios/wrappers/push/NativeMessagingPush.mm b/ios/wrappers/push/NativeMessagingPush.mm index 86f969bf..6d169013 100644 --- a/ios/wrappers/push/NativeMessagingPush.mm +++ b/ios/wrappers/push/NativeMessagingPush.mm @@ -1,8 +1,5 @@ #import "../utils/RCTCustomerIOUtils.h" #import - -#ifdef RCT_NEW_ARCH_ENABLED - #import // Objective-C wrapper for new architecture TurboModule implementation @@ -88,36 +85,3 @@ - (void)getPushPermissionStatus:(RCTPromiseResolveBlock)resolve } @end - -#else - -// Old Architecture: Bridge methods exposed via RCT_EXTERN macros -// Maps to Swift implementation without TurboModule overhead - -@interface RCT_EXTERN_REMAP_MODULE (NativeCustomerIOMessagingPush, NativeMessagingPush, NSObject) - -RCT_EXTERN_METHOD(trackNotificationResponseReceived : (NSDictionary *)payload) - -RCT_EXTERN_METHOD(trackNotificationReceived : (NSDictionary *)payload) - -RCT_EXTERN_METHOD(getRegisteredDeviceToken - : (RCTPromiseResolveBlock)resolve reject - : (RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(showPromptForPushNotifications - : (NSDictionary *)options resolve - : (RCTPromiseResolveBlock)resolve reject - : (RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(getPushPermissionStatus - : (RCTPromiseResolveBlock)resolve reject - : (RCTPromiseRejectBlock)reject) - -// Module initialization can happen on background thread -+ (BOOL)requiresMainQueueSetup { - return NO; -} - -@end - -#endif diff --git a/package-lock.json b/package-lock.json index 7c6c60e2..46ad0e65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "customerio-reactnative", - "version": "5.2.0", + "version": "5.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "customerio-reactnative", - "version": "5.2.0", + "version": "5.3.0", "hasInstallScript": true, "license": "MIT", "devDependencies": { @@ -17,10 +17,10 @@ "@evilmartians/lefthook": "^1.11.12", "@microsoft/api-extractor": "^7.52.8", "@react-native-community/cli": "^20.0.0", - "@react-native/babel-preset": "^0.82.0", - "@react-native/eslint-config": "^0.82.0", - "@react-native/typescript-config": "^0.82.0", - "@types/react": "^19.1.2", + "@react-native/babel-preset": "^0.83.0", + "@react-native/eslint-config": "^0.83.0", + "@react-native/typescript-config": "^0.83.0", + "@types/react": "^19.2.0", "@typescript-eslint/eslint-plugin": "^8.31.1", "@typescript-eslint/parser": "^8.31.1", "eslint": "9.25.1", @@ -31,8 +31,8 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-native": "^5.0.0", "prettier": "^3.5.3", - "react": "19.1.1", - "react-native": "^0.82.0", + "react": "19.2.0", + "react-native": "^0.83.0", "react-native-builder-bob": "^0.40.10", "typescript": "^5.8.3", "typescript-eslint": "^8.31.1" @@ -2651,64 +2651,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -2725,64 +2667,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/fake-timers": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", @@ -2801,35 +2685,47 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "node_modules/@jest/schemas": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/chalk": { + "node_modules/@jest/transform/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -2846,7 +2742,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { + "node_modules/@jest/transform/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -2859,47 +2755,7 @@ "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@jest/types": { + "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", @@ -2917,17 +2773,7 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { + "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -2944,7 +2790,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/supports-color": { + "node_modules/@jest/types/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -3655,9 +3501,9 @@ } }, "node_modules/@react-native/assets-registry": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.82.1.tgz", - "integrity": "sha512-B1SRwpntaAcckiatxbjzylvNK562Ayza05gdJCjDQHTiDafa1OABmyB5LHt7qWDOpNkaluD+w11vHF7pBmTpzQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.0.tgz", + "integrity": "sha512-EmGSKDvmnEnBrTK75T+0Syt6gy/HACOTfziw5+392Kr1Bb28Rv26GyOIkvptnT+bb2VDHU0hx9G0vSy5/S3rmQ==", "dev": true, "license": "MIT", "engines": { @@ -3665,23 +3511,23 @@ } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.82.1.tgz", - "integrity": "sha512-wzmEz/RlR4SekqmaqeQjdMVh4LsnL9e62mrOikOOkHDQ3QN0nrKLuUDzXyYptVbxQ0IRua4pTm3efJLymDBoEg==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.0.tgz", + "integrity": "sha512-H5K0hnv9EhcenojZb9nUMIKPvHZ7ba9vpCyQIeGJmUTDYwZqjmXXyH73+uZo+GHjCIq1n0eF/soC5HJQzalh/Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.82.1" + "@react-native/codegen": "0.83.0" }, "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-preset": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.82.1.tgz", - "integrity": "sha512-Olj7p4XIsUWLKjlW46CqijaXt45PZT9Lbvv/Hz698FXTenPKk4k7sy6RGRGZPWO2TCBBfcb73dus1iNHRFSq7g==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.0.tgz", + "integrity": "sha512-v20aTae9+aergUgRQSiy3CLqRSJu5VrHLpPpyYcAkTJ2JWTbtTlKfYuEw0V/WMFpeYZnZ7IVtu/6gTISVV74UQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3726,7 +3572,7 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.82.1", + "@react-native/babel-plugin-codegen": "0.83.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" @@ -3766,9 +3612,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.82.1.tgz", - "integrity": "sha512-ezXTN70ygVm9l2m0i+pAlct0RntoV4afftWMGUIeAWLgaca9qItQ54uOt32I/9dBJvzBibT33luIR/pBG0dQvg==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.0.tgz", + "integrity": "sha512-3fvMi/pSJHhikjwMZQplU4Ar9ANoR2GSBxotbkKIMI6iNduh+ln1FTvB2me69FA68aHtVZOO+cO+QpGCcvgaMA==", "dev": true, "license": "MIT", "dependencies": { @@ -3805,18 +3651,18 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.82.1.tgz", - "integrity": "sha512-H/eMdtOy9nEeX7YVeEG1N2vyCoifw3dr9OV8++xfUElNYV7LtSmJ6AqxZUUfxGJRDFPQvaU/8enmJlM/l11VxQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.0.tgz", + "integrity": "sha512-bJD5pLURgKY2YK0R6gUsFWHiblSAFt1Xyc2fsyCL8XBnB7kJfVhLAKGItk6j1QZbwm1Io41ekZxBmZdyQqIDrg==", "dev": true, "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.82.1", + "@react-native/dev-middleware": "0.83.0", "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.83.1", - "metro-config": "^0.83.1", - "metro-core": "^0.83.1", + "metro": "^0.83.3", + "metro-config": "^0.83.3", + "metro-core": "^0.83.3", "semver": "^7.1.3" }, "engines": { @@ -3836,9 +3682,9 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.82.1.tgz", - "integrity": "sha512-a2O6M7/OZ2V9rdavOHyCQ+10z54JX8+B+apYKCQ6a9zoEChGTxUMG2YzzJ8zZJVvYf1ByWSNxv9Se0dca1hO9A==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.0.tgz", + "integrity": "sha512-7XVbkH8nCjLKLe8z5DS37LNP62/QNNya/YuLlVoLfsiB54nR/kNZij5UU7rS0npAZ3WN7LR0anqLlYnzDd0JHA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3846,9 +3692,9 @@ } }, "node_modules/@react-native/debugger-shell": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.82.1.tgz", - "integrity": "sha512-fdRHAeqqPT93bSrxfX+JHPpCXHApfDUdrXMXhoxlPgSzgXQXJDykIViKhtpu0M6slX6xU/+duq+AtP/qWJRpBw==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.0.tgz", + "integrity": "sha512-rJJxRRLLsKW+cqd0ALSBoqwL5SQTmwpd5SGl6rq9sY+fInCUKfkLEIc5HWQ0ppqoPyDteQVWbQ3a5VN84aJaNg==", "dev": true, "license": "MIT", "dependencies": { @@ -3860,15 +3706,15 @@ } }, "node_modules/@react-native/dev-middleware": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.82.1.tgz", - "integrity": "sha512-wuOIzms/Qg5raBV6Ctf2LmgzEOCqdP3p1AYN4zdhMT110c39TVMbunpBaJxm0Kbt2HQ762MQViF9naxk7SBo4w==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.0.tgz", + "integrity": "sha512-HWn42tbp0h8RWttua6d6PjseaSr3IdwkaoqVxhiM9kVDY7Ro00eO7tdlVgSzZzhIibdVS2b2C3x+sFoWhag1fA==", "dev": true, "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.82.1", - "@react-native/debugger-shell": "0.82.1", + "@react-native/debugger-frontend": "0.83.0", + "@react-native/debugger-shell": "0.83.0", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", @@ -3877,7 +3723,7 @@ "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", - "ws": "^6.2.3" + "ws": "^7.5.10" }, "engines": { "node": ">= 20.19.4" @@ -3913,16 +3759,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@react-native/eslint-config": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.82.1.tgz", - "integrity": "sha512-K3xCTEAg8WDd7WpDhQ1hsKbuY3OXaQtqpokeOdgyJag100ZvUX84YIaqDqsVaAZqjA53zCA5PbxerWs6mPA+PQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.83.0.tgz", + "integrity": "sha512-HTJg5XGQSGkVqeTvO7kOm1a1fNZ0VyZqhaLKAdWNwry+cWLkSnk9uohztnEIIP33FbP0Aybc7JuZIQon9OI3+w==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", - "@react-native/eslint-plugin": "0.82.1", + "@react-native/eslint-plugin": "0.83.0", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", "eslint-config-prettier": "^8.5.0", @@ -3930,7 +3798,7 @@ "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^29.0.1", "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-native": "^4.0.0" }, "engines": { @@ -3998,6 +3866,26 @@ } } }, + "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-react-native": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz", @@ -4011,10 +3899,27 @@ "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/@react-native/eslint-config/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/eslint-config/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/@react-native/eslint-plugin": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.82.1.tgz", - "integrity": "sha512-PU0ho8pNp24pdegIpYRAwppfO8z7werpoTts2CJ/wXYQ+ryZKa2M31DHW+kl+K3wwwqVqFKAzLh4t3sP/mOqMQ==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.83.0.tgz", + "integrity": "sha512-a0lObGV1/1P6mrekSF+1KpRkdH2fefQ/8fm1kLTUNvR5mae8xXz+U+f+1lsgqqEHtoGHey5Ve5MUkjgj4WnqTQ==", "dev": true, "license": "MIT", "engines": { @@ -4022,9 +3927,9 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.82.1.tgz", - "integrity": "sha512-KkF/2T1NSn6EJ5ALNT/gx0MHlrntFHv8YdooH9OOGl9HQn5NM0ZmQSr86o5utJsGc7ME3R6p3SaQuzlsFDrn8Q==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.0.tgz", + "integrity": "sha512-BXZRmfsbgPhEPkrRPjk2njA2AzhSelBqhuoklnv3DdLTdxaRjKYW+LW0zpKo1k3qPKj7kG1YGI3miol6l1GB5g==", "dev": true, "license": "MIT", "engines": { @@ -4032,9 +3937,9 @@ } }, "node_modules/@react-native/js-polyfills": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.82.1.tgz", - "integrity": "sha512-tf70X7pUodslOBdLN37J57JmDPB/yiZcNDzS2m+4bbQzo8fhx3eG9QEBv5n4fmzqfGAgSB4BWRHgDMXmmlDSVA==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.0.tgz", + "integrity": "sha512-cVB9BMqlfbQR0v4Wxi5M2yDhZoKiNqWgiEXpp7ChdZIXI0SEnj8WwLwE3bDkyOfF8tCHdytpInXyg/al2O+dLQ==", "dev": true, "license": "MIT", "engines": { @@ -4042,23 +3947,23 @@ } }, "node_modules/@react-native/normalize-colors": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.82.1.tgz", - "integrity": "sha512-CCfTR1uX+Z7zJTdt3DNX9LUXr2zWXsNOyLbwupW2wmRzrxlHRYfmLgTABzRL/cKhh0Ubuwn15o72MQChvCRaHw==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.0.tgz", + "integrity": "sha512-DG1ELOqQ6RS82R1zEUGTWa/pfSPOf+vwAnQB7Ao1vRuhW/xdd2OPQJyqx5a5QWMYpGrlkCb7ERxEVX6p2QODCA==", "dev": true, "license": "MIT" }, "node_modules/@react-native/typescript-config": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.82.1.tgz", - "integrity": "sha512-kCTjmBg44p0kqU4xEMg7l6SNJyHWTHuTqiT9MpHasEYcnVpBWyEQsSQAiVKONHwcUWcAktrGVLE1dYGfBmPJ3Q==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.83.0.tgz", + "integrity": "sha512-8IcgamT0qoBDL3D8Ho6YHkQrxUMf3fKHkRd6MYDjVKNamz0XtfXNLY/FNnUOolx1HbgMkkwKFcbP3AbIKFxirQ==", "dev": true, "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.82.1.tgz", - "integrity": "sha512-f5zpJg9gzh7JtCbsIwV+4kP3eI0QBuA93JGmwFRd4onQ3DnCjV2J5pYqdWtM95sjSKK1dyik59Gj01lLeKqs1Q==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.0.tgz", + "integrity": "sha512-AVnDppwPidQrPrzA4ETr4o9W+40yuijg3EVgFt2hnMldMZkqwPRrgJL2GSreQjCYe1NfM5Yn4Egyy4Kd0yp4Lw==", "dev": true, "license": "MIT", "dependencies": { @@ -4069,7 +3974,7 @@ "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^19.1.1", + "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, @@ -4431,6 +4336,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", @@ -7822,9 +7737,9 @@ } }, "node_modules/hermes-compiler": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.0.0.tgz", - "integrity": "sha512-boVFutx6ME/Km2mB6vvsQcdnazEYYI/jV1pomx1wcFUG/EVqTkr5CU0CW9bKipOA/8Hyu3NYwW3THg2Q1kNCfA==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.0.tgz", + "integrity": "sha512-clxa193o+GYYwykWVFfpHduCATz8fR5jvU7ngXpfKHj+E9hr9vjLNtdLSEe8MUbObvVexV3wcyxQ00xTPIrB1Q==", "dev": true, "license": "MIT" }, @@ -8842,64 +8757,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -8936,64 +8793,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -9015,34 +8814,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -9088,64 +8859,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-regex-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", @@ -9174,34 +8887,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -9250,34 +8935,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "17.0.34", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", - "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -11164,9 +10821,9 @@ } }, "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "dev": true, "license": "MIT", "engines": { @@ -11214,20 +10871,20 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.82.1", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.82.1.tgz", - "integrity": "sha512-tFAqcU7Z4g49xf/KnyCEzI4nRTu1Opcx05Ov2helr8ZTg1z7AJR/3sr2rZ+AAVlAs2IXk+B0WOxXGmdD3+4czA==", + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.0.tgz", + "integrity": "sha512-a8wPjGfkktb1+Mjvzkky3d0u6j6zdWAzftZ2LdQtgRgqkMMfgQxD9S+ri3RNlfAFQpuCAOYUIyrNHiVkUQChxA==", "dev": true, "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.82.1", - "@react-native/codegen": "0.82.1", - "@react-native/community-cli-plugin": "0.82.1", - "@react-native/gradle-plugin": "0.82.1", - "@react-native/js-polyfills": "0.82.1", - "@react-native/normalize-colors": "0.82.1", - "@react-native/virtualized-lists": "0.82.1", + "@react-native/assets-registry": "0.83.0", + "@react-native/codegen": "0.83.0", + "@react-native/community-cli-plugin": "0.83.0", + "@react-native/gradle-plugin": "0.83.0", + "@react-native/js-polyfills": "0.83.0", + "@react-native/normalize-colors": "0.83.0", + "@react-native/virtualized-lists": "0.83.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -11237,23 +10894,23 @@ "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", - "hermes-compiler": "0.0.0", + "hermes-compiler": "0.14.0", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.83.1", - "metro-source-map": "^0.83.1", + "metro-runtime": "^0.83.3", + "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.26.0", + "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.3", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { @@ -11264,7 +10921,7 @@ }, "peerDependencies": { "@types/react": "^19.1.1", - "react": "^19.1.1" + "react": "^19.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -11462,6 +11119,28 @@ "hermes-estree": "0.32.0" } }, + "node_modules/react-native/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -11804,9 +11483,9 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "dev": true, "license": "MIT" }, @@ -13264,6 +12943,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/package.json b/package.json index 79840632..19d0f4d4 100644 --- a/package.json +++ b/package.json @@ -73,10 +73,10 @@ "@evilmartians/lefthook": "^1.11.12", "@microsoft/api-extractor": "^7.52.8", "@react-native-community/cli": "^20.0.0", - "@react-native/babel-preset": "^0.82.0", - "@react-native/eslint-config": "^0.82.0", - "@react-native/typescript-config": "^0.82.0", - "@types/react": "^19.1.2", + "@react-native/babel-preset": "^0.83.0", + "@react-native/eslint-config": "^0.83.0", + "@react-native/typescript-config": "^0.83.0", + "@types/react": "^19.2.0", "@typescript-eslint/eslint-plugin": "^8.31.1", "@typescript-eslint/parser": "^8.31.1", "eslint": "9.25.1", @@ -87,8 +87,8 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-native": "^5.0.0", "prettier": "^3.5.3", - "react": "19.1.1", - "react-native": "^0.82.0", + "react": "19.2.0", + "react-native": "^0.83.0", "react-native-builder-bob": "^0.40.10", "typescript": "^5.8.3", "typescript-eslint": "^8.31.1" diff --git a/src/customerio-inapp.ts b/src/customerio-inapp.ts index 88280d9d..17a03911 100644 --- a/src/customerio-inapp.ts +++ b/src/customerio-inapp.ts @@ -1,8 +1,4 @@ -import { - NativeEventEmitter, - type EventSubscription, - type TurboModule, -} from 'react-native'; +import { type EventSubscription, type TurboModule } from 'react-native'; import { InlineInAppMessageView } from './components'; import { NativeLoggerListener } from './native-logger-listener'; import NativeCustomerIOMessagingInApp, { @@ -11,22 +7,13 @@ import NativeCustomerIOMessagingInApp, { import type { InAppMessageEventType } from './types'; import { callNativeModule, ensureNativeModule } from './utils/native-bridge'; -// Constant value used for emitting all events for in-app from native modules -const InAppEventListenerEventName = 'InAppEventListener'; - /** * Ensures all methods defined in codegen spec are implemented by the public module * * @internal */ interface NativeInAppSpec - extends Omit< - CodegenSpec, - | keyof TurboModule - | 'onInAppEventReceived' - | 'addListener' - | 'removeListeners' - > {} + extends Omit {} // Reference to the native CustomerIO Data Pipelines module for SDK operations const nativeModule = ensureNativeModule(NativeCustomerIOMessagingInApp); @@ -59,16 +46,22 @@ class CustomerIOInAppMessaging implements NativeInAppSpec { return withNativeModule((native) => { try { - // Try new arch TurboModule event listener (not available in old arch) + // Register TurboModule event listener and return subscription. + // This method is generated by codegen in the native modules. + // Wrapped in try-catch due to previous reports of crashes on certain Android architectures. return native.onInAppEventReceived(emitter); - } catch { + } catch (error) { NativeLoggerListener.warn( - 'Falling back to legacy event emitter (likely using old architecture). ' + - 'Switch to new architecture for better performance and event handling.' + 'Failed to attach in-app event listener:', + error ); - // Fallback to old arch NativeEventEmitter when new arch method fails - const eventEmitter = new NativeEventEmitter(native); - return eventEmitter.addListener(InAppEventListenerEventName, emitter); + // Return a no-op subscription to maintain backwards compatibility + return { + remove: () => {}, + eventType: '', + key: 0, + subscriber: null as any, + } as EventSubscription; } }); } diff --git a/src/native-logger-listener.ts b/src/native-logger-listener.ts index 73baaa3f..e418a31f 100644 --- a/src/native-logger-listener.ts +++ b/src/native-logger-listener.ts @@ -1,4 +1,3 @@ -import { NativeEventEmitter } from 'react-native'; import NativeCustomerIOLogging from './specs/modules/NativeCustomerIOLogging'; import { CioLogLevel } from './types'; import { callNativeModule, ensureNativeModule } from './utils/native-bridge'; @@ -56,21 +55,12 @@ export class NativeLoggerListener { withNativeModule((native) => { try { - // Try new arch TurboModule log listener (not available in old arch) + // Register TurboModule event listener and return subscription. + // This method is generated by codegen in the native modules. + // Wrapped in try-catch due to previous reports of crashes on certain Android architectures. native.onCioLogEvent(logHandler); - } catch { - // Fallback to old arch NativeEventEmitter when new arch method fails - try { - // Use try-catch to prevent crashes for cases where native module may - // not be available instantly - const bridge = new NativeEventEmitter(native); - bridge.addListener('CioLogEvent', logHandler); - } catch (error) { - NativeLoggerListener.warn( - 'Failed to attach old arch log listener:', - error - ); - } + } catch (error) { + NativeLoggerListener.warn('Failed to attach log listener:', error); } }); this.isInitialized = true; diff --git a/src/specs/modules/NativeCustomerIOLogging.ts b/src/specs/modules/NativeCustomerIOLogging.ts index c8a0246c..d6842a39 100644 --- a/src/specs/modules/NativeCustomerIOLogging.ts +++ b/src/specs/modules/NativeCustomerIOLogging.ts @@ -1,6 +1,5 @@ import { TurboModuleRegistry, type TurboModule } from 'react-native'; import type { - Double, EventEmitter, UnsafeObject, } from 'react-native/Libraries/Types/CodegenTypes'; @@ -18,11 +17,6 @@ import type { /** TurboModule interface for CustomerIO logging native operations */ export interface Spec extends TurboModule { readonly onCioLogEvent: EventEmitter; - // Old architecture support: EventEmitter requires these methods for proper functionality - /** @internal - Registers an event listener for old architecture EventEmitter */ - addListener: (eventName: string) => void; - /** @internal - Removes event listeners for old architecture EventEmitter */ - removeListeners: (count: Double) => void; } export default TurboModuleRegistry.getEnforcing( diff --git a/src/specs/modules/NativeCustomerIOMessagingInApp.ts b/src/specs/modules/NativeCustomerIOMessagingInApp.ts index 5114d420..92fc41da 100644 --- a/src/specs/modules/NativeCustomerIOMessagingInApp.ts +++ b/src/specs/modules/NativeCustomerIOMessagingInApp.ts @@ -1,6 +1,5 @@ import { TurboModuleRegistry, type TurboModule } from 'react-native'; import type { - Double, EventEmitter, UnsafeObject, } from 'react-native/Libraries/Types/CodegenTypes'; @@ -16,11 +15,6 @@ import type { export interface Spec extends TurboModule { dismissMessage(): void; readonly onInAppEventReceived: EventEmitter; - // Old architecture support: EventEmitter requires these methods for proper functionality - /** @internal - Registers an event listener for old architecture EventEmitter */ - addListener: (eventName: string) => void; - /** @internal - Removes event listeners for old architecture EventEmitter */ - removeListeners: (count: Double) => void; } export default TurboModuleRegistry.getEnforcing(