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/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