Skip to content

Commit e7f1b27

Browse files
tapashmajumderroninopf
authored andcommitted
Merge pull request #307 from Iterable/feature/mob-1530-add-device-attributes
[MOB-1530] - Feature/mob 1530 add device attributes
1 parent eee4c18 commit e7f1b27

File tree

8 files changed

+142
-34
lines changed

8 files changed

+142
-34
lines changed

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
AC87172621A4E47E00FEA369 /* TestInAppPayloadGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC87172521A4E47E00FEA369 /* TestInAppPayloadGenerator.swift */; };
107107
AC8874AA22178BD80075B54B /* InAppContentParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8874A922178BD80075B54B /* InAppContentParser.swift */; };
108108
AC89661E2124FBCE0051A6CD /* IterableAutoRegistrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC89661D2124FBCE0051A6CD /* IterableAutoRegistrationTests.swift */; };
109+
AC8E9268246284F800BEB68E /* DataFieldsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8E9267246284F800BEB68E /* DataFieldsHelper.swift */; };
109110
AC8F35A2239806B500302994 /* InboxViewControllerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8F35A1239806B500302994 /* InboxViewControllerViewModelTests.swift */; };
110111
AC90C4CD20D8632E00EECA5D /* IterableAppExtensions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC90C4C420D8632D00EECA5D /* IterableAppExtensions.framework */; };
111112
AC90C4E220D8639E00EECA5D /* ITBNotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC90C4E120D8639E00EECA5D /* ITBNotificationServiceExtension.swift */; };
@@ -339,6 +340,7 @@
339340
AC87172521A4E47E00FEA369 /* TestInAppPayloadGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestInAppPayloadGenerator.swift; sourceTree = "<group>"; };
340341
AC8874A922178BD80075B54B /* InAppContentParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppContentParser.swift; sourceTree = "<group>"; };
341342
AC89661D2124FBCE0051A6CD /* IterableAutoRegistrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAutoRegistrationTests.swift; sourceTree = "<group>"; };
343+
AC8E9267246284F800BEB68E /* DataFieldsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataFieldsHelper.swift; sourceTree = "<group>"; };
342344
AC8F35A1239806B500302994 /* InboxViewControllerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxViewControllerViewModelTests.swift; sourceTree = "<group>"; };
343345
AC90C4C420D8632D00EECA5D /* IterableAppExtensions.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IterableAppExtensions.framework; sourceTree = BUILT_PRODUCTS_DIR; };
344346
AC90C4C720D8632E00EECA5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -747,6 +749,7 @@
747749
children = (
748750
AC845106228DF54E0052BB8F /* ApiClient.swift */,
749751
AC84510822910A0C0052BB8F /* RequestCreator.swift */,
752+
AC8E9267246284F800BEB68E /* DataFieldsHelper.swift */,
750753
);
751754
name = "API Client";
752755
sourceTree = "<group>";
@@ -1383,6 +1386,7 @@
13831386
AC72A0C720CF4CE2004D7997 /* CommerceItem.swift in Sources */,
13841387
AC31B040232AB42100BE25EB /* InboxSessionManager.swift in Sources */,
13851388
ACE34AB321376B1000691224 /* UserDefaultsLocalStorage.swift in Sources */,
1389+
AC8E9268246284F800BEB68E /* DataFieldsHelper.swift in Sources */,
13861390
AC2C667E20D3111900D46CC9 /* DateProvider.swift in Sources */,
13871391
ACA8D1AB21966555001B1332 /* InAppManager.swift in Sources */,
13881392
AC81918822713A110014955E /* Dwifft+UIKit.swift in Sources */,

swift-sdk/Internal/ApiClient.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ protocol ApiClientProtocol: AnyObject {
1111
appName: String,
1212
deviceId: String,
1313
sdkVersion: String?,
14+
deviceAttributes: [String: String],
1415
pushServicePlatform: String,
1516
notificationsEnabled: Bool) -> Future<SendRequestValue, SendRequestError>
1617

