Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c

COCOAPODS: 1.9.0
COCOAPODS: 1.10.0

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import UIKit
import PetstoreClient

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
Expand All @@ -15,6 +16,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

// Customize requestBuilderFactory
PetstoreClientAPI.requestBuilderFactory = BearerRequestBuilderFactory()

return true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// BearerDecodableRequestBuilder.swift
// SwaggerClient
//
// Created by Bruno Coelho on 31/12/2020.
// Copyright © 2020 Swagger. All rights reserved.
//

import Foundation
import Alamofire
import PetstoreClient

class BearerRequestBuilderFactory: RequestBuilderFactory {
func getNonDecodableBuilder<T>() -> RequestBuilder<T>.Type {
BearerRequestBuilder<T>.self
}

func getBuilder<T: Decodable>() -> RequestBuilder<T>.Type {
BearerDecodableRequestBuilder<T>.self
}
}

class BearerRequestBuilder<T>: AlamofireRequestBuilder<T> {
override func createSessionManager() -> SessionManager {
let sessionManager = super.createSessionManager()

let bearerTokenHandler = BearerTokenHandler()
sessionManager.adapter = bearerTokenHandler
sessionManager.retrier = bearerTokenHandler

return sessionManager
}
}

class BearerDecodableRequestBuilder<T: Decodable>: AlamofireDecodableRequestBuilder<T> {
override func createSessionManager() -> SessionManager {
let sessionManager = super.createSessionManager()

let bearerTokenHandler = BearerTokenHandler()
sessionManager.adapter = bearerTokenHandler
sessionManager.retrier = bearerTokenHandler

return sessionManager
}
}

class BearerTokenHandler: RequestAdapter, RequestRetrier {
private static var bearerToken: String? = nil

func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
if let bearerToken = Self.bearerToken {
var urlRequest = urlRequest
urlRequest.setValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization")
return urlRequest
}

return urlRequest
}

func should(_: SessionManager, retry request: Request, with _: Error, completion: @escaping RequestRetryCompletion) {
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
Self.startRefreshingToken { isTokenRefreshed in
completion(isTokenRefreshed, 0.0)
}
} else {
completion(false, 0.0)
}
}

