From 769202adb907e2089d8f52cfac9103644a3402bb Mon Sep 17 00:00:00 2001 From: Maciej Kucharski Date: Wed, 2 Apr 2025 12:07:03 +0200 Subject: [PATCH] persistent deviceId --- swift-sdk/Core/Constants.swift | 1 + swift-sdk/Internal/InternalIterableAPI.swift | 8 +---- .../Utilities/Keychain/IterableKeychain.swift | 18 +++++++++++ .../Internal/Utilities/LocalStorage.swift | 20 ++++++++++--- .../Utilities/LocalStorageProtocol.swift | 2 +- tests/common/MockLocalStorage.swift | 2 +- tests/unit-tests/LocalStorageTests.swift | 30 +++++++++++-------- 7 files changed, 56 insertions(+), 25 deletions(-) diff --git a/swift-sdk/Core/Constants.swift b/swift-sdk/Core/Constants.swift index 65333bae9..0932651a9 100644 --- a/swift-sdk/Core/Constants.swift +++ b/swift-sdk/Core/Constants.swift @@ -70,6 +70,7 @@ enum Const { static let email = "itbl_email" static let userId = "itbl_userid" static let authToken = "itbl_auth_token" + static let deviceId = "itbl_device_id" } } diff --git a/swift-sdk/Internal/InternalIterableAPI.swift b/swift-sdk/Internal/InternalIterableAPI.swift index 9c6aca25f..2888dfd4e 100644 --- a/swift-sdk/Internal/InternalIterableAPI.swift +++ b/swift-sdk/Internal/InternalIterableAPI.swift @@ -39,13 +39,7 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider { } var deviceId: String { - if let value = localStorage.deviceId { - return value - } else { - let value = IterableUtil.generateUUID() - localStorage.deviceId = value - return value - } + localStorage.deviceId } var deviceMetadata: DeviceMetadata { diff --git a/swift-sdk/Internal/Utilities/Keychain/IterableKeychain.swift b/swift-sdk/Internal/Utilities/Keychain/IterableKeychain.swift index 2737a3a73..ce1cf676c 100644 --- a/swift-sdk/Internal/Utilities/Keychain/IterableKeychain.swift +++ b/swift-sdk/Internal/Utilities/Keychain/IterableKeychain.swift @@ -63,6 +63,24 @@ class IterableKeychain { } } + var deviceId: String? { + get { + let data = wrapper.data(forKey: Const.Keychain.Key.deviceId) + + return data.flatMap { String(data: $0, encoding: .utf8) } + } + + set { + guard let deviceId = newValue, + let data = deviceId.data(using: .utf8) else { + wrapper.removeValue(forKey: Const.Keychain.Key.deviceId) + return + } + + wrapper.set(data, forKey: Const.Keychain.Key.deviceId) + } + } + // MARK: - PRIVATE/INTERNAL private let wrapper: KeychainWrapper diff --git a/swift-sdk/Internal/Utilities/LocalStorage.swift b/swift-sdk/Internal/Utilities/LocalStorage.swift index a6e049ec6..e0c8094b5 100644 --- a/swift-sdk/Internal/Utilities/LocalStorage.swift +++ b/swift-sdk/Internal/Utilities/LocalStorage.swift @@ -3,6 +3,7 @@ // import Foundation +import UIKit.UIDevice struct LocalStorage: LocalStorageProtocol { init(userDefaults: UserDefaults = UserDefaults.standard, @@ -43,11 +44,22 @@ struct LocalStorage: LocalStorageProtocol { } } - var deviceId: String? { + var deviceId: String { get { - iterableUserDefaults.deviceId - } set { - iterableUserDefaults.deviceId = newValue + let foundDeviceId = [ + iterableUserDefaults.deviceId, + keychain.deviceId, + UIDevice.current.identifierForVendor?.uuidString + ].compactMap { $0 }.first + let deviceId = foundDeviceId ?? IterableUtil.generateUUID() + + if iterableUserDefaults.deviceId == nil { + iterableUserDefaults.deviceId = deviceId + } + if keychain.deviceId == nil { + keychain.deviceId = deviceId + } + return deviceId } } diff --git a/swift-sdk/Internal/Utilities/LocalStorageProtocol.swift b/swift-sdk/Internal/Utilities/LocalStorageProtocol.swift index 420d076db..ca28d545c 100644 --- a/swift-sdk/Internal/Utilities/LocalStorageProtocol.swift +++ b/swift-sdk/Internal/Utilities/LocalStorageProtocol.swift @@ -13,7 +13,7 @@ protocol LocalStorageProtocol { var ddlChecked: Bool { get set } - var deviceId: String? { get set } + var deviceId: String { get } var sdkVersion: String? { get set } diff --git a/tests/common/MockLocalStorage.swift b/tests/common/MockLocalStorage.swift index 56aaed4f0..37e7cab07 100644 --- a/tests/common/MockLocalStorage.swift +++ b/tests/common/MockLocalStorage.swift @@ -15,7 +15,7 @@ class MockLocalStorage: LocalStorageProtocol { var ddlChecked: Bool = false - var deviceId: String? = nil + var deviceId: String = "AAAABBBB-CCCC-DDDD-EEEE-FFFFGGGGHHHH" var sdkVersion: String? = nil diff --git a/tests/unit-tests/LocalStorageTests.swift b/tests/unit-tests/LocalStorageTests.swift index 208ff53e5..4fa7d3e78 100644 --- a/tests/unit-tests/LocalStorageTests.swift +++ b/tests/unit-tests/LocalStorageTests.swift @@ -10,8 +10,8 @@ class LocalStorageTests: XCTestCase { override func setUp() { super.setUp() - LocalStorageTests.clearTestUserDefaults() - LocalStorageTests.clearTestKeychain() + Self.clearTestUserDefaults() + Self.clearTestKeychain() } static let localStorageTestSuiteName = "localstorage.tests" @@ -37,7 +37,7 @@ class LocalStorageTests: XCTestCase { } func testUserIdAndEmail() throws { - var localStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) + var localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) let userId = "zeeUserId" let email = "user@example.com" localStorage.userId = userId @@ -48,7 +48,7 @@ class LocalStorageTests: XCTestCase { } func testAuthDataInKeychain() { - let testUserDefaults = LocalStorageTests.getTestUserDefaults() + let testUserDefaults = Self.getTestUserDefaults() let testKeychain = IterableKeychain.init(wrapper: KeychainWrapper.init(serviceName: "test-localstorage")) var localStorage = LocalStorage(userDefaults: testUserDefaults, @@ -80,7 +80,7 @@ class LocalStorageTests: XCTestCase { } func testDDLChecked() throws { - var localStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) + var localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) localStorage.ddlChecked = true XCTAssertTrue(localStorage.ddlChecked) @@ -90,7 +90,7 @@ class LocalStorageTests: XCTestCase { func testAttributionInfo() throws { let mockDateProvider = MockDateProvider() - let localStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) + let localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) let attributionInfo = IterableAttributionInfo(campaignId: 1, templateId: 2, messageId: "3") let currentDate = Date() let expiration = Calendar.current.date(byAdding: Calendar.Component.hour, value: 24, to: currentDate)! @@ -109,21 +109,27 @@ class LocalStorageTests: XCTestCase { } func testDeviceId() { - var localStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) - let deviceId = UUID().uuidString - localStorage.deviceId = deviceId + let localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) + let deviceId = "AAAABBBB-CCCC-DDDD-EEEE-FFFFGGGGHHHH" + IterableUserDefaults(userDefaults: Self.getTestUserDefaults()).deviceId = deviceId + XCTAssertEqual(localStorage.deviceId, deviceId) + } + + func testKeychainDeviceId() { + let localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) + let deviceId = IterableKeychain().deviceId XCTAssertEqual(localStorage.deviceId, deviceId) } func testSdkVersion() { - var localStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) + var localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) let sdkVersion = "6.0.2" localStorage.sdkVersion = sdkVersion XCTAssertEqual(localStorage.sdkVersion, sdkVersion) } func testAuthToken() { - var localStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) + var localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) let authToken = "03.10.11" localStorage.authToken = authToken XCTAssertEqual(localStorage.authToken, authToken) @@ -151,7 +157,7 @@ class LocalStorageTests: XCTestCase { private func testLocalStorage(saver: (LocalStorageProtocol, T) -> Void, retriever: (LocalStorageProtocol) -> T?, value: T) where T: Equatable { - let localStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) + let localStorage = LocalStorage(userDefaults: Self.getTestUserDefaults()) saver(localStorage, value) let retrievedLocalStorage = LocalStorage(userDefaults: LocalStorageTests.getTestUserDefaults()) let retrieved = retriever(retrievedLocalStorage)