diff --git a/Source/Config.swift b/Source/Config.swift index 88dda05..7be2870 100644 --- a/Source/Config.swift +++ b/Source/Config.swift @@ -20,7 +20,7 @@ struct Config { proto = "http://" } - var components = URLComponents(string: proto + self.host + "/" + self.app + "/" + path)! + guard var components = URLComponents(string: proto + self.host + "/" + self.app + "/" + path) else { return nil } components.queryItems = [ URLQueryItem(name: "osdk", value: OptableSDK.version) ] return components.url } diff --git a/Source/Core/Client.swift b/Source/Core/Client.swift index b6c1aea..358c7f4 100644 --- a/Source/Core/Client.swift +++ b/Source/Core/Client.swift @@ -44,11 +44,14 @@ class Client { // Unlike res.value(forHTTPHeaderField:...) which was introduced in iOS 13.0, allHeaderFields is // case-sensitive, so we need to take special care to perform a case-INsensitive search: for (key, value) in res.allHeaderFields { - let header = key as! String - let result: ComparisonResult = header.compare(self.passportHeader, options: NSString.CompareOptions.caseInsensitive) - if result == .orderedSame { - self.storage.setPassport(value as! String) - break + if let header = key as? String { + let result: ComparisonResult = header.compare(self.passportHeader, options: NSString.CompareOptions.caseInsensitive) + if result == .orderedSame { + if let pp = value as? String { + self.storage.setPassport(pp) + break + } + } } } } @@ -56,7 +59,7 @@ class Client { } } - func postRequest(url: URL, data: Any) throws -> URLRequest? { + func postRequest(url: URL, data: Any) throws -> URLRequest { var req = URLRequest(url: url) req.httpMethod = "POST" @@ -70,14 +73,14 @@ class Client { req.addValue(passport, forHTTPHeaderField: self.passportHeader) } - if self.ua != nil { - req.addValue(self.ua!, forHTTPHeaderField: "User-Agent") + if let ua = self.ua { + req.addValue(ua, forHTTPHeaderField: "User-Agent") } return req } - func getRequest(url: URL) throws -> URLRequest? { + func getRequest(url: URL) throws -> URLRequest { var req = URLRequest(url: url) req.httpMethod = "GET" @@ -88,8 +91,8 @@ class Client { req.addValue(passport, forHTTPHeaderField: self.passportHeader) } - if self.ua != nil { - req.addValue(self.ua!, forHTTPHeaderField: "User-Agent") + if let ua = self.ua { + req.addValue(ua, forHTTPHeaderField: "User-Agent") } return req diff --git a/Source/Core/LocalStorage.swift b/Source/Core/LocalStorage.swift index 3ae02d1..6ae2729 100644 --- a/Source/Core/LocalStorage.swift +++ b/Source/Core/LocalStorage.swift @@ -18,10 +18,10 @@ class LocalStorage: NSObject { init(_ config: Config) { // The key used for storage should be unique to the host+app that this instance was initialized with: - let utf8str = (config.host + "/" + config.app).data(using: .utf8)!.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) + let utf8str = (config.host + "/" + config.app).data(using: .utf8)?.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) - self.passportKey = self.keyPfx + "_PASS_" + utf8str - self.targetingKey = self.keyPfx + "_TGT_" + utf8str + self.passportKey = self.keyPfx + "_PASS_" + (utf8str ?? "UNKNOWN") + self.targetingKey = self.keyPfx + "_TGT_" + (utf8str ?? "UNKNOWN") } func getPassport() -> String? { diff --git a/Source/Edge/Identify.swift b/Source/Edge/Identify.swift index e53bb6a..af4ea58 100644 --- a/Source/Edge/Identify.swift +++ b/Source/Edge/Identify.swift @@ -8,8 +8,8 @@ import Foundation -func Identify(config: Config, client: Client, ids: [String], completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask { - let url = config.edgeURL("identify") - let req = try client.postRequest(url: url!, data: ids) - return client.dispatchRequest(req!, completionHandler) +func Identify(config: Config, client: Client, ids: [String], completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask? { + guard let url = config.edgeURL("identify") else { return nil } + let req = try client.postRequest(url: url, data: ids) + return client.dispatchRequest(req, completionHandler) } diff --git a/Source/Edge/Profile.swift b/Source/Edge/Profile.swift index eaf70af..c3d5ae6 100644 --- a/Source/Edge/Profile.swift +++ b/Source/Edge/Profile.swift @@ -8,8 +8,8 @@ import Foundation -func Profile(config: Config, client: Client, traits: NSDictionary, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask { - let url = config.edgeURL("profile") - let req = try client.postRequest(url: url!, data: ["traits": traits]) - return client.dispatchRequest(req!, completionHandler) +func Profile(config: Config, client: Client, traits: NSDictionary, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask? { + guard let url = config.edgeURL("profile") else { return nil } + let req = try client.postRequest(url: url, data: ["traits": traits]) + return client.dispatchRequest(req, completionHandler) } diff --git a/Source/Edge/Targeting.swift b/Source/Edge/Targeting.swift index 0cb058c..12701c1 100644 --- a/Source/Edge/Targeting.swift +++ b/Source/Edge/Targeting.swift @@ -8,8 +8,8 @@ import Foundation -func Targeting(config: Config, client: Client, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask { - let url = config.edgeURL("targeting") - let req = try client.getRequest(url: url!) - return client.dispatchRequest(req!, completionHandler) +func Targeting(config: Config, client: Client, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask? { + guard let url = config.edgeURL("targeting") else { return nil } + let req = try client.getRequest(url: url) + return client.dispatchRequest(req, completionHandler) } diff --git a/Source/Edge/Witness.swift b/Source/Edge/Witness.swift index 11d2a0a..1b4d9e3 100644 --- a/Source/Edge/Witness.swift +++ b/Source/Edge/Witness.swift @@ -8,8 +8,8 @@ import Foundation -func Witness(config: Config, client: Client, event: String, properties: NSDictionary, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask { - let url = config.edgeURL("witness") - let req = try client.postRequest(url: url!, data: ["event": event, "properties": properties]) - return client.dispatchRequest(req!, completionHandler) +func Witness(config: Config, client: Client, event: String, properties: NSDictionary, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) throws -> URLSessionDataTask? { + guard let url = config.edgeURL("witness") else { return nil } + let req = try client.postRequest(url: url, data: ["event": event, "properties": properties]) + return client.dispatchRequest(req, completionHandler) } diff --git a/Source/OptableSDK.swift b/Source/OptableSDK.swift index 6dbe181..b73f603 100644 --- a/Source/OptableSDK.swift +++ b/Source/OptableSDK.swift @@ -77,21 +77,25 @@ public class OptableSDK: NSObject { // public func identify(ids: [String], _ completion: @escaping (Result) -> Void) throws -> Void { try Identify(config: self.config, client: self.client, ids: ids) { (data, response, error) in - guard let response = response as? HTTPURLResponse, error == nil else { - completion(.failure(OptableError.identify("Session error: \(error!)"))) + guard let response = response as? HTTPURLResponse, error == nil, data != nil else { + if let err = error { + completion(.failure(OptableError.identify("Session error: \(err)"))) + } else { + completion(.failure(OptableError.identify("Session error: Unknown"))) + } return } guard 200 ..< 300 ~= response.statusCode else { var msg = "HTTP response.statusCode: \(response.statusCode)" do { - let json = try JSONSerialization.jsonObject(with: data!, options: []) + let json = try JSONSerialization.jsonObject(with: data ?? Data(), options: []) msg += ", data: \(json)" } catch {} completion(.failure(OptableError.identify(msg))) return } completion(.success(response)) - }.resume() + }?.resume() } // @@ -178,8 +182,12 @@ public class OptableSDK: NSObject { // public func targeting(_ completion: @escaping (Result) -> Void) throws -> Void { try Targeting(config: self.config, client: self.client) { (data, response, error) in - guard let response = response as? HTTPURLResponse, error == nil else { - completion(.failure(OptableError.targeting("Session error: \(error!)"))) + guard let response = response as? HTTPURLResponse, error == nil, data != nil else { + if let err = error { + completion(.failure(OptableError.targeting("Session error: \(err)"))) + } else { + completion(.failure(OptableError.targeting("Session error: Unknown"))) + } return } guard 200 ..< 300 ~= response.statusCode else { @@ -193,17 +201,17 @@ public class OptableSDK: NSObject { } do { - let keyvalues = try JSONSerialization.jsonObject(with: data!, options: []) - let result = keyvalues as? NSDictionary + let keyvalues = try JSONSerialization.jsonObject(with: data ?? Data(), options: []) + let result = keyvalues as? NSDictionary ?? NSDictionary() // We cache the latest targeting result in client storage for targetingFromCache() users: - self.client.storage.setTargeting(keyvalues as! [String: Any]) + self.client.storage.setTargeting(keyvalues as? [String: Any] ?? [String: Any]()) - completion(.success(result!)) + completion(.success(result)) } catch { completion(.failure(OptableError.targeting("Error parsing JSON response: \(error)"))) } - }.resume() + }?.resume() } // @@ -229,11 +237,10 @@ public class OptableSDK: NSObject { // @objc public func targetingFromCache() -> NSDictionary? { - let keyvalues = self.client.storage.getTargeting() - if (keyvalues == nil) { + guard let keyvalues = self.client.storage.getTargeting() as NSDictionary? else { return nil } - return (keyvalues! as NSDictionary) + return keyvalues } // @@ -255,20 +262,24 @@ public class OptableSDK: NSObject { public func witness(event: String, properties: NSDictionary, _ completion: @escaping (Result) -> Void) throws -> Void { try Witness(config: self.config, client: self.client, event: event, properties: properties) { (data, response, error) in guard let response = response as? HTTPURLResponse, error == nil else { - completion(.failure(OptableError.witness("Session error: \(error!)"))) + if let err = error { + completion(.failure(OptableError.witness("Session error: \(err)"))) + } else { + completion(.failure(OptableError.witness("Session error: Unknown"))) + } return } guard 200 ..< 300 ~= response.statusCode else { var msg = "HTTP response.statusCode: \(response.statusCode)" do { - let json = try JSONSerialization.jsonObject(with: data!, options: []) + let json = try JSONSerialization.jsonObject(with: data ?? Data(), options: []) msg += ", data: \(json)" } catch {} completion(.failure(OptableError.witness(msg))) return } completion(.success(response)) - }.resume() + }?.resume() } // @@ -300,20 +311,24 @@ public class OptableSDK: NSObject { public func profile(traits: NSDictionary, _ completion: @escaping (Result) -> Void) throws -> Void { try Profile(config: self.config, client: self.client, traits: traits) { (data, response, error) in guard let response = response as? HTTPURLResponse, error == nil else { - completion(.failure(OptableError.profile("Session error: \(error!)"))) + if let err = error { + completion(.failure(OptableError.profile("Session error: \(err)"))) + } else { + completion(.failure(OptableError.profile("Session error: Unknown"))) + } return } guard 200 ..< 300 ~= response.statusCode else { var msg = "HTTP response.statusCode: \(response.statusCode)" do { - let json = try JSONSerialization.jsonObject(with: data!, options: []) + let json = try JSONSerialization.jsonObject(with: data ?? Data(), options: []) msg += ", data: \(json)" } catch {} completion(.failure(OptableError.profile(msg))) return } completion(.success(response)) - }.resume() + }?.resume() } //