Evg Enii
Evg Enii

Reputation: 31

retry alamofire request when I get statusCode 401

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

Answers (0)

Related Questions