source/UberRides/RidesClient.swift (268 lines of code) (raw):
//
// RidesClient.swift
// UberRides
//
// Copyright © 2015 Uber Technologies, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import CoreLocation
import UberCore
/// API client for the Uber Rides API.
@objc(UBSDKRidesClient) public class RidesClient: NSObject {
/// Application client ID. Required for every instance of RidesClient.
var clientID: String = Configuration.shared.clientID
/// The Access Token Identifier. The identifier to use for looking up this client's accessToken
let accessTokenIdentifier: String
/// The Keychain Access Group. The access group to use when looking up this client's accessToken
let keychainAccessGroup: String
/// NSURLSession used to make requests to Uber API. Default session configuration unless otherwise initialized.
var session: URLSession
/// Developer server token.
private var serverToken: String? = Configuration.shared.serverToken
/**
Initializer for the RidesClient. The RidesClient handles making requests to the API
for you.
- parameter accessTokenIdentifier: The accessTokenIdentifier to use. This identifier
is used (along with keychainAccessGroup) to fetch the appropriate AccessToken. Defaults
to the value set in your Configuration struct
- parameter sessionConfiguration: Configuration to use for NSURLSession. Defaults to defaultSessionConfiguration.
- parameter keychainAccessGroup: The keychain access group to use. Uses this group
(along with the accessTokenIdentifier) to fetch the appropriate AccessToken. Defaults
to the value set in yoru Configuration struct
- returns: An initialized RidesClient
*/
@objc public init(accessTokenIdentifier: String, sessionConfiguration: URLSessionConfiguration, keychainAccessGroup: String) {
self.accessTokenIdentifier = accessTokenIdentifier
self.keychainAccessGroup = keychainAccessGroup
self.session = URLSession(configuration: sessionConfiguration)
}
/**
Initializer for the RidesClient. The RidesClient handles making requests to the API
for you.
By default, uses NSURLSessionConfiguration.defaultSessionConfiguration() for the URL requests
- parameter accessTokenIdentifier: Initializer for the RidesClient. The RidesClient handles making requests to the API
for you.
By default, it is initialized using the keychainAccessGroup default from your Configuration object
Also uses NSURLSessionConfiguration.defaultSessionConfiguration() for the URL requests
- parameter keychainAccessGroup: The keychain access group to use. Uses this group
(along with the accessTokenIdentifier) to fetch the appropriate AccessToken. Defaults
to the value set in yoru Configuration struct
- returns: An initialized RidesClient
*/
@objc public convenience init(accessTokenIdentifier: String, keychainAccessGroup: String) {
self.init(accessTokenIdentifier: accessTokenIdentifier,
sessionConfiguration: URLSessionConfiguration.default,
keychainAccessGroup: keychainAccessGroup)
}
/**
Initializer for the RidesClient. The RidesClient handles making requests to the API
for you.
By default, it is initialized using the keychainAccessGroup default from your Configuration object
- parameter accessTokenIdentifier: The accessTokenIdentifier to use. This identifier
is used (along with keychainAccessGroup) to fetch the appropriate AccessToken
- parameter sessionConfiguration: Configuration to use for NSURLSession. Defaults to defaultSessionConfiguration.
- returns: An initialized RidesClient
*/
@objc public convenience init(accessTokenIdentifier: String, sessionConfiguration: URLSessionConfiguration) {
self.init(accessTokenIdentifier: accessTokenIdentifier,
sessionConfiguration: sessionConfiguration,
keychainAccessGroup: Configuration.shared.defaultKeychainAccessGroup)
}
/**
Initializer for the RidesClient. The RidesClient handles making requests to the API
for you.
By default, it is initialized using the keychainAccessGroup default from your Configuration object
Also uses NSURLSessionConfiguration.defaultSessionConfiguration() for the URL requests
- parameter accessTokenIdentifier: The accessTokenIdentifier to use. This identifier
is used (along with keychainAccessGroup) to fetch the appropriate AccessToken
- returns: An initialized RidesClient
*/
@objc public convenience init(accessTokenIdentifier: String) {
self.init(accessTokenIdentifier: accessTokenIdentifier,
sessionConfiguration: URLSessionConfiguration.default,
keychainAccessGroup: Configuration.shared.defaultKeychainAccessGroup)
}
/**
Initializer for the RidesClient. The RidesClient handles making requests to the API
for you.
By default, it is initialized using the accessTokenIdentifier & keychainAccessGroup
defaults from your Configuration object
Also uses NSURLSessionConfiguration.defaultSessionConfiguration() for the URL requests
- returns: An initialized RidesClient
*/
@objc public convenience override init() {
self.init(accessTokenIdentifier: Configuration.shared.defaultAccessTokenIdentifier,
sessionConfiguration: URLSessionConfiguration.default,
keychainAccessGroup: Configuration.shared.defaultKeychainAccessGroup)
}
/**
Retrieves the token used by this rides client.
Currently pulls from the keychain each time.
- returns: an AccessToken object, or nil if one can't be located
*/
@objc public func fetchAccessToken() -> AccessToken? {
guard let accessToken = TokenManager.fetchToken(identifier: accessTokenIdentifier, accessGroup: keychainAccessGroup) else {
return nil
}
return accessToken
}
/**
Public getter to check for the existence of a server token.
- returns: true if a server token exists, false otherwise.
*/
@objc public var hasServerToken: Bool {
return serverToken != nil
}
// MARK: Helper functions
/**
Helper function to execute request. All endpoints should use this function.
- parameter endpoint: endpoint that conforms to APIEndpoint.
- parameter completion: completion block for when request is completed.
*/
private func apiCall(_ endpoint: APIEndpoint, completion: @escaping (_ response: Response) -> Void) {
let accessTokenString = fetchAccessToken()?.tokenString
guard let request = Request(session: session, endpoint: endpoint, serverToken: serverToken, bearerToken: accessTokenString) else {
let response = Response(data: nil, statusCode: 400, response: nil, error: UberError(status: 400, code: "bad_request", title: "Unable to create request"))
completion(response)
return
}
request.execute { response in
completion(response)
}
}
/**
Helper function to execute request that will return a Ride object.
- parameter endpoint: endpoint that conforms to APIEndpoint.
- parameter completion: user's completion block for returned ride.
*/
private func apiCallForRideResponse(_ endpoint: APIEndpoint, completion:@escaping (_ ride: Ride?, _ response: Response) -> Void) {
apiCall(endpoint, completion: { response in
var ride: Ride? = nil
if let data = response.data,
response.error == nil {
ride = try? JSONDecoder.uberDecoder.decode(Ride.self, from: data)
}
completion(ride, response)
})
}
// MARK: Endpoints
/**
Get all products at specified location.
- parameter location: coordinates of pickup location
- parameter completion: completion handler for returned products.
*/
@objc public func fetchProducts(pickupLocation location: CLLocation, completion:@escaping (_ products: [Product], _ response: Response) -> Void) {
let endpoint = Products.getAll(location: location)
apiCall(endpoint, completion: { response in
var products: UberProducts?
if let data = response.data,
response.error == nil {
products = try? JSONDecoder.uberDecoder.decode(UberProducts.self, from: data)
if let productList = products?.list {
completion(productList, response)
return
}
}
completion([], response)
})
}
/**
Get information for specific product.
- parameter productID: string representing product ID.
- parameter completion: completion handler for returned product.
*/
@objc public func fetchProduct(productID: String, completion:@escaping (_ product: Product?, _ response: Response) -> Void) {
let endpoint = Products.getProduct(productID: productID)
apiCall(endpoint, completion: { response in
var product: Product?
if let data = response.data,
response.error == nil {
product = try? JSONDecoder.uberDecoder.decode(Product.self, from: data)
}
completion(product, response)
})
}
/**
Get time estimates for all products (or specific product) at specified pickup location.
- parameter pickupLocation: coordinates of pickup location
- parameter productID: optional string representing the productID.
- parameter completion: completion handler for returned estimates.
*/
@objc public func fetchTimeEstimates(pickupLocation location: CLLocation, productID: String? = nil, completion:@escaping (_ timeEstimates: [TimeEstimate], _ response: Response) -> Void) {
let endpoint = Estimates.time(location: location, productID: productID)
apiCall(endpoint, completion: { response in
var timeEstimates: TimeEstimates?
if let data = response.data,
response.error == nil {
timeEstimates = try? JSONDecoder.uberDecoder.decode(TimeEstimates.self, from: data)
if let estimateList = timeEstimates?.list {
completion(estimateList, response)
return
}
}
completion([], response)
})
}
/**
Get price estimates for all products between specified pickup and dropoff locations.
- parameter pickupLocation: coordinates of pickup location.
- parameter dropoffLocation: coordinates of dropoff location
- parameter completion: completion handler for returned estimates.
*/
@objc public func fetchPriceEstimates(pickupLocation: CLLocation, dropoffLocation: CLLocation, completion:@escaping (_ priceEstimates: [PriceEstimate], _ response: Response) -> Void) {
let endpoint = Estimates.price(startLocation: pickupLocation,
endLocation: dropoffLocation)
apiCall(endpoint, completion: { response in
var priceEstimates: PriceEstimates?
if let data = response.data,
response.error == nil {
priceEstimates = try? JSONDecoder.uberDecoder.decode(PriceEstimates.self, from: data)
if let estimateList = priceEstimates?.list {
completion(estimateList, response)
return
}
}
completion([], response)
})
}
/**
Get trip history for current authenticated user.
- parameter offset: offset the list of returned results by this amount. Default is zero.
- parameter limit: number of items to retrieve. Default is 5, maximum is 50.
- parameter completion: completion handler for returned user trip history.
*/
@objc public func fetchTripHistory(offset: Int = 0, limit: Int = 5, completion:@escaping (_ tripHistory: TripHistory?, _ response: Response) -> Void) {
let endpoint = History.get(offset: offset, limit: limit)
apiCall(endpoint, completion: { response in
var history: TripHistory?
if let data = response.data,
response.error == nil {
history = try? JSONDecoder.uberDecoder.decode(TripHistory.self, from: data)
}
completion(history, response)
})
}
/**
Gets user profile of current authenticated user.
- parameter completion: completion handler for returned user profile.
*/
@objc public func fetchUserProfile(completion:@escaping (_ profile: UserProfile?, _ response: Response) -> Void) {
let endpoint = Me.userProfile
apiCall(endpoint, completion: { response in
var userProfile: UserProfile?
if let data = response.data,
response.error == nil {
userProfile = try? JSONDecoder.uberDecoder.decode(UserProfile.self, from: data)
}
completion(userProfile, response)
})
}
/**
Request a ride on behalf of Uber user.
- parameter parameters: RideParameters object containing paramaters for the request.
- parameter completion: completion handler for returned request information.
*/
@objc public func requestRide(parameters: RideParameters, completion:@escaping (_ ride: Ride?, _ response: Response) -> Void) {
let endpoint = Requests.make(rideParameters: parameters)
apiCallForRideResponse(endpoint, completion: completion)
}
/**
Get the real-time details for an ongoing ride.
- parameter completion: completion handler for returned ride information.
*/
@objc public func fetchCurrentRide(completion: @escaping (_ ride: Ride?, _ response: Response) -> Void) {
let endpoint = Requests.getCurrent
apiCallForRideResponse(endpoint, completion: completion)
}
/**
Get the status of an ongoing or completed ride that was created using the Ride Request endpoint.
- parameter requestID: unique identifier representing a Request.
- parameter completion: completion handler for returned trip information.
*/
@objc public func fetchRideDetails(requestID: String, completion:@escaping (_ ride: Ride? , _ response: Response) -> Void) {
let endpoint = Requests.getRequest(requestID: requestID)
apiCallForRideResponse(endpoint, completion: completion)
}
/**
Estimate a ride request given the desired product, start, and end locations.
- parameter rideParameters: RideParameters object containing necessary information.
- parameter completion: completion handler for returned estimate.
*/
@objc public func fetchRideRequestEstimate(parameters: RideParameters, completion:@escaping (_ estimate: RideEstimate?, _ response: Response) -> Void) {
let endpoint = Requests.estimate(rideParameters: parameters)
apiCall(endpoint, completion: { response in
var estimate: RideEstimate? = nil
if let data = response.data,
response.error == nil {
estimate = try? JSONDecoder.uberDecoder.decode(RideEstimate.self, from: data)
}
completion(estimate, response)
})
}
/**
Retrieve the list of the user’s available payment methods.
- parameter completion: completion handler for returned payment method list as well as last used payment method.
*/
@objc public func fetchPaymentMethods(completion:@escaping (_ methods: [PaymentMethod], _ lastUsed: PaymentMethod?, _ response: Response) -> Void) {
let endpoint = Payment.getMethods
apiCall(endpoint, completion: { response in
var paymentMethods = [PaymentMethod]()
var lastUsed: PaymentMethod?
if response.error == nil,
let data = response.data,
let allPayments = try? JSONDecoder.uberDecoder.decode(PaymentMethods.self, from: data),
let payments = allPayments.list {
paymentMethods = payments
lastUsed = paymentMethods.filter({$0.methodID == allPayments.lastUsed}).first
}
completion(paymentMethods, lastUsed, response)
})
}
/**
Retrieve home and work addresses from an Uber user's profile.
- parameter placeID: the name of the place to retrieve. Only home and work are acceptable.
- parameter completion: completion handler for returned place.
*/
@objc public func fetchPlace(placeID: String, completion:@escaping (_ place: Place?, _ response: Response) -> Void) {
let endpoint = Places.getPlace(placeID: placeID)
apiCall(endpoint, completion: { response in
var place: Place? = nil
if let data = response.data,
response.error == nil {
place = try? JSONDecoder.uberDecoder.decode(Place.self, from: data)
}
completion(place, response)
})
}
/**
Update home and work addresses for an Uber user's profile.
- parameter placeID: the name of the place to update. Only home and work are acceptable.
- parameter address: the address of the place that should be tied to the given placeID.
- parameter completion: completion handler for response.
*/
@objc public func updatePlace(placeID: String, withAddress address: String, completion:@escaping (_ place: Place?, _ response: Response) -> Void) {
let endpoint = Places.putPlace(placeID: placeID, address: address)
apiCall(endpoint, completion: { response in
var place: Place?
if let data = response.data,
response.error == nil {
place = try? JSONDecoder.uberDecoder.decode(Place.self, from: data)
}
completion(place, response)
})
}
/**
Update the ride details for an ongoing ride by ID.
- parameter requestID: the ID of the ride request. If nil, will attempt to update current trip.
- parameter rideParameters: the RideParameters object containing the updated parameters.
- parameter completion: completion handler for response.
*/
@objc public func updateRideDetails(requestID: String?, rideParameters: RideParameters, completion:@escaping (_ response: Response) -> Void) {
guard let requestID = requestID else {
updateCurrentRide(rideParameters: rideParameters, completion: completion)
return
}
let endpoint = Requests.patchRequest(requestID: requestID, rideParameters: rideParameters)
apiCall(endpoint, completion: { response in
completion(response)
})
}
/**
Update an ongoing request’s destination that was requested using the Ride Request endpoint.
- parameter rideParameters: RideParameters object with updated ride parameters.
- parameter completion: completion handler for response.
*/
@objc public func updateCurrentRide(rideParameters: RideParameters, completion:@escaping (_ response: Response) -> Void) {
let endpoint = Requests.patchCurrent(rideParameters: rideParameters)
apiCall(endpoint, completion: { response in
completion(response)
})
}
/**
Cancel a user's ride using the request ID.
- parameter requestID: request ID of the ride. If nil, current ride will be canceled.
- parameter completion: completion handler for response.
*/
@objc public func cancelRide(requestID: String?, completion:@escaping (_ response: Response) -> Void) {
guard let requestID = requestID else {
cancelCurrentRide(completion: completion)
return
}
let endpoint = Requests.deleteRequest(requestID: requestID)
apiCall(endpoint, completion: { response in
completion(response)
})
}
/**
Cancel the user's current trip. This endpoint can only be used on trips that your app requested.
- parameter completion: completion handler for response
*/
@objc public func cancelCurrentRide(completion:@escaping (_ response: Response) -> Void) {
let endpoint = Requests.deleteCurrent
apiCall(endpoint, completion: { response in
completion(response)
})
}
/**
Get the receipt information of a completed request.
- parameter requestID: unique identifier representing a ride request
- parameter completion: completion handler for receipt
*/
@objc public func fetchRideReceipt(requestID: String, completion:@escaping (_ rideReceipt: RideReceipt?, _ response: Response) -> Void) {
let endpoint = Requests.rideReceipt(requestID: requestID)
apiCall(endpoint, completion: { response in
var receipt: RideReceipt?
if let data = response.data,
response.error == nil {
receipt = try? JSONDecoder.uberDecoder.decode(RideReceipt.self, from: data)
}
completion(receipt, response)
})
}
/**
Get a map with a visual representation of a Request.
- parameter requestID: unique identifier representing a request
- parameter completion: completion handler for map
*/
@objc public func fetchRideMap(requestID: String, completion:@escaping (_ map: RideMap?, _ response: Response) -> Void) {
let endpoint = Requests.rideMap(requestID: requestID)
apiCall(endpoint, completion: { response in
var map: RideMap?
if let data = response.data,
response.error == nil {
map = try? JSONDecoder.uberDecoder.decode(RideMap.self, from: data)
}
completion(map, response)
})
}
/**
Get a refreshed AccessToken from a refresh token string. Only works for access
tokens retrieved via SSO
- parameter refreshToken: The Refresh Token String from an SSO access token
- parameter completion: completion handler for the new access token
*/
@objc public func refreshAccessToken(usingRefreshToken refreshToken: String, completion:@escaping (_ accessToken: AccessToken?, _ response: Response) -> Void) {
let endpoint = OAuth.refresh(clientID: clientID, refreshToken: refreshToken)
apiCall(endpoint) { response in
var accessToken: AccessToken?
if let data = response.data,
response.error == nil {
accessToken = try? AccessTokenFactory.createAccessToken(fromJSONData: data)
}
completion(accessToken, response)
}
}
}