I'm implementing a way to refresh the session token using OAuth2 with AlamoFire5, and I'm trying to figure out how to solve this scenario:
1 - When some request fail a refreshToken request must start, that must be the only one refreshToken request running at a time. i.e. the other requests that failed should not be retried until that request finishes.
2 - If the refreshToken finishes with an error the app must restarts and all the other request that were waiting must be cancelled.
3 - If the the refreshToken request succeeds the token must be updated and all the other requests waiting must now continue.
I'm using the AlamoFire's RequestInterceptor class to try to solve this problem, and my implementation so far is this one:
final class RequestInterceptor: Alamofire.RequestInterceptor {
private let disposeBag = DisposeBag()
private let lock = NSRecursiveLock()
private var refreshTokenParameters: TokenParameters {
TokenParameters(clientId: "pdappclient",
grantType: "refresh_token",
refreshToken: KeychainManager.shared.refreshToken)
}
private let storage: AccessTokenStorage
init(storage: AccessTokenStorage) {
self.storage = storage
}
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.setValue("Bearer " + storage.accessToken, forHTTPHeaderField: "Authorization")
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
lock.lock()
defer { lock.unlock() }
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
return completion(.doNotRetryWithError(error))
}
let refreshTokenRequest: Single<TokenResponse> = NetworkManager.shared
.fetchData(fromApi: IdentityServerAPI.token(parameters: self.refreshTokenParameters))
refreshTokenRequest.subscribe(onSuccess: { token in
self.lock.unlock()
self.storage.accessToken = token.accessToken ?? ""
completion(.retry)
}, onError: { error in
self.lock.unlock()
completion(.doNotRetryWithError(error))
}).disposed(by: disposeBag)
}
}
How can I solve this case using the RequestInterceptor?