165 lines
5.8 KiB
Swift
165 lines
5.8 KiB
Swift
//
|
|
// Client.swift
|
|
// FitKit
|
|
//
|
|
// Created by Ian Fijolek on 1/6/18.
|
|
// Copyright © 2018 iamthefij. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import Alamofire
|
|
import OAuthSwift
|
|
import Locksmith
|
|
import OAuthSwiftAlamofire
|
|
|
|
let ACCOUNT_NAME = "FitBit"
|
|
let SERVICE_NAME = "FitKit"
|
|
|
|
class FitbitClient {
|
|
private enum FitKitRequestError: Error {
|
|
case unableToParseResults
|
|
}
|
|
|
|
private var oauthClient: OAuth2Swift
|
|
private let consumerKey = "22CPJ4"
|
|
private let consumerSecret = "eb05e27f6aa224bcc1cf273119565b28"
|
|
private let callbackUrl = "fitkit://oauth-callback/fitbit"
|
|
private let authorizeUrl = "https://www.fitbit.com/oauth2/authorize"
|
|
private let accessTokenUrl = "https://api.fitbit.com/oauth2/token"
|
|
|
|
init() {
|
|
// Init inner client
|
|
self.oauthClient = OAuth2Swift(
|
|
consumerKey: self.consumerKey,
|
|
consumerSecret: self.consumerSecret,
|
|
authorizeUrl: self.authorizeUrl,
|
|
accessTokenUrl: self.accessTokenUrl,
|
|
responseType: "token"
|
|
)
|
|
|
|
// Set OAuth client to handle default sessions
|
|
let sessionManager = SessionManager.default
|
|
sessionManager.adapter = self.oauthClient.requestAdapter
|
|
sessionManager.retrier = self.oauthClient.requestAdapter
|
|
}
|
|
|
|
func loadStoredTokens() {
|
|
NSLog("Someday we'll load stored credentials here! Today is not that day")
|
|
/*
|
|
let (oAuthData, _) = Locksmith.loadDataForUserAccount(userAccount: ACCOUNT_NAME, inService: SERVICE_NAME)
|
|
if let accessToken = oAuthData?.objectForKey("accessToken") {
|
|
// We have the token!!
|
|
self.oauthClient.client.credentials.oauthToken = accessToken
|
|
} else {
|
|
// No token! Boo
|
|
}
|
|
*/
|
|
}
|
|
|
|
/// Check if the current client is already authorized
|
|
///
|
|
/// - Returns: True if the current client can make requests
|
|
func isAuthorized() -> Bool {
|
|
if self.oauthClient.client.credential.oauthToken != "" {
|
|
if self.oauthClient.client.credential.isTokenExpired() {
|
|
NSLog("Credentials are expired")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// TODO: This should probably return true sometimes
|
|
return false
|
|
}
|
|
|
|
/// Displays authorization screen for user in a Safari web view
|
|
///
|
|
/// - Parameters:
|
|
/// - viewController: the source controller to create the new Safari view
|
|
/// - success: Callback to be executed on success
|
|
/// - failure: Callback to be executed on failure
|
|
func authorize(viewController: UIViewController, success: @escaping () -> Void, failure: @escaping () -> Void) {
|
|
// Set webview handler
|
|
self.oauthClient.authorizeURLHandler = SafariURLHandler(
|
|
viewController: viewController,
|
|
oauthSwift: self.oauthClient
|
|
)
|
|
// Authorize
|
|
let _ = self.oauthClient.authorize(
|
|
withCallbackURL: URL(string: self.callbackUrl)!,
|
|
scope: "weight activity",
|
|
state:"FITKIT", // TODO: make CSRF token
|
|
success: {
|
|
credential, response, parameters in
|
|
NSLog("Succesfully authenticated with credentials %s", credential.oauthToken)
|
|
success()
|
|
},
|
|
failure: {
|
|
error in
|
|
// TODO: Maybe return the error
|
|
NSLog(error.localizedDescription)
|
|
failure()
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Request all weight data from Fitbit API for the current user beteween
|
|
/// two dates. Maximum allowed distance between the two dates is 31 days.
|
|
///
|
|
/// - Parameters:
|
|
/// - start: initial date to search from
|
|
/// - end: last date to include in search
|
|
/// - handler: function to handle the response
|
|
func getWeight(start: Date, end: Date, handler: @escaping (DataResponse<Any>) -> Void) {
|
|
let dateFormatter = DateFormatter()
|
|
dateFormatter.dateFormat = "yyyy-MM-dd"
|
|
|
|
let urlString = "https://api.fitbit.com/1/user/-/body/log/weight/date/\(dateFormatter.string(from: start))/\(dateFormatter.string(from: end)).json"
|
|
|
|
NSLog("Maing request to \(urlString)")
|
|
|
|
|
|
Alamofire.request(urlString)
|
|
.validate(contentType: ["application/json"])
|
|
.responseJSON(completionHandler: handler)
|
|
}
|
|
|
|
/// Handler that parses responses from Fitbit Weight API and provides
|
|
/// instances of FitbitWeight to a new callback.
|
|
///
|
|
/// - Parameters:
|
|
/// - response: Original DataResponse<Any> from the Fitbit request
|
|
/// - callback: Callback that will accept a list of FitbitWeight instances
|
|
/// and an Error if, if present.
|
|
class func weightResponseHandler(response: DataResponse<Any>, callback: @escaping ([FitbitWeight]?, Error?) -> Void) {
|
|
NSLog("Handling weight response \(response.debugDescription)")
|
|
|
|
guard response.result.isSuccess else {
|
|
NSLog("Error while fetching weight: \(response.result.error!)")
|
|
callback(nil, response.result.error)
|
|
return
|
|
}
|
|
|
|
NSLog("Got weight! \(response.result.value!)")
|
|
|
|
guard let results = response.result.value as? [String: Any] else {
|
|
print("Return result not in form [String: Any]")
|
|
callback(nil, FitKitRequestError.unableToParseResults)
|
|
return
|
|
}
|
|
|
|
guard let weights = results["weight"] as? [[String: Any]] else {
|
|
print("Return result does not contain a list of weights")
|
|
callback(nil, FitKitRequestError.unableToParseResults)
|
|
return
|
|
}
|
|
|
|
var fbWeights = [FitbitWeight]()
|
|
for weight in weights {
|
|
fbWeights.append(FitbitWeight(withResult: weight))
|
|
}
|
|
|
|
callback(fbWeights, nil)
|
|
}
|
|
}
|