diff --git a/QiscusCore.podspec b/QiscusCore.podspec index 5f8aa7b..4992c7b 100644 --- a/QiscusCore.podspec +++ b/QiscusCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "QiscusCore" -s.version = "1.13.3" +s.version = "1.13.4" s.summary = "Qiscus Core SDK for iOS" s.description = <<-DESC Qiscus SDK for iOS contains Qiscus public Model. diff --git a/README.md b/README.md index 25f4841..c351fff 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ let package = Package( ) ``` -## Installation Carthage +## Installation Carthage [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate QiscusCore into your Xcode project using Carthage, specify it in your Cartfile: ```bash diff --git a/Source/QiscusCore/Network/NetworkManager.swift b/Source/QiscusCore/Network/NetworkManager.swift index d5074f4..8fd1167 100644 --- a/Source/QiscusCore/Network/NetworkManager.swift +++ b/Source/QiscusCore/Network/NetworkManager.swift @@ -346,8 +346,8 @@ extension NetworkManager { /// - deviceToken: string device token for push notification /// - isDevelopment : default is false / using production /// - completion: @escaping when success register device token to sdk server returning value bool(success or not) and Optional String(error message) - func registerDeviceToken(deviceToken: String, isDevelopment: Bool = false, bundleId : String = "", onSuccess: @escaping (Bool) -> Void, onError: @escaping (QError) -> Void) { - clientRouter.request(.registerDeviceToken(token: deviceToken, isDevelopment: isDevelopment, bundleId: bundleId)) { (data, response, error) in + func registerDeviceToken(deviceToken: String, isDevelopment: Bool = false, bundleId : String = "", deviceId : String = "", onSuccess: @escaping (Bool) -> Void, onError: @escaping (QError) -> Void) { + clientRouter.request(.registerDeviceToken(token: deviceToken, isDevelopment: isDevelopment, bundleId: bundleId, deviceId : deviceId)) { (data, response, error) in if error != nil { onError(QError(message: error?.localizedDescription ?? "Please check your network connection.")) } diff --git a/Source/QiscusCore/Network/Service/Endpoint.swift b/Source/QiscusCore/Network/Service/Endpoint.swift index 5092de7..274aa03 100644 --- a/Source/QiscusCore/Network/Service/Endpoint.swift +++ b/Source/QiscusCore/Network/Service/Endpoint.swift @@ -79,7 +79,7 @@ internal enum APIClient { case sync(lastReceivedCommentId: String) case syncEvent(startEventId : String) case search(keyword: String, roomId: String?, lastCommentId: Int?) - case registerDeviceToken(token: String, isDevelopment: Bool, bundleId: String) // + case registerDeviceToken(token: String, isDevelopment: Bool, bundleId: String, deviceId: String) // case removeDeviceToken(token: String, isDevelopment: Bool) // case loginRegister(user: String, password: String , username: String?, avatarUrl: String?, extras: [String:Any]?) // case loginRegisterJWT(identityToken: String) // @@ -107,7 +107,7 @@ extension APIClient : EndPoint { return "/sync_event" case .search( _, _, _): return "/search_messages" - case .registerDeviceToken( _, _, _): + case .registerDeviceToken( _, _, _, _): return "/set_user_device_token" case .removeDeviceToken( _, _): return "/remove_user_device_token" @@ -177,12 +177,13 @@ extension APIClient : EndPoint { } return .requestParameters(bodyParameters: param, bodyEncoding: .jsonEncoding, urlParameters: nil) - case .registerDeviceToken(let token, let isDevelopment, let bundleId): + case .registerDeviceToken(let token, let isDevelopment, let bundleId, let deviceId): let param = [ "device_token" : token, "device_platform" : "ios", "is_development" : isDevelopment, - "bundle_id" : bundleId + "bundle_id" : bundleId, + "device_id" : deviceId ] as [String : Any] return .requestParameters(bodyParameters: param, bodyEncoding: .jsonEncoding, urlParameters: nil) case .removeDeviceToken(let token, let isDevelopment): diff --git a/Source/QiscusCore/QiscusCore.swift b/Source/QiscusCore/QiscusCore.swift index 252ff42..09ed800 100644 --- a/Source/QiscusCore/QiscusCore.swift +++ b/Source/QiscusCore/QiscusCore.swift @@ -714,7 +714,15 @@ public class QiscusCore: NSObject { /// - completion: The code to be executed once the request has finished public func registerDeviceToken(token : String, isDevelopment:Bool = false, bundleId : String = "", onSuccess: @escaping (Bool) -> Void, onError: @escaping (QError) -> Void) { if QiscusCore.isLogined { - QiscusCore.network.registerDeviceToken(deviceToken: token, isDevelopment: isDevelopment, bundleId: bundleId, onSuccess: { (success) in + var bundleID = "" + if bundleId.isEmpty { + bundleID = Bundle.main.bundleIdentifier ?? "" + }else{ + bundleID = bundleId + } + + let deviceId = getUUID(bundleId: bundleID) ?? "" + QiscusCore.network.registerDeviceToken(deviceToken: token, isDevelopment: isDevelopment, bundleId: bundleID, deviceId: deviceId, onSuccess: { (success) in onSuccess(success) }) { (error) in onError(error) @@ -724,6 +732,32 @@ public class QiscusCore: NSObject { } } + /// Creates a new unique user identifier or retrieves the last one created + private func getUUID(bundleId : String) -> String? { + + // create a keychain helper instance + let keychain = QKeychainAccess() + + // this is the key we'll use to store the uuid in the keychain + let uuidKey = "\(bundleId).unique_uuid" + + // check if we already have a uuid stored, if so return it + if let uuid = try? keychain.queryKeychainData(itemKey: uuidKey), uuid != nil { + return uuid + } + + // generate a new id + guard let newId = UIDevice.current.identifierForVendor?.uuidString else { + return nil + } + + // store new identifier in keychain + try? keychain.addKeychainData(itemKey: uuidKey, itemValue: newId) + + // return new id + return newId + } + /// Remove device token /// /// - Parameters: diff --git a/Source/QiscusCore/Util/QKeychainAccess.swift b/Source/QiscusCore/Util/QKeychainAccess.swift new file mode 100644 index 0000000..a7228b0 --- /dev/null +++ b/Source/QiscusCore/Util/QKeychainAccess.swift @@ -0,0 +1,78 @@ +// +// QKeychainAccess.swift +// QiscusCore +// +// Created by arief nur putranto on 05/11/24. +// + +import Foundation + +class QKeychainAccess { + + func addKeychainData(itemKey: String, itemValue: String) throws { + guard let valueData = itemValue.data(using: .utf8) else { + print("Keychain: Unable to store data, invalid input - key: \(itemKey), value: \(itemValue)") + return + } + + //delete old value if stored first + do { + try deleteKeychainData(itemKey: itemKey) + } catch { + print("Keychain: nothing to delete...") + } + + let queryAdd: [String: AnyObject] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: itemKey as AnyObject, + kSecValueData as String: valueData as AnyObject, + kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked + ] + let resultCode: OSStatus = SecItemAdd(queryAdd as CFDictionary, nil) + + if resultCode != 0 { + print("Keychain: value not added - Error: \(resultCode)") + } else { + print("Keychain: value added successfully") + } + } + + func deleteKeychainData(itemKey: String) throws { + let queryDelete: [String: AnyObject] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: itemKey as AnyObject + ] + + let resultCodeDelete = SecItemDelete(queryDelete as CFDictionary) + + if resultCodeDelete != 0 { + print("Keychain: unable to delete from keychain: \(resultCodeDelete)") + } else { + print("Keychain: successfully deleted item") + } + } + + func queryKeychainData (itemKey: String) throws -> String? { + let queryLoad: [String: AnyObject] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: itemKey as AnyObject, + kSecReturnData as String: kCFBooleanTrue, + kSecMatchLimit as String: kSecMatchLimitOne + ] + var result: AnyObject? + let resultCodeLoad = withUnsafeMutablePointer(to: &result) { + SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0)) + } + + if resultCodeLoad != 0 { + print("Keychain: unable to load data - \(resultCodeLoad)") + return nil + } + + guard let resultVal = result as? NSData, let keyValue = NSString(data: resultVal as Data, encoding: String.Encoding.utf8.rawValue) as String? else { + print("Keychain: error parsing keychain result - \(resultCodeLoad)") + return nil + } + return keyValue + } +}