From 7f1a26000d04c2e6ed28e0ff9bb5a73f865c1d51 Mon Sep 17 00:00:00 2001 From: dabear Date: Fri, 2 Jun 2017 20:55:59 +0200 Subject: [PATCH 1/4] Support US, NON_US and Custom ShareServer during initialization --- ShareClient/ShareClient.swift | 63 ++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/ShareClient/ShareClient.swift b/ShareClient/ShareClient.swift index 435bbdd..6a50ea0 100644 --- a/ShareClient/ShareClient.swift +++ b/ShareClient/ShareClient.swift @@ -26,6 +26,12 @@ public enum ShareError: Error { case dateError } +public enum ShareServer { + case US + case Non_US + case Custom(String) +} + // From the Dexcom Share iOS app, via @bewest and @shanselman: // https://github.com/bewest/share2nightscout-bridge private let dexcomUserAgent = "Dexcom Share/3.0.2.11 CFNetwork/711.2.23 Darwin/14.0.0" @@ -41,22 +47,22 @@ private let maxReauthAttempts = 2 // ¯\_(ツ)_/¯ private func dexcomPOST(_ url: URL, JSONData: [String: AnyObject]? = nil, callback: @escaping (Error?, String?) -> Void) { var data: Data? - + if let JSONData = JSONData { guard let encoded = try? JSONSerialization.data(withJSONObject: JSONData, options:[]) else { return callback(ShareError.dataError(reason: "Failed to encode JSON for POST to " + url.absoluteString), nil) } - + data = encoded } - + var request = URLRequest(url: url) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue(dexcomUserAgent, forHTTPHeaderField: "User-Agent") request.httpBody = data - + URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in if error != nil { callback(error, nil) @@ -69,18 +75,27 @@ private func dexcomPOST(_ url: URL, JSONData: [String: AnyObject]? = nil, callba public class ShareClient { public let username: String public let password: String - + + private let shareServer:String? private var token: String? - - public init(username: String, password: String) { + + public init(username: String, password: String, shareServer:ShareServer=ShareServer.US) { self.username = username self.password = password + switch shareServer { + case .US: + self.shareServer=dexcomServerUS + case .Non_US: + self.shareServer=dexcomServerNonUS + case .Custom(let url): + self.shareServer=url + } } public func fetchLast(_ n: Int, callback: @escaping (ShareError?, [ShareGlucose]?) -> Void) { fetchLastWithRetries(n, remaining: maxReauthAttempts, callback: callback) } - + private func ensureToken(_ callback: @escaping (ShareError?) -> Void) { if token != nil { callback(nil) @@ -95,30 +110,30 @@ public class ShareClient { } } } - + private func fetchToken(_ callback: @escaping (ShareError?, String?) -> Void) { let data = [ "accountName": username, "password": password, "applicationId": dexcomApplicationId ] - - guard let url = URL(string: dexcomServerUS + dexcomLoginPath) else { + + guard let url = URL(string: shareServer! + dexcomLoginPath) else { return callback(ShareError.fetchError, nil) } - + dexcomPOST(url, JSONData: data as [String : AnyObject]?) { (error, response) in if let error = error { return callback(.httpError(error), nil) } - + guard let response = response, let data = response.data(using: .utf8), let decoded = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { return callback(.loginError(errorCode: "unknown"), nil) } - + if let token = decoded as? String { // success is a JSON-encoded string containing the token callback(nil, token) @@ -129,37 +144,37 @@ public class ShareClient { } } } - + private func fetchLastWithRetries(_ n: Int, remaining: Int, callback: @escaping (ShareError?, [ShareGlucose]?) -> Void) { ensureToken() { (error) in guard error == nil else { return callback(error, nil) } - - guard var components = URLComponents(string: dexcomServerUS + dexcomLatestGlucosePath) else { + + guard var components = URLComponents(string: self.shareServer! + dexcomLatestGlucosePath) else { return callback(.fetchError, nil) } - + components.queryItems = [ URLQueryItem(name: "sessionId", value: self.token), URLQueryItem(name: "minutes", value: String(1440)), URLQueryItem(name: "maxCount", value: String(n)) ] - + guard let url = components.url else { return callback(.fetchError, nil) } - + dexcomPOST(url) { (error, response) in if let error = error { return callback(.httpError(error), nil) } - + do { guard let response = response else { throw ShareError.fetchError } - + let decoded = try? JSONSerialization.jsonObject(with: response.data(using: .utf8)!, options: []) guard let sgvs = decoded as? Array else { if remaining > 0 { @@ -169,7 +184,7 @@ public class ShareClient { throw ShareError.dataError(reason: "Failed to decode SGVs as array after trying to reauth: " + response) } } - + var transformed: Array = [] for sgv in sgvs { if let glucose = sgv["Value"] as? Int, let trend = sgv["Trend"] as? Int, let wt = sgv["WT"] as? String { @@ -191,7 +206,7 @@ public class ShareClient { } } } - + private func parseDate(_ wt: String) throws -> Date { // wt looks like "/Date(1462404576000)/" let re = try NSRegularExpression(pattern: "\\((.*)\\)", options: []) From da85d4756e4d4a651937ce27e7c88da3a43b5648 Mon Sep 17 00:00:00 2001 From: dabear Date: Sun, 4 Jun 2017 00:37:49 +0200 Subject: [PATCH 2/4] remove unnecessary optional and unwrapping --- ShareClient/ShareClient.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ShareClient/ShareClient.swift b/ShareClient/ShareClient.swift index 6a50ea0..64195a7 100644 --- a/ShareClient/ShareClient.swift +++ b/ShareClient/ShareClient.swift @@ -76,7 +76,7 @@ public class ShareClient { public let username: String public let password: String - private let shareServer:String? + private let shareServer:String private var token: String? public init(username: String, password: String, shareServer:ShareServer=ShareServer.US) { @@ -118,7 +118,7 @@ public class ShareClient { "applicationId": dexcomApplicationId ] - guard let url = URL(string: shareServer! + dexcomLoginPath) else { + guard let url = URL(string: shareServer + dexcomLoginPath) else { return callback(ShareError.fetchError, nil) } @@ -151,7 +151,7 @@ public class ShareClient { return callback(error, nil) } - guard var components = URLComponents(string: self.shareServer! + dexcomLatestGlucosePath) else { + guard var components = URLComponents(string: self.shareServer + dexcomLatestGlucosePath) else { return callback(.fetchError, nil) } From d1822c0edfaaafeb3ac1fcb83a0d64eb1b8b8e17 Mon Sep 17 00:00:00 2001 From: dabear Date: Sun, 4 Jun 2017 00:58:25 +0200 Subject: [PATCH 3/4] remove trailing whitespaces as requested --- ShareClient/ShareClient.swift | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/ShareClient/ShareClient.swift b/ShareClient/ShareClient.swift index 64195a7..886258c 100644 --- a/ShareClient/ShareClient.swift +++ b/ShareClient/ShareClient.swift @@ -47,22 +47,22 @@ private let maxReauthAttempts = 2 // ¯\_(ツ)_/¯ private func dexcomPOST(_ url: URL, JSONData: [String: AnyObject]? = nil, callback: @escaping (Error?, String?) -> Void) { var data: Data? - + if let JSONData = JSONData { guard let encoded = try? JSONSerialization.data(withJSONObject: JSONData, options:[]) else { return callback(ShareError.dataError(reason: "Failed to encode JSON for POST to " + url.absoluteString), nil) } - + data = encoded } - + var request = URLRequest(url: url) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue(dexcomUserAgent, forHTTPHeaderField: "User-Agent") request.httpBody = data - + URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in if error != nil { callback(error, nil) @@ -75,10 +75,10 @@ private func dexcomPOST(_ url: URL, JSONData: [String: AnyObject]? = nil, callba public class ShareClient { public let username: String public let password: String - + private let shareServer:String private var token: String? - + public init(username: String, password: String, shareServer:ShareServer=ShareServer.US) { self.username = username self.password = password @@ -95,7 +95,7 @@ public class ShareClient { public func fetchLast(_ n: Int, callback: @escaping (ShareError?, [ShareGlucose]?) -> Void) { fetchLastWithRetries(n, remaining: maxReauthAttempts, callback: callback) } - + private func ensureToken(_ callback: @escaping (ShareError?) -> Void) { if token != nil { callback(nil) @@ -110,30 +110,30 @@ public class ShareClient { } } } - + private func fetchToken(_ callback: @escaping (ShareError?, String?) -> Void) { let data = [ "accountName": username, "password": password, "applicationId": dexcomApplicationId ] - + guard let url = URL(string: shareServer + dexcomLoginPath) else { return callback(ShareError.fetchError, nil) } - + dexcomPOST(url, JSONData: data as [String : AnyObject]?) { (error, response) in if let error = error { return callback(.httpError(error), nil) } - + guard let response = response, let data = response.data(using: .utf8), let decoded = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { return callback(.loginError(errorCode: "unknown"), nil) } - + if let token = decoded as? String { // success is a JSON-encoded string containing the token callback(nil, token) @@ -144,37 +144,37 @@ public class ShareClient { } } } - + private func fetchLastWithRetries(_ n: Int, remaining: Int, callback: @escaping (ShareError?, [ShareGlucose]?) -> Void) { ensureToken() { (error) in guard error == nil else { return callback(error, nil) } - + guard var components = URLComponents(string: self.shareServer + dexcomLatestGlucosePath) else { return callback(.fetchError, nil) } - + components.queryItems = [ URLQueryItem(name: "sessionId", value: self.token), URLQueryItem(name: "minutes", value: String(1440)), URLQueryItem(name: "maxCount", value: String(n)) ] - + guard let url = components.url else { return callback(.fetchError, nil) } - + dexcomPOST(url) { (error, response) in if let error = error { return callback(.httpError(error), nil) } - + do { guard let response = response else { throw ShareError.fetchError } - + let decoded = try? JSONSerialization.jsonObject(with: response.data(using: .utf8)!, options: []) guard let sgvs = decoded as? Array else { if remaining > 0 { @@ -184,7 +184,7 @@ public class ShareClient { throw ShareError.dataError(reason: "Failed to decode SGVs as array after trying to reauth: " + response) } } - + var transformed: Array = [] for sgv in sgvs { if let glucose = sgv["Value"] as? Int, let trend = sgv["Trend"] as? Int, let wt = sgv["WT"] as? String { @@ -206,7 +206,7 @@ public class ShareClient { } } } - + private func parseDate(_ wt: String) throws -> Date { // wt looks like "/Date(1462404576000)/" let re = try NSRegularExpression(pattern: "\\((.*)\\)", options: []) From b27d5ed5175453813b0c66baa8a9bf12f08a9542 Mon Sep 17 00:00:00 2001 From: dabear Date: Sat, 17 Jun 2017 11:41:38 +0200 Subject: [PATCH 4/4] expose list of known share urls, make init take shareserver as a strign parameter --- ShareClient/ShareClient.swift | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/ShareClient/ShareClient.swift b/ShareClient/ShareClient.swift index 886258c..d5fcda8 100644 --- a/ShareClient/ShareClient.swift +++ b/ShareClient/ShareClient.swift @@ -26,10 +26,11 @@ public enum ShareError: Error { case dateError } -public enum ShareServer { - case US - case Non_US - case Custom(String) + +public enum KnownShareServers: String { + case US="https://share1.dexcom.com" + case NON_US="https://shareous1.dexcom.com" + } // From the Dexcom Share iOS app, via @bewest and @shanselman: @@ -38,8 +39,6 @@ private let dexcomUserAgent = "Dexcom Share/3.0.2.11 CFNetwork/711.2.23 Darwin/1 private let dexcomApplicationId = "d89443d2-327c-4a6f-89e5-496bbb0317db" private let dexcomLoginPath = "/ShareWebServices/Services/General/LoginPublisherAccountByName" private let dexcomLatestGlucosePath = "/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues" -private let dexcomServerUS = "https://share1.dexcom.com" -private let dexcomServerNonUS = "https://shareous1.dexcom.com" private let maxReauthAttempts = 2 // TODO use an HTTP library which supports JSON and futures instead of callbacks. @@ -79,17 +78,15 @@ public class ShareClient { private let shareServer:String private var token: String? - public init(username: String, password: String, shareServer:ShareServer=ShareServer.US) { + public init(username: String, password: String, shareServer:String=KnownShareServers.US.rawValue) { self.username = username self.password = password - switch shareServer { - case .US: - self.shareServer=dexcomServerUS - case .Non_US: - self.shareServer=dexcomServerNonUS - case .Custom(let url): - self.shareServer=url - } + self.shareServer = shareServer + } + public convenience init(username: String, password: String, shareServer:KnownShareServers=KnownShareServers.US) { + + self.init(username: username, password: password, shareServer:shareServer.rawValue) + } public func fetchLast(_ n: Int, callback: @escaping (ShareError?, [ShareGlucose]?) -> Void) {