Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save freshking/1c386d1bb5130bcc00449636e2be9243 to your computer and use it in GitHub Desktop.
Save freshking/1c386d1bb5130bcc00449636e2be9243 to your computer and use it in GitHub Desktop.
import Foundation
// OAuth2Token structure
struct OAuth2Token: Codable {
let date = Date() // date when the token was initialized
var accessToken: String // access token
var refreshToken: String? // refresh token (optional)
var expiresIn: Int // seconds until token expires
var tokenType: String // for example "Bearer"
// returns if token is still valid or has expired
var isValid: Bool {
let now = Date()
let seconds = TimeInterval(expiresIn)
return now.timeIntervalSince(date) < seconds
}
}
// Session handles all the requests and token
class Session {
// data task completion definition
typealias DataTaskCompletion = (Result<Any, Error>) -> Void
// OAuthRequest structure
struct OAuthRequest {
var url: URL
var completion: Session.DataTaskCompletion
}
private let oAuthRequestsQueue = DispatchQueue( label: "Session.oAuthRequestsQueue", attributes: .concurrent)
private var unsafeOAuthRequests = Array<OAuthRequest>()
private var token: OAuth2Token?
// begin a data request
func request(url: URL, completion: @escaping (Result<Any, Error>) -> Void) {
// construct the OAuthRequest in case we need it later
let oAuthRequest = OAuthRequest(url: url, completion: completion)
// check if a token is available
if let token = token {
// token found -> check if token is valid
if token.isValid == false {
// check if refresh token is available
if let token = token.refreshToken {
// safely add OAuthRequest to be sent later
safelyAddRequest(oAuthRequest: oAuthRequest)
// refresh current token
refreshToken(tokenString: token)
} else {
// safely add OAuthRequest to be sent later
safelyAddRequest(oAuthRequest: oAuthRequest)
// fetch new token
fetchToken()
}
} else {
// token is valid so now setup url request
var request = URLRequest(url: url)
request.setValue(token.tokenType + " " + token.accessToken, forHTTPHeaderField: "Authorization")
// and data task
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// handle the result of the request however you like
}
// begin task
task.resume()
}
} else {
// no token found -> save request to re-send after fetching token
// safely add OAuthRequest to be sent later
safelyAddRequest(oAuthRequest: oAuthRequest)
// fetch new OAuth2Token
fetchToken()
}
}
// fetches a new OAuth2Token
private func fetchToken() {
// this is where you actually fetch the OAuth2Token from your API provider
// after the fetch is complete, the new token must be assigned
token = <#OAuth2Token#>
// and then all the saved requests will be re-sent
safelySendAllRequests()
}
// refreshes the token using the current refresh token
private func refreshToken(tokenString: String) {
// this is where you refresh the token with your API provider
// after the refresh is complete, the refreshed token must be assigned
token = <#OAuth2Token#>
// and then all the saved requests will be re-sent
safelySendAllRequests()
}
// safely adds the OAuthRequests to the array
private func safelyAddRequest(oAuthRequest: OAuthRequest) {
oAuthRequestsQueue.async(flags: .barrier) { [weak self] in
self?.unsafeOAuthRequests.append(oAuthRequest)
}
}
// safely sends all saved OAuthRequests
private func safelySendAllRequests() {
oAuthRequestsQueue.async(flags: .barrier) { [weak self] in
self?.unsafeOAuthRequests.forEach { (oAuthRequest) in
self?.request(url: oAuthRequest.url, completion: oAuthRequest.completion)
}
self?.unsafeOAuthRequests.removeAll()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment