RxSwift продлить токен аутентификации

Я пытаюсь найти решение с использованием Moya и RxSwift, которое обновляет токен аутентификации и повторяет запросы.

Проблема в том, что у меня одновременно выполняется несколько запросов, поэтому допустим, что 10 запросов запускаются, пока срок действия токена аутентификации истек, я попытаюсь обновить токен на всех из них, и как только первый из них обновит другие не удастся, потому что они используют неправильный токен для продления.

Я хотел бы просто создать очередь (возможно) запросов, а затем повторить их. Не уверен, что это лучший сценарий для этого.

Вот что у меня есть на данный момент:

final class NetworkOnlineProvider {

    fileprivate let database = DatabaseClient(database: DatabaseRealm()).database
    fileprivate let provider: MoyaProvider<NetworkAPI>

    init(endpointClosure: @escaping MoyaProvider<NetworkAPI>.EndpointClosure = MoyaProvider<NetworkAPI>.defaultEndpointMapping,
         requestClosure: @escaping MoyaProvider<NetworkAPI>.RequestClosure = MoyaProvider<NetworkAPI>.defaultRequestMapping,
         stubClosure: @escaping MoyaProvider<NetworkAPI>.StubClosure = MoyaProvider.neverStub,
         manager: Manager = MoyaProvider<NetworkAPI>.defaultAlamofireManager(),
         plugins: [PluginType] = [],
         trackInflights: Bool = false) {

        self.provider = MoyaProvider(endpointClosure: endpointClosure, requestClosure: requestClosure, stubClosure: stubClosure, manager: manager, plugins: plugins, trackInflights: trackInflights)
    }

    fileprivate func getJWTRenewRequest() -> Single<Response>? {
        if let token = JWTManager.sharedInstance.token {

            return provider.rx.request(.renew(token: token))
        }

        return nil
    }

    func tokenRequest() -> Single<String> {
        let errorSingle = Single<String>.create { single in
            single(.error(APIError.failure))

            return Disposables.create()
        }

        let emptyJWTSingle = Single<String>.create { single in
            single(.success(""))

            return Disposables.create()
        }

        // Return if no token found
        guard let appToken = JWTManager.sharedInstance.getJWT() else {
            return refreshToken() ?? emptyJWTSingle
        }

        // If we have a valid token, just return it
        if !appToken.hasTokenExpired {
            return Single<String>.create { single in
                single(.success(appToken.token))

                return Disposables.create()
            }
        }

        // Token has expired
        let newTokenRequest = refreshToken()
        return newTokenRequest ?? errorSingle
    }

    func refreshToken() -> Single<String>? {
        return getJWTRenewRequest()?
            .debug("Renewing JWT")
            .filterSuccessfulStatusCodes()
            .map { (response: Response) -> (token: String, expiration: Double) in
                guard let json = try? JSON(data: response.data) else { throw RxError.unknown }

                let success = json["success"]

                guard
                    let jwt = success["jwt"].string,
                    let jwt_expiration = success["jwt_expiration"].double,
                    let valid_login = success["valid_login"].bool, valid_login
                    else { throw RxError.unknown }

                return (token: jwt, expiration: jwt_expiration)
            }
            .do(onSuccess: { (token: String, expiration: Double) in
                JWTManager.sharedInstance.save(token: JWT(token: token, expiration: String(expiration)))
            })
            .map { (token: String, expiration: Double) in
                return token
            }
            .catchError { e -> Single<String> in
                print("Failed to Renew JWT")
                JWTManager.sharedInstance.delete()

                UIApplication.shared.appDelegate.cleanPreviousContext(jwt: true)
                let loginVC = UIStoryboard(storyboard: .login).instantiateViewController(vc: LoginViewController.self)
                UIApplication.shared.appDelegate.window?.setRootViewController(UINavigationController(rootViewController: loginVC))

                throw e
            }
    }

    func request(_ target: NetworkAPI) -> Single<Response> {
        let actualRequest = provider.rx.request(target)

        if target.isAuthenticatedCall {
            return tokenRequest().flatMap { _ in
                actualRequest
            }
        }


        return actualRequest
    }
}

person Diogo Antunes    schedule 17.04.2019    source источник


Ответы (1)


Решение здесь: RxSwift и Повторная попытка сетевого запроса, несмотря на недействительный токен

Ключ в том, чтобы использовать flatMapFirst, чтобы вы делали только один запрос для первого 401 и игнорировали другие 401, пока этот запрос находится в полете.

Суть статьи включает в себя модульные тесты, подтверждающие ее работу.

person Daniel T.    schedule 17.04.2019