diff --git a/swift-sdk/Internal/InternalIterableAPI.swift b/swift-sdk/Internal/InternalIterableAPI.swift index c47880787..df754f639 100644 --- a/swift-sdk/Internal/InternalIterableAPI.swift +++ b/swift-sdk/Internal/InternalIterableAPI.swift @@ -189,7 +189,7 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider { hexToken = token.hexString() - let mobileFrameworkType = MobileFrameworkType.detect() + let mobileFrameworkType = IterableAPIMobileFrameworkType.detect() let registerTokenInfo = RegisterTokenInfo(hexToken: token.hexString(), appName: appName, pushServicePlatform: config.pushPlatform, @@ -197,7 +197,7 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider { deviceId: deviceId, deviceAttributes: deviceAttributes, sdkVersion: localStorage.sdkVersion, - mobileFrameworkInfo: MobileFrameworkInfo(frameworkType: frameworkType, iterableSdkVersion: localStorage.sdkVersion)) + mobileFrameworkInfo: IterableAPIMobileFrameworkInfo(frameworkType: mobileFrameworkType, iterableSdkVersion: nil)) requestHandler.register(registerTokenInfo: registerTokenInfo, notificationStateProvider: notificationStateProvider, onSuccess: { (_ data: [AnyHashable: Any]?) in @@ -779,24 +779,67 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider { } } -extension MobileFrameworkType { - - static func detect() -> MobileFrameworkType { +extension IterableAPIMobileFrameworkType { + private struct FrameworkClasses { + static let flutter = [ + "FlutterViewController", + "GeneratedPluginRegistrant", + "FlutterEngine", + "FlutterPluginRegistry" + ] + + static let reactNative = [ + "RCTBridge", + "RCTRootView", + "RCTBundleURLProvider", + "RCTEventEmitter" + ] + } + + static func detect() -> IterableAPIMobileFrameworkType { let bundle = Bundle.main - // Check for Flutter - if bundle.classNamed("FlutterViewController") != nil || - bundle.classNamed("GeneratedPluginRegistrant") != nil { - return .flutter + // Helper function to check for framework classes + func hasFrameworkClasses(_ classNames: [String]) -> Bool { + // Consider framework present if ANY of its core classes are found + return classNames.contains { className in + bundle.classNamed(className) != nil + } } - // Check for React Native - if bundle.classNamed("RCTBridge") != nil || - bundle.classNamed("RCTRootView") != nil { + // Check for multiple framework classes to increase confidence + let hasFlutter = hasFrameworkClasses(FrameworkClasses.flutter) + let hasReactNative = hasFrameworkClasses(FrameworkClasses.reactNative) + + switch (hasFlutter, hasReactNative) { + case (true, true): + ITBError("Both Flutter and React Native frameworks detected. This is unexpected.") + // In case of ambiguity, we could try to determine the primary framework + // by checking for more framework-specific indicators + if let mainBundle = Bundle.main.infoDictionary, + mainBundle["CFBundleExecutable"] as? String == "Runner" { + return .flutter // Flutter apps typically use "Runner" as executable name + } return .reactNative + + case (true, false): + return .flutter + + case (false, true): + return .reactNative + + case (false, false): + // Additional check for framework-specific build settings or Info.plist entries + if let mainBundle = Bundle.main.infoDictionary { + if mainBundle["FlutterDeploymentTarget"] != nil { + return .flutter + } + if mainBundle["RNBundleURLProvider"] != nil { + return .reactNative + } + } + + return .native } - - // If no framework detected, assume native - return .native } } diff --git a/swift-sdk/Internal/api-client/Request/RequestProcessorProtocol.swift b/swift-sdk/Internal/api-client/Request/RequestProcessorProtocol.swift index d4eab4c9b..b80db3ae8 100644 --- a/swift-sdk/Internal/api-client/Request/RequestProcessorProtocol.swift +++ b/swift-sdk/Internal/api-client/Request/RequestProcessorProtocol.swift @@ -4,16 +4,6 @@ import Foundation -enum MobileFrameworkType: String { - case flutter = "Flutter" - case reactNative = "ReactNative" - case native = "Native" -} - -struct MobileFrameworkInfo { - let frameworkType: MobileFrameworkType - let iterableSdkVersion: String? -} struct RegisterTokenInfo { let hexToken: String @@ -23,7 +13,7 @@ struct RegisterTokenInfo { let deviceId: String let deviceAttributes: [String: String] let sdkVersion: String? - let mobileFrameworkInfo: MobileFrameworkInfo? + let mobileFrameworkInfo: IterableAPIMobileFrameworkInfo? } struct UpdateSubscriptionsInfo { diff --git a/swift-sdk/SDK/IterableConfig.swift b/swift-sdk/SDK/IterableConfig.swift index 88fe1b081..6f593dd3b 100644 --- a/swift-sdk/SDK/IterableConfig.swift +++ b/swift-sdk/SDK/IterableConfig.swift @@ -4,6 +4,17 @@ import Foundation +public enum IterableAPIMobileFrameworkType: String { + case flutter = "Flutter" + case reactNative = "ReactNative" + case native = "Native" +} + +public struct IterableAPIMobileFrameworkInfo { + let frameworkType: IterableAPIMobileFrameworkType + let iterableSdkVersion: String? +} + /// Custom URL handling delegate @objc public protocol IterableURLDelegate: AnyObject { /// Callback called for a deep link action. Return true to override default behavior @@ -133,4 +144,8 @@ public class IterableConfig: NSObject { /// Allows for fetching embedded messages. public var enableEmbeddedMessaging = false + + /// The type of mobile framework we are using. + public var mobileFrameworkInfo: IterableAPIMobileFrameworkInfo? } +