How to using ReactiveCocoa to transparently authenticate before making API calls?

后端 未结 3 620
小蘑菇
小蘑菇 2020-12-22 21:34

I am using ReactiveCocoa in an app which makes calls to remote web APIs. But before any thing can be retrieved from a given API host, the app must provide the user\'s creden

3条回答
  •  夕颜
    夕颜 (楼主)
    2020-12-22 22:08

    Thinking about token will expire later and we have to refresh it.

    I store token in a MutableProperty, and used a lock to prevent multiple expired request to refresh the token, once the token is gained or refreshed, just request again with new token.

    For the first few requests, since there's no token, request signal will flatMap to error, and thus trigger refreshAT, meanwhile we do not have refreshToken, thus trigger refreshRT, and set both at and rt in the final step.

    here's full code

    static var headers = MutableProperty(["TICKET":""])
    static let atLock = NSLock()
    static let manager = Manager(
        configuration: NSURLSessionConfiguration.defaultSessionConfiguration()
    )
    
    internal static func GET(path:String!, params:[String: String]) -> SignalProducer<[String: AnyObject], NSError> {
        let reqSignal = SignalProducer<[String: AnyObject], NSError> {
            sink, dispose in
            manager.request(Router.GET(path: path, params: params))
            .validate()
            .responseJSON({ (response) -> Void in
                if let error = response.result.error {
                    sink.sendFailed(error)
                } else {
                    sink.sendNext(response.result.value!)
                    sink.sendCompleted()
                }
            })
        }
    
        return reqSignal.flatMapError { (error) -> SignalProducer<[String: AnyObject], NSError> in
                return HHHttp.refreshAT()
            }.flatMapError({ (error) -> SignalProducer<[String : AnyObject], NSError> in
                return HHHttp.refreshRT()
            }).then(reqSignal)
    }
    
    private static func refreshAT() -> SignalProducer<[String: AnyObject], NSError> {
        return SignalProducer<[String: AnyObject], NSError> {
            sink, dispose in
            if atLock.tryLock() {
                Alamofire.Manager.sharedInstance.request(.POST, "http://example.com/auth/refresh")
                    .validate()
                    .responseJSON({ (response) -> Void in
                        if let error = response.result.error {
                            sink.sendFailed(error)
                        } else {
                            let v = response.result.value!["data"]
                            headers.value.updateValue(v!["at"] as! String, forKey: "TICKET")
                            sink.sendCompleted()
                        }
                        atLock.unlock()
                    })
            } else {
                headers.signal.observe(Observer(next: { value in
                    print("get headers from local: \(value)")
                    sink.sendCompleted()
                }))
            }
        }
    }
    
    private static func refreshRT() -> SignalProducer<[String: AnyObject], NSError> {
        return SignalProducer<[String: AnyObject], NSError> {
            sink, dispose in
            Alamofire.Manager.sharedInstance.request(.POST, "http://example.com/auth/refresh")
            .responseJSON({ (response) -> Void in
                let v = response.result.value!["data"]                
                headers.value.updateValue(v!["at"] as! String, forKey: "TICKET")                
                sink.sendCompleted()
            })
        }
    }
    

提交回复
热议问题