Skip to content

Commit ab02a03

Browse files
authored
[MOB-10951] Add mobile framework info to register token request (#884)
1 parent b57c7f5 commit ab02a03

13 files changed

+160
-11
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
All notable changes to this project will be documented in this file.
44
This project adheres to [Semantic Versioning](http://semver.org/).
55

6+
## [Unreleased]
7+
### Added
8+
- Added `mobileFrameworkInfo` configuration option to `IterableConfig` to identify the mobile framework (Flutter, React Native, or Native) being used with the SDK.
9+
610
## [6.5.9]
711
### Added
812
- Support for JSON-only in-app messages, JSON-only messages are now handled by the onNewInApp handler and consumed after retrieval

swift-sdk.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@
283283
8AAA8CDE2D074C2000DF8220 /* EmbeddedMessagingProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8C5D2D074C2000DF8220 /* EmbeddedMessagingProcessor.swift */; };
284284
8AAA8CDF2D074C2000DF8220 /* APNSTypeChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8C532D074C2000DF8220 /* APNSTypeChecker.swift */; };
285285
8AAA8CE02D074C2000DF8220 /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8C552D074C2000DF8220 /* Auth.swift */; };
286+
8AB8D7D22D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB8D7D12D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift */; };
286287
9FF05EAC2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
287288
9FF05EAD2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
288289
9FF05EAE2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
@@ -708,6 +709,7 @@
708709
8AAA8C822D074C2000DF8220 /* RequestHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestHandlerProtocol.swift; sourceTree = "<group>"; };
709710
8AAA8C832D074C2000DF8220 /* RequestProcessorUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessorUtil.swift; sourceTree = "<group>"; };
710711
8AAA8C842D074C2000DF8220 /* RequestSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSender.swift; sourceTree = "<group>"; };
712+
8AB8D7D12D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAPIMobileFrameworkDetector.swift; sourceTree = "<group>"; };
711713
9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthManager.swift; sourceTree = "<group>"; };
712714
AC02CAA5234E50B5006617E0 /* RegistrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationTests.swift; sourceTree = "<group>"; };
713715
AC05644A26387B54001FB810 /* MockPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPersistence.swift; sourceTree = "<group>"; };
@@ -1177,6 +1179,7 @@
11771179
8AAA8C672D074C2000DF8220 /* InboxViewControllerViewModel.swift */,
11781180
8AAA8C682D074C2000DF8220 /* InboxViewControllerViewModelProtocol.swift */,
11791181
8AAA8C692D074C2000DF8220 /* InboxViewControllerViewModelView.swift */,
1182+
8AB8D7D12D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift */,
11801183
8AAA8C6A2D074C2000DF8220 /* InternalIterableAPI.swift */,
11811184
8AAA8C6B2D074C2000DF8220 /* InternalIterableAppIntegration.swift */,
11821185
8AAA8C6C2D074C2000DF8220 /* IterableAPICallRequest.swift */,
@@ -2020,6 +2023,7 @@
20202023
8AAA8BB22D07310600DF8220 /* IterableInboxView.swift in Sources */,
20212024
8AAA8BB52D07310600DF8220 /* IterableEmbeddedMessage.swift in Sources */,
20222025
8AAA8BB82D07310600DF8220 /* IterableMessaging.swift in Sources */,
2026+
8AB8D7D22D3805A900DECFE5 /* IterableAPIMobileFrameworkDetector.swift in Sources */,
20232027
8AAA8BC32D07310600DF8220 /* IterableInboxViewController.swift in Sources */,
20242028
8AAA8BC42D07310600DF8220 /* IterableAppIntegration.swift in Sources */,
20252029
8AAA8BCD2D07310600DF8220 /* RetryPolicy.swift in Sources */,

swift-sdk/Core/Constants.swift

