Skip to content

Commit

Permalink
[MOB-10951] Add mobile framework info to register token request (#884)
Browse files Browse the repository at this point in the history
  • Loading branch information
sumeruchat authored Jan 16, 2025
1 parent b57c7f5 commit ab02a03
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Added `mobileFrameworkInfo` configuration option to `IterableConfig` to identify the mobile framework (Flutter, React Native, or Native) being used with the SDK.

## [6.5.9]
### Added
- Support for JSON-only in-app messages, JSON-only messages are now handled by the onNewInApp handler and consumed after retrieval
Expand Down
4 changes: 4 additions & 0 deletions swift-sdk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
8AAA8CDE2D074C2000DF8220 /* EmbeddedMessagingProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8C5D2D074C2000DF8220 /* EmbeddedMessagingProcessor.swift */; };
8AAA8CDF2D074C2000DF8220 /* APNSTypeChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8C532D074C2000DF8220 /* APNSTypeChecker.swift */; };
8AAA8CE02D074C2000DF8220 /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8C552D074C2000DF8220 /* Auth.swift */; };
8AB8D7D22D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB8D7D12D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift */; };
9FF05EAC2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
9FF05EAD2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
9FF05EAE2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
Expand Down Expand Up @@ -708,6 +709,7 @@
8AAA8C822D074C2000DF8220 /* RequestHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestHandlerProtocol.swift; sourceTree = "<group>"; };
8AAA8C832D074C2000DF8220 /* RequestProcessorUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessorUtil.swift; sourceTree = "<group>"; };
8AAA8C842D074C2000DF8220 /* RequestSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSender.swift; sourceTree = "<group>"; };
8AB8D7D12D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAPIMobileFrameworkDetector.swift; sourceTree = "<group>"; };
9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthManager.swift; sourceTree = "<group>"; };
AC02CAA5234E50B5006617E0 /* RegistrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationTests.swift; sourceTree = "<group>"; };
AC05644A26387B54001FB810 /* MockPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPersistence.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1177,6 +1179,7 @@
8AAA8C672D074C2000DF8220 /* InboxViewControllerViewModel.swift */,
8AAA8C682D074C2000DF8220 /* InboxViewControllerViewModelProtocol.swift */,
8AAA8C692D074C2000DF8220 /* InboxViewControllerViewModelView.swift */,
8AB8D7D12D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift */,
8AAA8C6A2D074C2000DF8220 /* InternalIterableAPI.swift */,
8AAA8C6B2D074C2000DF8220 /* InternalIterableAppIntegration.swift */,
8AAA8C6C2D074C2000DF8220 /* IterableAPICallRequest.swift */,
Expand Down Expand Up @@ -2020,6 +2023,7 @@
8AAA8BB22D07310600DF8220 /* IterableInboxView.swift in Sources */,
8AAA8BB52D07310600DF8220 /* IterableEmbeddedMessage.swift in Sources */,
8AAA8BB82D07310600DF8220 /* IterableMessaging.swift in Sources */,
8AB8D7D22D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift in Sources */,
8AAA8BC32D07310600DF8220 /* IterableInboxViewController.swift in Sources */,
8AAA8BC42D07310600DF8220 /* IterableAppIntegration.swift in Sources */,
8AAA8BCD2D07310600DF8220 /* RetryPolicy.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions swift-sdk/Core/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ enum JsonKey {

static let contentType = "Content-Type"

static let mobileFrameworkInfo = "mobileFrameworkInfo"

static let frameworkType = "frameworkType"

// embedded
static let embeddedSessionId = "session"
Expand Down
8 changes: 7 additions & 1 deletion swift-sdk/Internal/DataFieldsHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ struct DataFieldsHelper {
device: UIDevice,
bundle: Bundle,
notificationsEnabled: Bool,
deviceAttributes: [String: String]) -> [String: Any] {
deviceAttributes: [String: String],
mobileFrameworkInfo: IterableAPIMobileFrameworkInfo) -> [String: Any] {
var dataFields = [String: Any]()

deviceAttributes.forEach { deviceAttribute in
Expand All @@ -33,6 +34,11 @@ struct DataFieldsHelper {

dataFields.addAll(other: createUIDeviceFields(device: device))

dataFields[JsonKey.mobileFrameworkInfo] = [
JsonKey.frameworkType: mobileFrameworkInfo.frameworkType.rawValue,
JsonKey.iterableSdkVersion: mobileFrameworkInfo.iterableSdkVersion ?? "unknown"
]

return dataFields
}

Expand Down
27 changes: 21 additions & 6 deletions swift-sdk/Internal/InternalIterableAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,17 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
}

hexToken = token

let mobileFrameworkInfo = config.mobileFrameworkInfo ?? createDefaultMobileFrameworkInfo()

let registerTokenInfo = RegisterTokenInfo(hexToken: token,
appName: appName,
pushServicePlatform: config.pushPlatform,
apnsType: dependencyContainer.apnsTypeChecker.apnsType,
deviceId: deviceId,
deviceAttributes: deviceAttributes,
sdkVersion: localStorage.sdkVersion)
appName: appName,
pushServicePlatform: config.pushPlatform,
apnsType: dependencyContainer.apnsTypeChecker.apnsType,
deviceId: deviceId,
deviceAttributes: deviceAttributes,
sdkVersion: localStorage.sdkVersion,
mobileFrameworkInfo: mobileFrameworkInfo)
requestHandler.register(registerTokenInfo: registerTokenInfo,
notificationStateProvider: notificationStateProvider,
onSuccess: { (_ data: [AnyHashable: Any]?) in
Expand Down Expand Up @@ -819,9 +823,20 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
}
}

private func createDefaultMobileFrameworkInfo() -> IterableAPIMobileFrameworkInfo {
let frameworkType = IterableAPIMobileFrameworkDetector.frameworkType()
return IterableAPIMobileFrameworkInfo(
frameworkType: frameworkType,
iterableSdkVersion: frameworkType == .native ? localStorage.sdkVersion : nil
)
}

deinit {
ITBInfo()
notificationCenter.removeObserver(self)
requestHandler.stop()
}

}


81 changes: 81 additions & 0 deletions swift-sdk/Internal/IterableAPIMobileFrameworkDetector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation

final class IterableAPIMobileFrameworkDetector {
private struct FrameworkClasses {
static let flutter = [
"FlutterViewController",
"GeneratedPluginRegistrant",
"FlutterEngine",
"FlutterPluginRegistry"
]

static let reactNative = [
"RCTBridge",
"RCTRootView",
"RCTBundleURLProvider",
"RCTEventEmitter"
]
}

private struct BundleIdentifiers {
static let executableKey = "CFBundleExecutable"
static let flutterTargetKey = "FlutterDeploymentTarget"
static let reactNativeProviderKey = "RNBundleURLProvider"
static let flutterExecutableName = "Runner"
}

private static var cachedFrameworkType: IterableAPIMobileFrameworkType = {
detectFramework()
}()

static func detectFramework() -> IterableAPIMobileFrameworkType {
let bundle = Bundle.main

// Helper function to check for framework classes
func hasFrameworkClasses(_ classNames: [String]) -> Bool {
guard !classNames.isEmpty else { return false }
return classNames.contains { className in
guard IterableUtil.isNotNullOrEmpty(string: className) else { return false }
return bundle.classNamed(className) != nil
}
}

// Safely check frameworks
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.")
if let mainBundle = Bundle.main.infoDictionary,
let executableName = mainBundle[BundleIdentifiers.executableKey] as? String,
!executableName.isEmpty,
executableName == BundleIdentifiers.flutterExecutableName {
return .flutter
} else {
return .reactNative
}

case (true, false):
return .flutter

case (false, true):
return .reactNative

case (false, false):
if let mainBundle = Bundle.main.infoDictionary {
if let _ = mainBundle[BundleIdentifiers.flutterTargetKey] as? String {
return .flutter
}
if let _ = mainBundle[BundleIdentifiers.reactNativeProviderKey] as? String {
return .reactNative
}
}
return .native
}
}

public static func frameworkType() -> IterableAPIMobileFrameworkType {
return cachedFrameworkType
}
}
5 changes: 3 additions & 2 deletions swift-sdk/Internal/api-client/Request/RequestCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ struct RequestCreator {
device: UIDevice.current,
bundle: Bundle.main,
notificationsEnabled: notificationsEnabled,
deviceAttributes: registerTokenInfo.deviceAttributes)
deviceAttributes: registerTokenInfo.deviceAttributes,
mobileFrameworkInfo: registerTokenInfo.mobileFrameworkInfo)

let deviceDictionary: [String: Any] = [
JsonKey.token: registerTokenInfo.hexToken,
JsonKey.platform: RequestCreator.pushServicePlatformToString(registerTokenInfo.pushServicePlatform,
apnsType: registerTokenInfo.apnsType),
JsonKey.applicationName: registerTokenInfo.appName,
JsonKey.dataFields: dataFields,
JsonKey.dataFields: dataFields
]

var body = [AnyHashable: Any]()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import Foundation


struct RegisterTokenInfo {
let hexToken: String
let appName: String
Expand All @@ -12,6 +13,7 @@ struct RegisterTokenInfo {
let deviceId: String
let deviceAttributes: [String: String]
let sdkVersion: String?
let mobileFrameworkInfo: IterableAPIMobileFrameworkInfo
}

struct UpdateSubscriptionsInfo {
Expand Down
15 changes: 15 additions & 0 deletions swift-sdk/SDK/IterableConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

import Foundation

public enum IterableAPIMobileFrameworkType: String, Codable {
case flutter = "flutter"
case reactNative = "reactnative"
case native = "native"
}

public struct IterableAPIMobileFrameworkInfo: Codable {
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
Expand Down Expand Up @@ -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?
}

3 changes: 2 additions & 1 deletion tests/offline-events-tests/RequestHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class RequestHandlerTests: XCTestCase {
apnsType: .sandbox,
deviceId: "deviceId",
deviceAttributes: [:],
sdkVersion: "6.x.x")
sdkVersion: "6.x.x",
mobileFrameworkInfo: IterableAPIMobileFrameworkInfo(frameworkType: .native, iterableSdkVersion: "6.x.x"))

let device = UIDevice.current
let dataFields: [String: Any] = [
Expand Down
6 changes: 6 additions & 0 deletions tests/unit-tests/IterableAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,11 @@ class IterableAPITests: XCTestCase {
TestUtils.validateMatch(keyPath: KeyPath(string: "device.dataFields.reactNativeSDKVersion"), value: "x.xx.xxx", inDictionary: body)
TestUtils.validateNil(keyPath: KeyPath(string: "device.dataFields.\(attributeToAddAndRemove)"), inDictionary: body)

TestUtils.validateMatch(keyPath: KeyPath(string: "device.dataFields.mobileFrameworkInfo.frameworkType"), value: "native", inDictionary: body)


TestUtils.validateMatch(keyPath: KeyPath(string: "device.dataFields.mobileFrameworkInfo.iterableSdkVersion"), value: IterableAPI.sdkVersion, inDictionary: body)

expectation.fulfill()
}) { reason, _ in
// failure
Expand Down Expand Up @@ -1310,4 +1315,5 @@ class IterableAPITests: XCTestCase {
XCTAssertEqual(localStorage.authToken, authToken)
userDefaults.removePersistentDomain(forName: "upgrade.test")
}

}
12 changes: 11 additions & 1 deletion tests/unit-tests/RequestCreatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,15 @@ class RequestCreatorTests: XCTestCase {
let userIdRequestCreator = RequestCreator(auth: userIdAuth,
deviceMetadata: deviceMetadata)

let testSdkVersion = "1.2.3"
let registerTokenInfo = RegisterTokenInfo(hexToken: "hex-token",
appName: "tester",
pushServicePlatform: .auto,
apnsType: .production,
deviceId: IterableUtil.generateUUID(),
deviceAttributes: [:],
sdkVersion: nil)
sdkVersion: testSdkVersion,
mobileFrameworkInfo: IterableAPIMobileFrameworkInfo(frameworkType: .native, iterableSdkVersion: testSdkVersion))

let urlRequest = convertToUrlRequest(userIdRequestCreator.createRegisterTokenRequest(registerTokenInfo: registerTokenInfo,
notificationsEnabled: true))
Expand All @@ -339,6 +341,14 @@ class RequestCreatorTests: XCTestCase {
let body = urlRequest.bodyDict
TestUtils.validateMatch(keyPath: KeyPath(keys: JsonKey.userId), value: userIdAuth.userId, inDictionary: body)
TestUtils.validateMatch(keyPath: KeyPath(keys: JsonKey.preferUserId), value: true, inDictionary: body)

// Add assertions for mobile framework info
TestUtils.validateMatch(keyPath: KeyPath(string: "mobileFrameworkInfo.frameworkType"),
value: "native",
inDictionary: body)
TestUtils.validateMatch(keyPath: KeyPath(string: "mobileFrameworkInfo.iterableSdkVersion"),
value: testSdkVersion,
inDictionary: body)
}

func testProcessorTypeOfflineInHeader() throws {
Expand Down
1 change: 1 addition & 0 deletions tests/unit-tests/TestUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ struct TestUtils {
queryItem.name == name
}!
}

}

struct KeyPath {
Expand Down

0 comments on commit ab02a03

Please sign in to comment.