RxSwift возобновить токен аутентификации - PullRequest
2 голосов
/ 17 апреля 2019

Hy Я пытаюсь найти решение, использующее 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
    }
}

1 Ответ

1 голос
/ 18 апреля 2019

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

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

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

...