+3
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ enum JsonKey {
182182

183183
static let contentType = "Content-Type"
184184

185+
static let mobileFrameworkInfo = "mobileFrameworkInfo"
186+
187+
static let frameworkType = "frameworkType"
185188

186189
// embedded
187190
static let embeddedSessionId = "session"

swift-sdk/Internal/DataFieldsHelper.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ struct DataFieldsHelper {
1414
device: UIDevice,
1515
bundle: Bundle,
1616
notificationsEnabled: Bool,
17-
deviceAttributes: [String: String]) -> [String: Any] {
17+
deviceAttributes: [String: String],
18+
mobileFrameworkInfo: IterableAPIMobileFrameworkInfo) -> [String: Any] {
1819
var dataFields = [String: Any]()
1920

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

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

37+
dataFields[JsonKey.mobileFrameworkInfo] = [
38+
JsonKey.frameworkType: mobileFrameworkInfo.frameworkType.rawValue,
39+
JsonKey.iterableSdkVersion: mobileFrameworkInfo.iterableSdkVersion ?? "unknown"
40+
]
41+
3642
return dataFields
3743
}
3844

swift-sdk/Internal/InternalIterableAPI.swift

+21-6
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,17 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
188188
}
189189

190190
hexToken = token
191+
192+
let mobileFrameworkInfo = config.mobileFrameworkInfo ?? createDefaultMobileFrameworkInfo()
193+
191194
let registerTokenInfo = RegisterTokenInfo(hexToken: token,
192-
appName: appName,
193-
pushServicePlatform: config.pushPlatform,
194-
apnsType: dependencyContainer.apnsTypeChecker.apnsType,
195-
deviceId: deviceId,
196-
deviceAttributes: deviceAttributes,
197-
sdkVersion: localStorage.sdkVersion)
195+
appName: appName,
196+
pushServicePlatform: config.pushPlatform,
197+
apnsType: dependencyContainer.apnsTypeChecker.apnsType,
198+
deviceId: deviceId,
199+
deviceAttributes: deviceAttributes,
200+
sdkVersion: localStorage.sdkVersion,
201+
mobileFrameworkInfo: mobileFrameworkInfo)
198202
requestHandler.register(registerTokenInfo: registerTokenInfo,
199203
notificationStateProvider: notificationStateProvider,
200204
onSuccess: { (_ data: [AnyHashable: Any]?) in
@@ -819,9 +823,20 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
819823
}
820824
}
821825

826+
private func createDefaultMobileFrameworkInfo() -> IterableAPIMobileFrameworkInfo {
827+
let frameworkType = IterableAPIMobileFrameworkDetector.frameworkType()
828+
return IterableAPIMobileFrameworkInfo(
829+
frameworkType: frameworkType,
830+
iterableSdkVersion: frameworkType == .native ? localStorage.sdkVersion : nil
831+
)
832+
}
833+
822834
deinit {
823835
ITBInfo()
824836
notificationCenter.removeObserver(self)
825837
requestHandler.stop()
826838
}
839+
827840
}
841+
842+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import Foundation
2+
3+
final class IterableAPIMobileFrameworkDetector {
4+
private struct FrameworkClasses {
5+
static let flutter = [
6+
"FlutterViewController",
7+
"GeneratedPluginRegistrant",
8+
"FlutterEngine",
9+
"FlutterPluginRegistry"
10+
]
11+
12+
static let reactNative = [
13+
"RCTBridge",
14+
"RCTRootView",
15+
"RCTBundleURLProvider",
16+
"RCTEventEmitter"
17+
]
18+
}
19+
20+
private struct BundleIdentifiers {
21+
static let executableKey = "CFBundleExecutable"
22+
static let flutterTargetKey = "FlutterDeploymentTarget"
23+
static let reactNativeProviderKey = "RNBundleURLProvider"
24+
static let flutterExecutableName = "Runner"
25+
}
26+
27+
private static var cachedFrameworkType: IterableAPIMobileFrameworkType = {
28+
detectFramework()
29+
}()
30+
31+
static func detectFramework() -> IterableAPIMobileFrameworkType {
32+
let bundle = Bundle.main
33+
34+
// Helper function to check for framework classes
35+
func hasFrameworkClasses(_ classNames: [String]) -> Bool {
36+
guard !classNames.isEmpty else { return false }
37+
return classNames.contains { className in
38+
guard IterableUtil.isNotNullOrEmpty(string: className) else { return false }
39+
return bundle.classNamed(className) != nil
40+
}
41+
}
42+
43+
// Safely check frameworks
44+
let hasFlutter = hasFrameworkClasses(FrameworkClasses.flutter)
45+
let hasReactNative = hasFrameworkClasses(FrameworkClasses.reactNative)
46+
47+
switch (hasFlutter, hasReactNative) {
48+
case (true, true):
49+
ITBError("Both Flutter and React Native frameworks detected. This is unexpected.")
50+
if let mainBundle = Bundle.main.infoDictionary,
51+
let executableName = mainBundle[BundleIdentifiers.executableKey] as? String,
52+
!executableName.isEmpty,
53+
executableName == BundleIdentifiers.flutterExecutableName {
54+
return .flutter
55+
} else {
56+
return .reactNative
57+
}
58+
59+
case (true, false):
60+
return .flutter
61+
62+
case (false, true):
63+
return .reactNative
64+
65+
case (false, false):
66+
if let mainBundle = Bundle.main.infoDictionary {
67+
if let _ = mainBundle[BundleIdentifiers.flutterTargetKey] as? String {
68+
return .flutter
69+
}
70+
if let _ = mainBundle[BundleIdentifiers.reactNativeProviderKey] as? String {
71+
return .reactNative
72+
}
73+
}
74+
return .native
75+
}
76+
}
77+
78+
public static func frameworkType() -> IterableAPIMobileFrameworkType {
79+
return cachedFrameworkType
80+
}
81+
}

