Reputation: 31
i have request builder with alamofire, and sometimes I have 401 statusCode (about token what need to refresh). how do I need to change the class network in order to be able to refresh the token and resend the request?
when I receive statusCode 401 in the response - I try to call a refreshToken request, but I cannot add my loginService to this class, and I also cannot understand how to make a repeated request in this manager.
if you can, please write an example in the answer.
below is mine Network class:
class Network {
typealias Method = Alamofire.HTTPMethod
typealias Headers = HTTPHeaders
typealias ResponseResult = Result<Response, Error>
typealias Completion = (ResponseResult) -> Void
// MARK: - Properties
lazy var baseURL: URL = {
#if PROD
return URL(string: "https://xxxxx.xxxx.com/")!
#else
return URL(string: "https://xxxxx.xxxx.com/")!
#endif
}()
func request(_ target: RequestConvertible,
queue: DispatchQueue,
completion: @escaping Completion) {
do {
let request = try makeURLRequest(for: target)
performRequest(AF.request(request), queue: queue, target: target, completion: completion)
} catch {
completion(.failure(error))
}
}
func requestWithDecode<T: Decodable>(_ target: RequestConvertible,
value: T.Type,
queue: DispatchQueue,
completion: @escaping (Result<T, Error>) -> Void) {
request(target, queue: queue) { [weak self] result in
switch result {
case .success(let response):
if response.statusCode == 401 {
debugPrint("401 status code")
}
do {
completion(try self?.handleResponse(response: response, value: value)
?? .failure(CommonError.undefined))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
// MARK: - Private
private func handleResponse<T: Decodable>(response: Response, value: T.Type) throws -> Result<T, Error> {
let apiResponse = try response.decode(APIResponse<T>.self)
if let result = apiResponse.data {
return .success(result)
} else if let error = apiResponse.errors {
return .failure(error)
} else {
return .failure(CommonError.custom(apiResponse.errors?.message.joined(separator: " ") ?? ""))
}
}
private func makeURLRequest(for target: RequestConvertible) throws -> URLRequest {
let url = target.baseURL ?? baseURL
let pathURL = target.path.isEmpty ? url : url.appendingPathComponent(target.path)
var request = try URLRequest(url: pathURL).encoded(for: target)
request.httpMethod = target.method.rawValue
target.headers?.dictionary.forEach { request.setValue($1, forHTTPHeaderField: $0) }
return request
}
private func performRequest(_ request: DataRequest,
queue: DispatchQueue,
target: RequestConvertible,
completion: @escaping Completion) {
request.responseData(queue: queue) { responseData in
guard let response = responseData.response else {
return completion(.failure(responseData.error ?? CommonError.undefined))
}
completion(Result { Response(data: responseData.data ?? Data(), response: response) })
}
request.resume()
}
}
here is my RequestIntercepto, but how to make it work?
extension Network: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var request = urlRequest
guard let token = UserDefaultService.token() else {
completion(.success(urlRequest))
return
}
let bearerToken = "Bearer \(token)"
request.setValue(bearerToken, forHTTPHeaderField: "Authorization")
print("\nadapted; token added to the header field is: \(bearerToken)\n")
completion(.success(request))
}
func retry(_ request: Request, for session: Session, dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
guard let statusCode = request.response?.statusCode else {
completion(.doNotRetry)
return
}
guard request.retryCount < retryLimit else {
completion(.doNotRetry)
return
}
print("retry statusCode....\(statusCode)")
switch statusCode {
case 200...299:
completion(.doNotRetry)
case 401:
refreshToken { isSuccess in isSuccess ? completion(.retry) : completion(.doNotRetry) }
break
default:
completion(.retry)
}
}
func refreshToken(completion: @escaping (_ isSuccess: Bool) -> Void) {
let url = URL(string: "https://xxxxx.xxxx.com/auth/refresh-token")!
AF.request(url, method: .post, parameters: nil, encoding: JSONEncoding.default).responseJSON { response in
if let data = response.data
, let token = (try? JSONSerialization.jsonObject(with: data, options: [])
as? [String: Any])?["access_token"] as? String
{
UserDefaultService.setToken(Token(accessToken: "\(token)", refreshToken: "\(token)", expiresIn: 7200))
print("\nRefresh token completed successfully. New token is: \(token)\n")
completion(true)
} else {
completion(false)
}
}
}
}
Upvotes: 0
Views: 218