sonsongithub / reddift

Swift Reddit API Wrapper
MIT License
242 stars 52 forks source link

Automatically renewing expired token #187

Closed jakunico closed 8 years ago

jakunico commented 8 years ago

Hi!

I noticed that token expires after some time and was wondering if you have any automatic mechanism to refresh it in place.

I noticed that you have a refreshToken function in the Session class which is amazing. So this is the expected behaviour:

  1. Request fails with 401
  2. Session attempts to refresh token
  3. (A) If refresh success, it performs request 1. again
  4. (B) If refresh failures, it just falls back to the failure of 1.

The thing is that this mechanism should be happen inside the session object (or at some low level) and not on consumer level.

Questions:

  1. Do you have any suggestion how to tackle this token expiring issue as seamlessly as possible?
  2. Any thoughts on how to implement it?

Let me know what you think and I can give it a shot

sonsongithub commented 8 years ago

Your suggestion is good.

My answer is as follow,

  1. I've thought that each programmer should handle the 401 error in each case.
  2. I like the behavior you gave.
  3. Sorry, I don't have any good suggestion for what you pointed out.

I don't have any good idea write now. I do want to handle it.....How can I add to Session the code to handle error 401 with only solid codes...

jakunico commented 8 years ago

I'll check this out this week and let you know

sonsongithub commented 8 years ago

Very ugly code. That's too bad.

I think this code can be shorten and beautified using blocks and generics.....maybe.

/**
 Search subreddits by title and description.

 - parameter query: The search keywords, must be less than 512 characters.
 - parameter paginator: Paginator object for paging.
 - parameter completion: The completion handler to call when the load request is complete.
 - returns: Data task which requests search to reddit.com.
 */
public func getSubredditSearchWithErrorHandling(query: String, paginator: Paginator, completion: (Result<Listing>) -> Void) throws -> NSURLSessionDataTask? {
    let parameter = paginator.addParametersToDictionary(["q":query])
    guard let request = NSMutableURLRequest.mutableOAuthRequestWithBaseURL(baseURL, path:"/subreddits/search", parameter:parameter, method:"GET", token:token)
        else { throw ReddiftError.URLError.error }
    let task = URLSession.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
        self.updateRateLimitWithURLResponse(response)
        let result: Result<Listing> = resultFromOptionalError(Response(data: data, urlResponse: response), optionalError:error)
            .flatMap(response2Data)
            .flatMap(data2Json)
            .flatMap(json2RedditAny)
            .flatMap(redditAny2Object)
        switch result {
        case .Failure(let error):
            if error.code == 401 {
                let task = self.URLSession.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
                    self.updateRateLimitWithURLResponse(response)
                    let result: Result<Listing> = resultFromOptionalError(Response(data: data, urlResponse: response), optionalError:error)
                        .flatMap(response2Data)
                        .flatMap(data2Json)
                        .flatMap(json2RedditAny)
                        .flatMap(redditAny2Object)
                    completion(result)
                })
                task.resume()
            } else {
                completion(result)
            }
        case .Success:
            completion(result)
        }
    })
    task.resume()
    return task
}
sonsongithub commented 8 years ago

I got a better code?

func handleTask<T>(request: NSMutableURLRequest, closure: (data: NSData?, response: NSURLResponse?, error: NSError?) -> Result<T>, completion: (Result<T>) -> Void) throws -> NSURLSessionDataTask? {

    let task = URLSession.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
        let result = closure(data:data, response: response, error: error)
        switch result {
        case .Failure(let error):
            if error.code == 401 {
                do {
                    try self.refreshToken({ (result) -> Void in
                        switch result {
                        case .Failure:
                            completion(result)
                        case .Success:
                            let task = self.URLSession.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
                                let result = closure(data:data, response: response, error: error)
                                completion(result)
                            })
                            task.resume()
                        }
                    })
                } catch { print(error) }
            } else {
                completion(result)
            }
        case .Success:
            completion(result)
        }
    })
    task.resume()
    return task
}

/**
 Search subreddits by title and description.

 - parameter query: The search keywords, must be less than 512 characters.
 - parameter paginator: Paginator object for paging.
 - parameter completion: The completion handler to call when the load request is complete.
 - returns: Data task which requests search to reddit.com.
 */
public func getSubredditSearchWithErrorHandling(query: String, paginator: Paginator, completion: (Result<Listing>) -> Void) throws -> NSURLSessionDataTask? {
    let parameter = paginator.addParametersToDictionary(["q":query])
    guard let request = NSMutableURLRequest.mutableOAuthRequestWithBaseURL(baseURL, path:"/subreddits/search", parameter:parameter, method:"GET", token:token)
        else { throw ReddiftError.URLError.error }

    let closure = {(data: NSData?, response: NSURLResponse?, error: NSError?) -> Result<Listing> in
        self.updateRateLimitWithURLResponse(response)
        let result: Result<Listing> = resultFromOptionalError(Response(data: data, urlResponse: response), optionalError:error)
            .flatMap(response2Data)
            .flatMap(data2Json)
            .flatMap(json2RedditAny)
            .flatMap(redditAny2Object)
        return result
    }

    return try! handleTask(request, closure: closure, completion: completion)
}
jakunico commented 8 years ago

Looking good to me!

sonsongithub commented 8 years ago

Now coding on auto-refresh branch.

sonsongithub commented 8 years ago

b7d2a8ba75ba1b50035e3d9e4946d4b6688d2b02