swift-sdk/Internal/api-client/Request/RequestCreator.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@ struct RequestCreator {
4545
device: UIDevice.current,
4646
bundle: Bundle.main,
4747
notificationsEnabled: notificationsEnabled,
48-
deviceAttributes: registerTokenInfo.deviceAttributes)
48+
deviceAttributes: registerTokenInfo.deviceAttributes,
49+
mobileFrameworkInfo: registerTokenInfo.mobileFrameworkInfo)
4950

5051
let deviceDictionary: [String: Any] = [
5152
JsonKey.token: registerTokenInfo.hexToken,
5253
JsonKey.platform: RequestCreator.pushServicePlatformToString(registerTokenInfo.pushServicePlatform,
5354
apnsType: registerTokenInfo.apnsType),
5455
JsonKey.applicationName: registerTokenInfo.appName,
55-
JsonKey.dataFields: dataFields,
56+
JsonKey.dataFields: dataFields
5657
]
5758

5859
var body = [AnyHashable: Any]()

swift-sdk/Internal/api-client/Request/RequestProcessorProtocol.swift

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import Foundation
66

7+
78
struct RegisterTokenInfo {
89
let hexToken: String
910
let appName: String
@@ -12,6 +13,7 @@ struct RegisterTokenInfo {
1213
let deviceId: String
1314
let deviceAttributes: [String: String]
1415
let sdkVersion: String?
16+
let mobileFrameworkInfo: IterableAPIMobileFrameworkInfo
1517
}
1618

1719
struct UpdateSubscriptionsInfo {

swift-sdk/SDK/IterableConfig.swift

+15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44

55
import Foundation
66

7+
public enum IterableAPIMobileFrameworkType: String, Codable {
8+
case flutter = "flutter"
9+
case reactNative = "reactnative"
10+
case native = "native"
11+
}
12+
13+
public struct IterableAPIMobileFrameworkInfo: Codable {
14+
let frameworkType: IterableAPIMobileFrameworkType
15+
let iterableSdkVersion: String?
16+
}
17+
718
/// Custom URL handling delegate
819
@objc public protocol IterableURLDelegate: AnyObject {
920
/// Callback called for a deep link action. Return true to override default behavior
@@ -133,4 +144,8 @@ public class IterableConfig: NSObject {
133144

134145
/// Allows for fetching embedded messages.
135146
public var enableEmbeddedMessaging = false
147+
148+
/// The type of mobile framework we are using.
149+
public var mobileFrameworkInfo: IterableAPIMobileFrameworkInfo?
136150
}
151+

tests/offline-events-tests/RequestHandlerTests.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ class RequestHandlerTests: XCTestCase {
3030
apnsType: .sandbox,
3131
deviceId: "deviceId",
3232
deviceAttributes: [:],
33-
sdkVersion: "6.x.x")
33+
sdkVersion: "6.x.x",
34+
mobileFrameworkInfo: IterableAPIMobileFrameworkInfo(frameworkType: .native, iterableSdkVersion: "6.x.x"))
3435

3536
let device = UIDevice.current
3637
let dataFields: [String: Any] = [

tests/unit-tests/IterableAPITests.swift

+6
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,11 @@ class IterableAPITests: XCTestCase {
495495
TestUtils.validateMatch(keyPath: KeyPath(string: "device.dataFields.reactNativeSDKVersion"), value: "x.xx.xxx", inDictionary: body)
496496
TestUtils.validateNil(keyPath: KeyPath(string: "device.dataFields.\(attributeToAddAndRemove)"), inDictionary: body)
497497

498+
TestUtils.validateMatch(keyPath: KeyPath(string: "device.dataFields.mobileFrameworkInfo.frameworkType"), value: "native", inDictionary: body)
499+
500+
501+
TestUtils.validateMatch(keyPath: KeyPath(string: "device.dataFields.mobileFrameworkInfo.iterableSdkVersion"), value: IterableAPI.sdkVersion, inDictionary: body)
502+
498503
expectation.fulfill()
499504
}) { reason, _ in
500505
// failure
@@ -1310,4 +1315,5 @@ class IterableAPITests: XCTestCase {
13101315
XCTAssertEqual(localStorage.authToken, authToken)
13111316
userDefaults.removePersistentDomain(forName: "upgrade.test")
13121317
}
1318+
13131319
}

tests/unit-tests/RequestCreatorTests.swift

+11-1
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,15 @@ class RequestCreatorTests: XCTestCase {
323323
let userIdRequestCreator = RequestCreator(auth: userIdAuth,
324324
deviceMetadata: deviceMetadata)
325325

326+
let testSdkVersion = "1.2.3"
326327
let registerTokenInfo = RegisterTokenInfo(hexToken: "hex-token",
327328
appName: "tester",
328329
pushServicePlatform: .auto,
329330
apnsType: .production,
330331
deviceId: IterableUtil.generateUUID(),
331332
deviceAttributes: [:],
332-
sdkVersion: nil)
333+
sdkVersion: testSdkVersion,
334+
mobileFrameworkInfo: IterableAPIMobileFrameworkInfo(frameworkType: .native, iterableSdkVersion: testSdkVersion))
333335

334336
let urlRequest = convertToUrlRequest(userIdRequestCreator.createRegisterTokenRequest(registerTokenInfo: registerTokenInfo,
335337
notificationsEnabled: true))
@@ -339,6 +341,14 @@ class RequestCreatorTests: XCTestCase {
339341
let body = urlRequest.bodyDict
340342
TestUtils.validateMatch(keyPath: KeyPath(keys: JsonKey.userId), value: userIdAuth.userId, inDictionary: body)
341343
TestUtils.validateMatch(keyPath: KeyPath(keys: JsonKey.preferUserId), value: true, inDictionary: body)
344+
345+
// Add assertions for mobile framework info
346+
TestUtils.validateMatch(keyPath: KeyPath(string: "mobileFrameworkInfo.frameworkType"),
347+
value: "native",
348+
inDictionary: body)
349+
TestUtils.validateMatch(keyPath: KeyPath(string: "mobileFrameworkInfo.iterableSdkVersion"),
350+
value: testSdkVersion,
351+
inDictionary: body)
342352
}
343353

344354
func testProcessorTypeOfflineInHeader() throws {

tests/unit-tests/TestUtils.swift

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ struct TestUtils {
210210
queryItem.name == name
211211
}!
212212
}
213+
213214
}
214215

215216
struct KeyPath {

0 commit comments

Comments
 (0)