@@ -94,12 +95,14 @@ class ApiClient: ApiClientProtocol {
9495
appName: String,
9596
deviceId: String,
9697
sdkVersion: String?,
98+
deviceAttributes: [String: String],
9799
pushServicePlatform: String,
98100
notificationsEnabled: Bool) -> Future<SendRequestValue, SendRequestError> {
99101
return send(iterableRequestResult: createRequestCreator().createRegisterTokenRequest(hexToken: hexToken,
100102
appName: appName,
101103
deviceId: deviceId,
102104
sdkVersion: sdkVersion,
105+
deviceAttributes: deviceAttributes,
103106
pushServicePlatform: pushServicePlatform,
104107
notificationsEnabled: notificationsEnabled))
105108
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// Created by Tapash Majumder on 5/6/20.
3+
// Copyright © 2020 Iterable. All rights reserved.
4+
//
5+
// This file contains static pure helper functions for creating
6+
// the dataFields dictionary.
7+
8+
import Foundation
9+
10+
struct DataFieldsHelper {
11+
static func createDataFields(sdkVersion: String?, deviceId: String, device: UIDevice, bundle: Bundle, notificationsEnabled: Bool, deviceAttributes: [String: String]) -> [String: Any] {
12+
var dataFields = [String: Any]()
13+
14+
deviceAttributes.forEach { deviceAttribute in
15+
dataFields[deviceAttribute.key] = deviceAttribute.value
16+
}
17+
18+
dataFields[JsonKey.deviceId.jsonKey] = deviceId
19+
if let sdkVersion = sdkVersion {
20+
dataFields[JsonKey.iterableSdkVersion.jsonKey] = sdkVersion
21+
}
22+
dataFields[JsonKey.notificationsEnabled.jsonKey] = notificationsEnabled
23+
24+
dataFields.addAll(other: createBundleFields(bundle: bundle))
25+
26+
dataFields.addAll(other: createUIDeviceFields(device: device))
27+
28+
return dataFields
29+
}
30+
31+
private static func createBundleFields(bundle: Bundle) -> [String: Any] {
32+
var fields = [String: Any]()
33+
34+
if let appPackageName = bundle.appPackageName {
35+
fields[JsonKey.appPackageName.jsonKey] = appPackageName
36+
}
37+
if let appVersion = bundle.appVersion {
38+
fields[JsonKey.appVersion.jsonKey] = appVersion
39+
}
40+
if let appBuild = bundle.appBuild {
41+
fields[JsonKey.appBuild.jsonKey] = appBuild
42+
}
43+
44+
return fields
45+
}
46+
47+
private static func createUIDeviceFields(device: UIDevice) -> [String: Any] {
48+
var fields = [String: Any]()
49+
50+
fields[JsonKey.Device.localizedModel] = device.localizedModel
51+
fields[JsonKey.Device.userInterfaceIdiom] = userInterfaceIdiomEnumToString(device.userInterfaceIdiom)
52+
fields[JsonKey.Device.systemName] = device.systemName
53+
fields[JsonKey.Device.systemVersion] = device.systemVersion
54+
fields[JsonKey.Device.model] = device.model
55+
56+
if let identifierForVendor = device.identifierForVendor?.uuidString {
57+
fields[JsonKey.Device.vendorId] = identifierForVendor
58+
}
59+
60+
return fields
61+
}
62+
63+
private static func userInterfaceIdiomEnumToString(_ idiom: UIUserInterfaceIdiom) -> String {
64+
switch idiom {
65+
case .phone:
66+
return JsonValue.DeviceIdiom.phone
67+
case .pad:
68+
return JsonValue.DeviceIdiom.pad
69+
case .tv:
70+
return JsonValue.DeviceIdiom.tv
71+
case .carPlay:
72+
return JsonValue.DeviceIdiom.carPlay
73+
default:
74+
return JsonValue.DeviceIdiom.unspecified
75+
}
76+
}
77+
}
78+
79+
extension Dictionary {
80+
mutating func addAll(other: [Key: Value]) {
81+
for (k, v) in other {
82+
self[k] = v
83+
}
84+
}
85+
}

swift-sdk/Internal/IterableAPIInternal.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
135135
appName: appName,
136136
deviceId: deviceId,
137137
sdkVersion: localStorage.sdkVersion,
138+
deviceAttributes: deviceAttributes,
138139
pushServicePlatform: pushServicePlatformString,
139140
notificationsEnabled: notificationsEnabled))
140141
}
@@ -362,6 +363,14 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
362363
return deepLinkManager.handleUniversalLink(url, urlDelegate: config.urlDelegate, urlOpener: AppUrlOpener())
363364
}
364365

