Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
34 changes: 0 additions & 34 deletions ios/wrappers/NativeCustomerIO.mm
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#import "utils/RCTCustomerIOUtils.h"
#import <React/RCTBridgeModule.h>

#ifdef RCT_NEW_ARCH_ENABLED

#import <RNCustomerIOSpec/RNCustomerIOSpec.h>

// Objective-C wrapper for new architecture TurboModule implementation
Expand Down Expand Up @@ -101,34 +98,3 @@ - (void)deleteDeviceToken {
Class<RCTBridgeModule> 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
4 changes: 2 additions & 2 deletions ios/wrappers/NativeCustomerIO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

}

@objc
Expand Down
25 changes: 0 additions & 25 deletions ios/wrappers/inapp/NativeMessagingInApp.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
#import <React/RCTEventEmitter.h>
#import <React/RCTInitializing.h>
#import <React/RCTInvalidating.h>

#ifdef RCT_NEW_ARCH_ENABLED

#import <RNCustomerIOSpec/RNCustomerIOSpec.h>

// Protocol that extends the spec with setEventEmitter method
Expand All @@ -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<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeCustomerIOMessagingInAppSpecJSI>(params);
}
#endif

// Validates Swift bridge is available before method calls
- (void)assertBridgeAvailable:(NSString *)context {
Expand Down Expand Up @@ -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
115 changes: 21 additions & 94 deletions ios/wrappers/inapp/NativeMessagingInApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}

/**
Expand All @@ -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 {
Expand All @@ -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()
}
}
16 changes: 8 additions & 8 deletions ios/wrappers/inapp/inline/RCTInlineMessageNative.h
Original file line number Diff line number Diff line change
@@ -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 <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>

#ifndef RCTInlineMessageNative_h
#define RCTInlineMessageNative_h

NS_ASSUME_NONNULL_BEGIN

@interface RCTInlineMessageNative : RCTViewComponentView
@end

NS_ASSUME_NONNULL_END

#endif /* RCTInlineMessageNative_h */

#endif
6 changes: 1 addition & 5 deletions ios/wrappers/inapp/inline/RCTInlineMessageNative.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#ifdef RCT_NEW_ARCH_ENABLED

#import "RCTInlineMessageNative.h"
#import "ReactInlineMessageView.h"

Expand All @@ -13,7 +11,7 @@
using namespace facebook::react;

/// New architecture React Native view for inline messages
@interface RCTInlineMessageNative ()
@interface RCTInlineMessageNative () <RCTInlineMessageNativeViewProtocol>
/// Bridge to Swift ReactInlineMessageView for platform-agnostic implementation
@property(nonatomic, strong) id bridge;
@end
Expand Down Expand Up @@ -155,5 +153,3 @@ + (ComponentDescriptorProvider)componentDescriptorProvider {
Class<RCTComponentViewProtocol> InlineMessageNativeCls(void) {
return RCTInlineMessageNative.class;
}

#endif
Loading
Loading