private static func startRefreshingToken(completionHandler: @escaping (Bool) -> Void) {
// Get a bearer token
let dummyBearerToken = "..."

bearerToken = dummyBearerToken
PetstoreClientAPI.customHeaders["Authorization"] = "Bearer \(dummyBearerToken)"

completionHandler(true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */; };
6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */; };
6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */; };
A5465867259E09C600C3929B /* BearerDecodableRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */; };
A5EA12642419439700E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12622419439700E30FC3 /* FileUtils.swift */; };
A5EA12652419439700E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12632419439700E30FC3 /* UIImage+Extras.swift */; };
FB5CCC7EFA680BB2746B695B /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */; };
Expand Down Expand Up @@ -49,6 +50,7 @@
6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAPITests.swift; sourceTree = "<group>"; };
7F98CC8B18E5FA9213F6A68D /* Pods_SwaggerClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClient.framework; sourceTree = BUILT_PRODUCTS_DIR; };
83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BearerDecodableRequestBuilder.swift; sourceTree = "<group>"; };
A5EA12622419439700E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
A5EA12632419439700E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = "<group>"; };
ACB80AC61FA8D8916D4559AA /* Pods-SwaggerClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -123,6 +125,7 @@
children = (
6D4EFB941C692C6300B96B06 /* AppDelegate.swift */,
6D4EFB961C692C6300B96B06 /* ViewController.swift */,
A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */,
6D4EFB981C692C6300B96B06 /* Main.storyboard */,
6D4EFB9B1C692C6300B96B06 /* Assets.xcassets */,
6D4EFB9D1C692C6300B96B06 /* LaunchScreen.storyboard */,
Expand Down Expand Up @@ -317,6 +320,7 @@
buildActionMask = 2147483647;
files = (
6D4EFB971C692C6300B96B06 /* ViewController.swift in Sources */,
A5465867259E09C600C3929B /* BearerDecodableRequestBuilder.swift in Sources */,
6D4EFB951C692C6300B96B06 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import UIKit
import PetstoreClient

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
Expand All @@ -15,6 +16,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

// Customize requestBuilderFactory
PetstoreClientAPI.requestBuilderFactory = BearerRequestBuilderFactory()

return true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// BearerDecodableRequestBuilder.swift
// SwaggerClient
//
// Created by Bruno Coelho on 31/12/2020.
// Copyright © 2020 Swagger. All rights reserved.
//

import Foundation
import PetstoreClient

class BearerRequestBuilderFactory: RequestBuilderFactory {
func getNonDecodableBuilder<T>() -> RequestBuilder<T>.Type {
BearerRequestBuilder<T>.self
}

func getBuilder<T: Decodable>() -> RequestBuilder<T>.Type {
BearerDecodableRequestBuilder<T>.self
}
}

class BearerRequestBuilder<T>: URLSessionRequestBuilder<T> {
override func execute(_ apiResponseQueue: DispatchQueue = PetstoreClientAPI.apiResponseQueue, _ completion: @escaping (Result<Response<T>, Error>) -> Void) {

// Before making the request, we can validate if we have a bearer token to be able to make a request
BearerTokenHandler.refreshTokenIfDoesntExist {

// Here we make the request
super.execute(apiResponseQueue) { result in

switch result {
case .success:
// If we got a successful response, we send the response to the completion block
completion(result)

case let .failure(error):

// If we got a failure response, we will analyse the error to see what we should do with it
if case let ErrorResponse.error(_, data, response, error) = error {

// If the error is an ErrorResponse.error() we will analyse it to see if it's a 401, and if it's a 401, we will refresh the token and retry the request
BearerTokenHandler.refreshTokenIfUnauthorizedRequestResponse(
data: data,
response: response,
error: error
) { wasTokenRefreshed in

if wasTokenRefreshed {
// If the token was refreshed, it's because it was a 401 error, so we refreshed the token, and we are going to retry the request by calling self.execute()
self.execute(apiResponseQueue, completion)
} else {
// If the token was not refreshed, it's because it was not a 401 error, so we send the response to the completion block
completion(result)
}
}
} else {
// If it's an unknown error, we send the response to the completion block
completion(result)
}

}
}
}
}
}

class BearerDecodableRequestBuilder<T: Decodable>: URLSessionDecodableRequestBuilder<T> {
override func execute(_ apiResponseQueue: DispatchQueue = PetstoreClientAPI.apiResponseQueue, _ completion: @escaping (Result<Response<T>, Error>) -> Void) {

// Before making the request, we can validate if we have a bearer token to be able to make a request
BearerTokenHandler.refreshTokenIfDoesntExist {

// Here we make the request
super.execute(apiResponseQueue) { result in

switch result {
case .success:
// If we got a successful response, we send the response to the completion block
completion(result)

case let .failure(error):

// If we got a failure response, we will analyse the error to see what we should do with it
if case let ErrorResponse.error(_, data, response, error) = error {

// If the error is an ErrorResponse.error() we will analyse it to see if it's a 401, and if it's a 401, we will refresh the token and retry the request
BearerTokenHandler.refreshTokenIfUnauthorizedRequestResponse(
data: data,
response: response,
error: error
) { wasTokenRefreshed in

if wasTokenRefreshed {
// If the token was refreshed, it's because it was a 401 error, so we refreshed the token, and we are going to retry the request by calling self.execute()
self.execute(apiResponseQueue, completion)
} else {
// If the token was not refreshed, it's because it was not a 401 error, so we send the response to the completion block
completion(result)
}
}
} else {
// If it's an unknown error, we send the response to the completion block
completion(result)
}

}
}
}
}
}

class BearerTokenHandler {
private static var bearerToken: String? = nil

static func refreshTokenIfDoesntExist(completionHandler: @escaping () -> Void) {
if bearerToken != nil {
completionHandler()
} else {
startRefreshingToken {
completionHandler()
}
}
}

static func refreshTokenIfUnauthorizedRequestResponse(data: Data?, response: URLResponse?, error: Error?, completionHandler: @escaping (Bool) -> Void) {
if let response = response as? HTTPURLResponse, response.statusCode == 401 {
startRefreshingToken {
completionHandler(true)
}
} else {
completionHandler(false)
}
}

private static func startRefreshingToken(completionHandler: @escaping () -> Void) {
// Get a bearer token
let dummyBearerToken = "..."

bearerToken = dummyBearerToken
PetstoreClientAPI.customHeaders["Authorization"] = "Bearer \(dummyBearerToken)"

completionHandler()
}
}