366+
func setDeviceAttribute(name: String, value: String) {
367+
deviceAttributes[name] = value
368+
}
369+
370+
func removeDeviceAttribute(name: String) {
371+
deviceAttributes.removeValue(forKey: name)
372+
}
373+
365374
@discardableResult private static func call(successHandler onSuccess: OnSuccessHandler? = nil,
366375
andFailureHandler onFailure: OnFailureHandler? = nil,
367376
forResult result: Future<SendRequestValue, SendRequestError>) -> Future<SendRequestValue, SendRequestError> {
@@ -403,6 +412,8 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
403412

404413
private var urlOpener: UrlOpenerProtocol
405414

415+
private var deviceAttributes = [String: String]()
416+
406417
private var dependencyContainer: DependencyContainerProtocol
407418

408419
// returns the push integration name for this app depending on the config options

swift-sdk/Internal/RequestCreator.swift

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,46 +51,20 @@ struct RequestCreator {
5151
appName: String,
5252
deviceId: String,
5353
sdkVersion: String?,
54+
deviceAttributes: [String: String],
5455
pushServicePlatform: String,
5556
notificationsEnabled: Bool) -> Result<IterableRequest, IterableError> {
5657
guard let keyValueForCurrentUser = keyValueForCurrentUser else {
5758
ITBError("Both email and userId are nil")
5859
return .failure(IterableError.general(description: "Both email and userId are nil"))
5960
}
6061

61-
let device = UIDevice.current
62-
63-
var dataFields: [String: Any] = [
64-
JsonKey.Device.localizedModel: device.localizedModel,
65-
JsonKey.Device.userInterfaceIdiom: RequestCreator.userInterfaceIdiomEnumToString(device.userInterfaceIdiom),
66-
JsonKey.Device.systemName: device.systemName,
67-
JsonKey.Device.systemVersion: device.systemVersion,
68-
JsonKey.Device.model: device.model,
69-
]
70-
71-
if let identifierForVendor = device.identifierForVendor?.uuidString {
72-
dataFields[JsonKey.Device.vendorId] = identifierForVendor
73-
}
74-
75-
dataFields[JsonKey.deviceId.jsonKey] = deviceId
76-
77-
if let sdkVersion = sdkVersion {
78-
dataFields[JsonKey.iterableSdkVersion.jsonKey] = sdkVersion
79-
}
80-
81-
if let appPackageName = Bundle.main.appPackageName {
82-
dataFields[JsonKey.appPackageName.jsonKey] = appPackageName
83-
}
84-
85-
if let appVersion = Bundle.main.appVersion {
86-
dataFields[JsonKey.appVersion.jsonKey] = appVersion
87-
}
88-
89-
if let appBuild = Bundle.main.appBuild {
90-
dataFields[JsonKey.appBuild.jsonKey] = appBuild
91-
}
92-
93-
dataFields[JsonKey.notificationsEnabled.jsonKey] = notificationsEnabled
62+
let dataFields = DataFieldsHelper.createDataFields(sdkVersion: sdkVersion,
63+
deviceId: deviceId,
64+
device: UIDevice.current,
65+
bundle: Bundle.main,
66+
notificationsEnabled: notificationsEnabled,
67+
deviceAttributes: deviceAttributes)
9468

9569
let deviceDictionary: [String: Any] = [
9670
JsonKey.token.jsonKey: hexToken,

swift-sdk/IterableAPI.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,25 @@ import UIKit
618618
return internalImplementation?.handleUniversalLink(url) ?? false
619619
}
620620

621+
/// This will send the device attribute to the back end when registering the device.
622+
///
623+
/// - Parameters:
624+
/// - name: The device attribute name
625+
/// - value: The device attribute value
626+
@objc(setDeviceAttribute:value:)
627+
public static func setDeviceAttribute(name: String, value: String) {
628+
internalImplementation?.setDeviceAttribute(name: name, value: value)
629+
}
630+
631+
/// Remove a device attribute set earlier.
632+
///
633+
/// - Parameters:
634+
/// - name: The device attribute name
635+
@objc(removeDeviceAttribute:)
636+
public static func removeDeviceAttribute(name: String) {
637+
internalImplementation?.removeDeviceAttribute(name: name)
638+
}
639+
621640
/// Use this property for getting and showing in-app messages.
622641
/// This property has no meaning if IterableAPI has not been initialized using
623642
/// IterableAPI.initialize

tests/swift-sdk-swift-tests/InAppHelperTests.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ class InAppHelperTests: XCTestCase {
9595
}
9696

9797
private class MockApiClient: ApiClientProtocol {
98-
func register(hexToken _: String, appName _: String, deviceId _: String, sdkVersion _: String?, pushServicePlatform _: String, notificationsEnabled _: Bool) -> Future<SendRequestValue, SendRequestError> {
98+
func register(hexToken _: String,
99+
appName _: String,
100+
deviceId _: String,
101+
sdkVersion _: String?,
102+
deviceAttributes: [String: String],
103+
pushServicePlatform _: String,
104+
notificationsEnabled _: Bool) -> Future<SendRequestValue, SendRequestError> {
99105
fatalError()
100106
}
101107

tests/swift-sdk-swift-tests/IterableAPITests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ class IterableAPITests: XCTestCase {
350350
IterableAPI.initializeForTesting(apiKey: IterableAPITests.apiKey, config: config, networkSession: networkSession)
351351
IterableAPI.email = "[email protected]"
352352
let token = "zeeToken".data(using: .utf8)!
353+
IterableAPI.setDeviceAttribute(name: "reactNativeSDKVersion", value: "x.xx.xxx")
354+
let attributeToAddAndRemove = IterableUtil.generateUUID()
355+
IterableAPI.setDeviceAttribute(name: attributeToAddAndRemove, value: "valueToAdd")
356+
IterableAPI.removeDeviceAttribute(name: attributeToAddAndRemove)
353357
IterableAPI.register(token: token, onSuccess: { _ in
354358
let body = networkSession.getRequestBody() as! [String: Any]
355359
TestUtils.validateElementPresent(withName: "email", andValue: "[email protected]", inDictionary: body)
@@ -366,6 +370,8 @@ class IterableAPITests: XCTestCase {
366370
TestUtils.validateMatch(keyPath: KeyPath("device.dataFields.appVersion"), value: appVersion, inDictionary: body)
367371
TestUtils.validateMatch(keyPath: KeyPath("device.dataFields.appBuild"), value: appBuild, inDictionary: body)
368372
TestUtils.validateMatch(keyPath: KeyPath("device.dataFields.iterableSdkVersion"), value: IterableAPI.sdkVersion, inDictionary: body)
373+
TestUtils.validateMatch(keyPath: KeyPath("device.dataFields.reactNativeSDKVersion"), value: "x.xx.xxx", inDictionary: body)
374+
TestUtils.validateNil(keyPath: KeyPath("device.dataFields.\(attributeToAddAndRemove)"), inDictionary: body)
369375

370376
expectation.fulfill()
371377
}) { reason, _ in

0 commit comments

Comments
 (0)