diff --git a/Classes/Networking/RKClient+Captcha.m b/Classes/Networking/RKClient+Captcha.m index df098bf..b4999bb 100644 --- a/Classes/Networking/RKClient+Captcha.m +++ b/Classes/Networking/RKClient+Captcha.m @@ -74,7 +74,7 @@ - (NSURLSessionDataTask *)imageForCaptchaIdentifier:(NSString *)identifier compl NSParameterAssert(identifier); NSURL *imageURL = [self URLForCaptchaWithIdentifier:identifier]; - NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:imageURL.absoluteString parameters:nil]; + NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:imageURL.absoluteString parameters:nil error:nil]; NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!completion) diff --git a/Classes/Networking/RKClient+Links.h b/Classes/Networking/RKClient+Links.h index 488700c..3af1ae4 100644 --- a/Classes/Networking/RKClient+Links.h +++ b/Classes/Networking/RKClient+Links.h @@ -148,7 +148,7 @@ typedef NS_ENUM(NSUInteger, RKSubredditCategory) { @param captchaValue The optional value of the CAPTCHA you are submitting with this post. @param completion An optional block to be executed upon request completion. Its only argument is any error that occurred. */ -- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion; +- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion; /** Submits a link post. @@ -160,7 +160,7 @@ typedef NS_ENUM(NSUInteger, RKSubredditCategory) { @param captchaValue The optional value of the CAPTCHA you are submitting with this post. @param completion An optional block to be executed upon request completion. Its only argument is any error that occurred. */ -- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion; +- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion; /** Submits a self post. @@ -172,7 +172,7 @@ typedef NS_ENUM(NSUInteger, RKSubredditCategory) { @param captchaValue The optional value of the CAPTCHA you are submitting with this post. @param completion An optional block to be executed upon request completion. Its only argument is any error that occurred. */ -- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion; +- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion; /** Submits a self post. @@ -184,7 +184,7 @@ typedef NS_ENUM(NSUInteger, RKSubredditCategory) { @param captchaValue The optional value of the CAPTCHA you are submitting with this post. @param completion An optional block to be executed upon request completion. Its only argument is any error that occurred. */ -- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion; +- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion; #pragma mark - Marking NSFW diff --git a/Classes/Networking/RKClient+Links.m b/Classes/Networking/RKClient+Links.m index f7eeb7f..4a7926b 100644 --- a/Classes/Networking/RKClient+Links.m +++ b/Classes/Networking/RKClient+Links.m @@ -132,12 +132,12 @@ - (NSURLSessionDataTask *)linkWithFullName:(NSString *)fullName completion:(RKOb #pragma mark - Submitting -- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion +- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion { return [self submitLinkPostWithTitle:title subredditName:subreddit.title URL:URL captchaIdentifier:captchaIdentifier captchaValue:captchaValue completion:completion]; } -- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion +- (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName URL:(NSURL *)URL captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion { NSParameterAssert(title); NSParameterAssert(subredditName); @@ -154,15 +154,15 @@ - (NSURLSessionDataTask *)submitLinkPostWithTitle:(NSString *)title subredditNam [parameters setObject:@"link" forKey:@"kind"]; - return [self basicPostTaskWithPath:@"api/submit" parameters:parameters completion:completion]; + return [self basicPostAndResponseTaskWithPath:@"api/submit" parameters:parameters completion:completion]; } -- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion +- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subreddit:(RKSubreddit *)subreddit text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion { return [self submitSelfPostWithTitle:title subredditName:subreddit.title text:text captchaIdentifier:captchaIdentifier captchaValue:captchaValue completion:completion]; } -- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKCompletionBlock)completion +- (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subredditName:(NSString *)subredditName text:(NSString *)text captchaIdentifier:(NSString *)captchaIdentifier captchaValue:(NSString *)captchaValue completion:(RKObjectCompletionBlock)completion { NSParameterAssert(title); NSParameterAssert(subredditName); @@ -178,7 +178,7 @@ - (NSURLSessionDataTask *)submitSelfPostWithTitle:(NSString *)title subredditNam [parameters setObject:@"self" forKey:@"kind"]; - return [self basicPostTaskWithPath:@"api/submit" parameters:parameters completion:completion]; + return [self basicPostAndResponseTaskWithPath:@"api/submit" parameters:parameters completion:completion]; } #pragma mark - Marking NSFW diff --git a/Classes/Networking/RKClient+Requests.h b/Classes/Networking/RKClient+Requests.h index 7c57211..bebca75 100644 --- a/Classes/Networking/RKClient+Requests.h +++ b/Classes/Networking/RKClient+Requests.h @@ -27,6 +27,16 @@ typedef void(^RKRequestCompletionBlock)(NSHTTPURLResponse *response, id response @interface RKClient (Requests) +/** + Many of reddit's API methods require a set of parameters, return an error if they fail, and something when they succeed. + This method eliminates much of the repetition when writing methods around these methods. + + @param path The path to request. + @param parameters The parameters to pass with the request. + @param completion A block to execute at the end of the request. + */ +- (NSURLSessionDataTask *)basicPostAndResponseTaskWithPath:(NSString *)path parameters:(NSDictionary *)parameters completion:(RKObjectCompletionBlock)completion; + /** Many of reddit's API methods require a set of parameters and simply return an error if they fail, and nothing (of value, at least) when they succeed. This method eliminates much of the repetition when writing methods around these methods. diff --git a/Classes/Networking/RKClient+Requests.m b/Classes/Networking/RKClient+Requests.m index 91b62b7..e32cc0f 100644 --- a/Classes/Networking/RKClient+Requests.m +++ b/Classes/Networking/RKClient+Requests.m @@ -27,7 +27,7 @@ @implementation RKClient (Requests) -- (NSURLSessionDataTask *)basicPostTaskWithPath:(NSString *)path parameters:(NSDictionary *)parameters completion:(RKCompletionBlock)completion +- (NSURLSessionDataTask *)basicPostAndResponseTaskWithPath:(NSString *)path parameters:(NSDictionary *)parameters completion:(RKObjectCompletionBlock)completion { NSParameterAssert(path); @@ -36,7 +36,7 @@ - (NSURLSessionDataTask *)basicPostTaskWithPath:(NSString *)path parameters:(NSD dispatch_async(dispatch_get_main_queue(), ^{ if (completion) { - completion([RKClient authenticationRequiredError]); + completion(nil, [RKClient authenticationRequiredError]); } }); @@ -44,6 +44,18 @@ - (NSURLSessionDataTask *)basicPostTaskWithPath:(NSString *)path parameters:(NSD } return [self postPath:path parameters:parameters completion:^(NSHTTPURLResponse *response, id responseObject, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) + { + completion(responseObject, error); + } + }); + }]; +} + +- (NSURLSessionDataTask *)basicPostTaskWithPath:(NSString *)path parameters:(NSDictionary *)parameters completion:(RKCompletionBlock)completion +{ + return [self basicPostAndResponseTaskWithPath:path parameters:parameters completion:^(id object, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (completion) { @@ -186,7 +198,7 @@ - (NSURLSessionDataTask *)taskWithMethod:(NSString *)method path:(NSString *)pat [alteredParameters setObject:@"json" forKey:@"api_type"]; NSString *URLString = [[NSURL URLWithString:path relativeToURL:self.baseURL] absoluteString]; - NSURLRequest *request = [[self requestSerializer] requestWithMethod:method URLString:URLString parameters:[alteredParameters copy]]; + NSURLRequest *request = [[self requestSerializer] requestWithMethod:method URLString:URLString parameters:[alteredParameters copy] error:nil]; NSURLSessionDataTask *task = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Classes/Networking/RKClient+Users.h b/Classes/Networking/RKClient+Users.h index 9f880ea..c17d7f3 100644 --- a/Classes/Networking/RKClient+Users.h +++ b/Classes/Networking/RKClient+Users.h @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "RKClient.h" +#import "RKOAuthClient.h" #import "RKCompletionBlocks.h" typedef NS_ENUM(NSUInteger, RKUserContentCategory) diff --git a/Classes/Networking/RKClient+Users.m b/Classes/Networking/RKClient+Users.m index 8ed62dc..50a170d 100644 --- a/Classes/Networking/RKClient+Users.m +++ b/Classes/Networking/RKClient+Users.m @@ -79,7 +79,7 @@ @implementation RKClient (Users) - (NSURLSessionDataTask *)currentUserWithCompletion:(RKObjectCompletionBlock)completion { - return [self getPath:@"api/me.json" parameters:nil completion:^(NSHTTPURLResponse *response, id responseObject, NSError *error) { + return [self getPath:[[self class] meURLPath] parameters:nil completion:^(NSHTTPURLResponse *response, id responseObject, NSError *error) { if (responseObject) { RKUser *account = [RKObjectBuilder objectFromJSON:responseObject]; diff --git a/Classes/Networking/RKClient.h b/Classes/Networking/RKClient.h index 9142723..cd4c54e 100644 --- a/Classes/Networking/RKClient.h +++ b/Classes/Networking/RKClient.h @@ -61,6 +61,11 @@ extern NSString * const RKClientErrorDomain; */ + (NSURL *)APIBaseHTTPSURL; +/** + The API endpoint the client connects to to get your profile information. + */ ++ (NSString *)meURLPath; + /** Signs into reddit. diff --git a/Classes/Networking/RKClient.m b/Classes/Networking/RKClient.m index f4dd9b0..7ca6792 100644 --- a/Classes/Networking/RKClient.m +++ b/Classes/Networking/RKClient.m @@ -41,7 +41,7 @@ + (instancetype)sharedClient static RKClient *sharedRKClient = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ - sharedRKClient = [[RKClient alloc] init]; + sharedRKClient = [[[self class] alloc] init]; }); return sharedRKClient; @@ -82,9 +82,14 @@ + (NSURL *)APIBaseHTTPSURL return [NSURL URLWithString:@"https://ssl.reddit.com/"]; } ++ (NSString *)meURLPath +{ + return @"api/me.json"; +} + #pragma mark - Authentication -- (NSURLSessionDataTask *)signInWithUsername:(NSString *)username password:(NSString *)password completion:(RKCompletionBlock)completion; +- (NSURLSessionDataTask *)signInWithUsername:(NSString *)username password:(NSString *)password completion:(RKCompletionBlock)completion { NSParameterAssert(username); NSParameterAssert(password); @@ -94,7 +99,7 @@ - (NSURLSessionDataTask *)signInWithUsername:(NSString *)username password:(NSSt NSURL *baseURL = [[self class] APIBaseHTTPSURL]; NSString *URLString = [[NSURL URLWithString:@"api/login" relativeToURL:baseURL] absoluteString]; - NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters]; + NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters error:nil]; __weak __typeof(self)weakSelf = self; NSURLSessionDataTask *authenticationTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { diff --git a/Classes/Networking/RKOAuthClient.h b/Classes/Networking/RKOAuthClient.h new file mode 100644 index 0000000..f9dfc85 --- /dev/null +++ b/Classes/Networking/RKOAuthClient.h @@ -0,0 +1,55 @@ +// +// RKOAuthClient.h +// Pods +// +// Created by Joseph Pintozzi on 11/14/13. +// +// + +#import "RKClient.h" + +/** + The different kinds of scope the OAuth client can request + Explainations found here: http://www.reddit.com/dev/api + */ + +static NSString * const kOAuthScopeEdit = @"edit"; +static NSString * const kOAuthScopeHistory = @"history"; +static NSString * const kOAuthScopeIdentity = @"identity"; +static NSString * const kOAuthScopeModConfig = @"modconfig"; +static NSString * const kOAuthScopeModFlair = @"modflair"; +static NSString * const kOAuthScopeModLog = @"modlog"; +static NSString * const kOAuthScopeModPosts = @"modposts"; +static NSString * const kOAuthScopeMySubreddits = @"mysubreddits"; +static NSString * const kOAuthScopePrivateMessages = @"privatemessages"; +static NSString * const kOAuthScopeRead = @"read"; +static NSString * const kOAuthScopeSave = @"save"; +static NSString * const kOAuthScopeSubmit = @"submit"; +static NSString * const kOAuthScopeSubscribe = @"subscribe"; +static NSString * const kOAuthScopeVote = @"vote"; + +@interface RKOAuthClient : RKClient + +/** + The current clientId and clientSecret for this app. + Only required if authenticating via OAuth + */ +@property (nonatomic, strong) NSString *clientId; +@property (nonatomic, strong) NSString *clientSecret; +@property (nonatomic, strong) NSString *accessToken; +@property (nonatomic, strong) NSString *refreshToken; + +/** + Returns a RKClient ready for OAuth + Get a client ID and secret here: https://ssl.reddit.com/prefs/apps + */ +- (id)initWithClientId:(NSString *)clientId clientSecret:(NSString *)clientSecret; + +/** + Signs into reddit via OAuth + */ +- (NSURL *)oauthURLWithRedirectURI:(NSString *)redirectURI state:(NSString *)state scope:(NSArray*)scope; +- (NSURLSessionDataTask *)signInWithAccessCode:(NSString *)accessCode redirectURI:(NSString *)redirectURI state:(NSString *)state completion:(RKCompletionBlock)completion; +- (NSURLSessionDataTask *)refreshAccessToken:(NSString*)refreshToken redirectURI:(NSString *)redirectURI state:(NSString *)state completion:(RKCompletionBlock)completion; + +@end diff --git a/Classes/Networking/RKOAuthClient.m b/Classes/Networking/RKOAuthClient.m new file mode 100644 index 0000000..f85f790 --- /dev/null +++ b/Classes/Networking/RKOAuthClient.m @@ -0,0 +1,207 @@ +// +// RKOAuthClient.m +// Pods +// +// Created by Joseph Pintozzi on 11/14/13. +// +// + +#import "RKOAuthClient.h" +#import "RKUser.h" +#import "RKResponseSerializer.h" +#import "RKObjectBuilder.h" + +#import "RKClient+Users.h" + +@interface RKOAuthClient () + +@property (nonatomic, strong) RKUser *currentUser; +@property (nonatomic, strong) NSTimer *tokenRefreshTimer; + +@end +@implementation RKOAuthClient + +- (id)initWithClientId:(NSString *)clientId clientSecret:(NSString *)clientSecret +{ + if (self = [super initWithBaseURL:[[self class] APIBaseURL]]) + { + self.requestSerializer = [AFHTTPRequestSerializer serializer]; + self.responseSerializer = [RKResponseSerializer serializer]; + _clientId = clientId; + _clientSecret = clientSecret; + } + + return self; +} + +// Overriding API urls + ++ (NSURL *)APIBaseURL +{ + //OAuth requires HTTPS + return [[self class] APIBaseHTTPSURL]; +} + ++ (NSURL *)APIBaseHTTPSURL +{ + return [NSURL URLWithString:@"https://oauth.reddit.com/"]; +} + ++ (NSURL *)APIBaseLoginURL +{ + return [NSURL URLWithString:@"https://ssl.reddit.com/"]; +} + ++ (NSString *)meURLPath +{ + return @"api/v1/me"; +} + +- (void)setClientId:(NSString *)clientId clientSecret:(NSString*)clientSecret{ + _clientId = [clientId copy]; + _clientSecret = [clientSecret copy]; +} + +- (void)setOAuthorizationHeader{ + NSAssert(_clientId != nil, @"You must first set a clientId"); + NSAssert(_clientSecret != nil, @"You must first set a clientSecret"); + [[self requestSerializer] setAuthorizationHeaderFieldWithUsername:_clientId password:_clientSecret]; +} + +- (NSURL *)oauthURLWithRedirectURI:(NSString *)redirectURI state:(NSString *)state scope:(NSArray*)scope { + NSParameterAssert(redirectURI); + NSParameterAssert(state); + NSParameterAssert(scope); + NSAssert(_clientId != nil, @"You must first set a clientId"); + + return [NSURL URLWithString:[NSString stringWithFormat:@"%@api/v1/authorize?response_type=code&redirect_uri=%@&client_id=%@&duration=permanent&scope=%@&state=%@", [[self class] APIBaseLoginURL], [redirectURI stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding], _clientId, [scope componentsJoinedByString:@","], state]]; +} + +- (NSURLSessionDataTask *)signInWithAccessCode:(NSString *)accessCode redirectURI:(NSString *)redirectURI state:(NSString *)state completion:(RKCompletionBlock)completion +{ + NSParameterAssert(accessCode); + NSParameterAssert(redirectURI); + NSParameterAssert(state); + + NSDictionary *parameters = @{@"code": accessCode, @"state": state, @"redirect_uri": redirectURI, @"grant_type": @"authorization_code"}; + return [self accessTokensWithParams:parameters completion:completion]; +} + +- (NSURLSessionDataTask *)refreshAccessTokenWithTimer:(NSTimer *)timer +{ + NSDictionary *parameters = timer.userInfo; + return [self refreshAccessToken:_refreshToken redirectURI:parameters[@"redirect_uri"] state:parameters[@"state"] completion:nil]; +} + +- (NSURLSessionDataTask *)refreshAccessToken:(NSString *)refreshToken redirectURI:(NSString *)redirectURI state:(NSString *)state completion:(RKCompletionBlock)completion +{ + NSParameterAssert(refreshToken); + NSParameterAssert(redirectURI); + NSParameterAssert(state); + + NSDictionary *parameters = @{@"refresh_token": refreshToken, @"state": state, @"redirect_uri": redirectURI, @"grant_type": @"refresh_token"}; + return [self accessTokensWithParams:parameters completion:completion]; +} + +- (NSURLSessionDataTask *)userInfoWithCompletion:(RKObjectCompletionBlock)completion +{ + + NSURL *baseURL = [[self class] APIBaseHTTPSURL]; + NSString *URLString = [[NSURL URLWithString:[[self class] meURLPath] relativeToURL:baseURL] absoluteString]; + + NSMutableURLRequest *request = [[self requestSerializer] requestWithMethod:@"GET" URLString:URLString parameters:@{} error:nil]; + + NSURLSessionDataTask *authenticationTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { + if (completion) { + completion(responseObject, error); + } + }]; + + [authenticationTask resume]; + + return authenticationTask; +} + +- (NSURLSessionDataTask *)accessTokensWithParams:(NSDictionary*)parameters completion:(RKCompletionBlock)completion +{ + [self setOAuthorizationHeader]; + NSURL *baseURL = [[self class] APIBaseLoginURL]; + NSString *URLString = [[NSURL URLWithString:@"api/v1/access_token" relativeToURL:baseURL] absoluteString]; + + NSMutableURLRequest *request = [[self requestSerializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters error:nil]; + + __weak __typeof(self)weakSelf = self; + NSURLSessionDataTask *authenticationTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { + if (!error) + { + _accessToken = responseObject[@"access_token"]; + if (responseObject[@"refresh_token"] && responseObject[@"refresh_token"] != [NSNull null]) { + _refreshToken = responseObject[@"refresh_token"]; + } else { + _refreshToken = parameters[@"refresh_token"]; + } + //if our token expires, we should refresh it + if (responseObject[@"expires_in"]) { + //if we have an existing timer, invalidate it so it doesn't fire twice + if (_tokenRefreshTimer) { + [_tokenRefreshTimer invalidate]; + } + int seconds = [responseObject[@"expires_in"] intValue] - 10; //be a little aggressive and refresh 10 seconds before our token expires + _tokenRefreshTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(refreshAccessTokenWithTimer:) userInfo:parameters repeats:NO]; + } + [self setBearerAccessToken:_accessToken]; + if (!self.currentUser) { + [weakSelf loadUserAccountWithCallback:^(NSError *error) { + if (completion) { + completion(error); + } + }]; + } + else if (completion) + { + completion(nil); + } + } + else if (completion) { + completion(error); + } + }]; + + [authenticationTask resume]; + + return authenticationTask; +} + +- (void)loadUserAccountWithCallback:(RKCompletionBlock)completion +{ + __weak __typeof(self)weakSelf = self; + [self userInfoWithCompletion:^(id object, NSError *error) { + if (error) { + completion(error); + } else { + RKUser *account = [RKObjectBuilder objectFromJSON:@{@"kind": kRKObjectTypeAccount, @"data":object}]; + if (account && !error) { + weakSelf.currentUser = account; + if (completion) + { + completion(nil); + } + } else if (completion) { + completion(error); + } + } + }]; +} + +- (BOOL)isSignedIn +{ + return self.modhash != nil || _accessToken != nil; +} + + +- (void)setBearerAccessToken:(NSString*)accessToken +{ + [[self requestSerializer] setValue:[@"bearer " stringByAppendingString: accessToken] forHTTPHeaderField:@"Authorization"]; +} + +@end diff --git a/Classes/RedditKit.h b/Classes/RedditKit.h index cc10a7c..07e7009 100644 --- a/Classes/RedditKit.h +++ b/Classes/RedditKit.h @@ -21,6 +21,7 @@ // THE SOFTWARE. #import "RKClient.h" +#import "RKOAuthClient.h" #import "RKClient+Apps.h" #import "RKClient+Captcha.h" diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index b3274d2..e36b205 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 4BB0B6DD180652D20023BB24 /* BrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0B6DC180652D20023BB24 /* BrowserViewController.m */; }; 4BC808121825AFEE0070B628 /* AuthenticationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BC808111825AFEE0070B628 /* AuthenticationManager.m */; }; 4BC8081518270FF80070B628 /* LinkTitleLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BC8081418270FF80070B628 /* LinkTitleLabel.m */; }; + D8976D6518399D5300BF36C8 /* OAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D8976D6418399D5300BF36C8 /* OAuthViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -47,6 +48,8 @@ 4BC8081418270FF80070B628 /* LinkTitleLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LinkTitleLabel.m; sourceTree = ""; }; 6AD7909B16B44DE7A9159C04 /* libPods-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 94D0CE0DEABE4AFA951E5C4A /* Pods-ios.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios.xcconfig"; path = "Pods/Pods-ios.xcconfig"; sourceTree = ""; }; + D8976D6318399D5300BF36C8 /* OAuthViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OAuthViewController.h; sourceTree = ""; }; + D8976D6418399D5300BF36C8 /* OAuthViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OAuthViewController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -103,6 +106,8 @@ 4B20490717F4C6350009BFF6 /* FrontPageViewController.m */, 4BB0B6DB180652D20023BB24 /* BrowserViewController.h */, 4BB0B6DC180652D20023BB24 /* BrowserViewController.m */, + D8976D6318399D5300BF36C8 /* OAuthViewController.h */, + D8976D6418399D5300BF36C8 /* OAuthViewController.m */, 4B723EDB1809A9C500E44AC1 /* LinkTableViewCell.h */, 4B723EDC1809A9C500E44AC1 /* LinkTableViewCell.m */, 4BC8081318270FF80070B628 /* LinkTitleLabel.h */, @@ -225,6 +230,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8976D6518399D5300BF36C8 /* OAuthViewController.m in Sources */, 4BC8081518270FF80070B628 /* LinkTitleLabel.m in Sources */, 4B20490817F4C6350009BFF6 /* FrontPageViewController.m in Sources */, 4BB0B6DD180652D20023BB24 /* BrowserViewController.m in Sources */, diff --git a/Example/Example/AppDelegate.m b/Example/Example/AppDelegate.m index 951c4e1..ecd0124 100644 --- a/Example/Example/AppDelegate.m +++ b/Example/Example/AppDelegate.m @@ -41,7 +41,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( dispatch_async(dispatch_get_main_queue(), ^{ [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; - [[RKClient sharedClient] setUserAgent:@"RedditKit 1.0.0 Example Project"]; + [[RKOAuthClient sharedClient] setUserAgent:@"RedditKit 1.0.0 Example Project"]; }); return YES; diff --git a/Example/Example/AuthenticationManager.h b/Example/Example/AuthenticationManager.h index 5b2337c..4277327 100644 --- a/Example/Example/AuthenticationManager.h +++ b/Example/Example/AuthenticationManager.h @@ -22,7 +22,7 @@ typedef void(^AuthenticationSuccessBlock)(); -@interface AuthenticationManager : NSObject +@interface AuthenticationManager : NSObject - (void)showSignInAlertViewWithCompletion:(AuthenticationSuccessBlock)completion; diff --git a/Example/Example/AuthenticationManager.m b/Example/Example/AuthenticationManager.m index 1ed82bc..d8d9cc1 100644 --- a/Example/Example/AuthenticationManager.m +++ b/Example/Example/AuthenticationManager.m @@ -21,6 +21,8 @@ // THE SOFTWARE. #import "AuthenticationManager.h" +#import "OAuthViewController.h" +#import "RKOAuthClient.h" @interface AuthenticationManager () @@ -36,11 +38,21 @@ - (void)showSignInAlertViewWithCompletion:(AuthenticationSuccessBlock)completion { self.authenticationSuccessBlock = completion; - [[self signInAlertView] show]; + [[self authenticationMethodActionSheet] showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]]; } #pragma mark - Private +- (UIActionSheet *)authenticationMethodActionSheet +{ + UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"How would you like to authenticate?" + delegate:self + cancelButtonTitle:@"Cancel" + destructiveButtonTitle:nil + otherButtonTitles:@"Username/Password", @"OAuth", nil]; + return actionSheet; +} + - (UIAlertView *)signInAlertView { UIAlertView *signInAlert = [[UIAlertView alloc] initWithTitle:@"Reddit Account" @@ -87,4 +99,27 @@ - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)butto } } +#pragma mark - UIActionSheetDelegate + +- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex +{ + if (buttonIndex == 0) + { + [[self signInAlertView] show]; + } + else if (buttonIndex == 1) + { + NSAssert([kOAuthClientID length], @"Make sure you've entered a value for kOAuthClientID in Example-Prefix.pch"); + NSAssert([kOAuthClientSecret length], @"Make sure you've entered a value for kOAuthClientSecret in Example-Prefix.pch"); + OAuthViewController *oauthViewController = [[OAuthViewController alloc] init]; + UINavigationController *navigationController = (UINavigationController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController]; + [navigationController presentViewController:[[UINavigationController alloc] initWithRootViewController:oauthViewController] animated:YES completion:^{ + [[RKOAuthClient sharedClient] setClientId:kOAuthClientID]; + [[RKOAuthClient sharedClient] setClientSecret:kOAuthClientSecret]; + NSURL *authUrl = [[RKOAuthClient sharedClient] oauthURLWithRedirectURI:kOAuthRedirectURI state:kOAuthState scope:@[kOAuthScopeIdentity, kOAuthScopeVote]]; + [oauthViewController.webView loadRequest:[NSURLRequest requestWithURL:authUrl]]; + }]; + } +} + @end diff --git a/Example/Example/Example-Prefix.pch b/Example/Example/Example-Prefix.pch index bc7c19d..cfa3172 100644 --- a/Example/Example/Example-Prefix.pch +++ b/Example/Example/Example-Prefix.pch @@ -5,3 +5,10 @@ #import #import #endif + + +#define kOAuthRedirectURI @"http://example.com/redditauth" +#define kOAuthState @"1234567890" +//ClientID and ClientSecret can be obtained on https://ssl.reddit.com/prefs/apps/ +#define kOAuthClientID @"" +#define kOAuthClientSecret @"" diff --git a/Example/Example/FrontPageViewController.m b/Example/Example/FrontPageViewController.m index 18a72c4..c8fc266 100644 --- a/Example/Example/FrontPageViewController.m +++ b/Example/Example/FrontPageViewController.m @@ -191,7 +191,7 @@ - (void)loadNewLinks self.loadingNewLinks = YES; __weak __typeof(self)weakSelf = self; - [[RKClient sharedClient] frontPageLinksWithPagination:self.currentPagination completion:^(NSArray *collection, RKPagination *pagination, NSError *error) { + [[RKOAuthClient sharedClient] frontPageLinksWithPagination:self.currentPagination completion:^(NSArray *collection, RKPagination *pagination, NSError *error) { if (!error) { [[weakSelf tableView] beginUpdates]; diff --git a/Example/Example/OAuthViewController.h b/Example/Example/OAuthViewController.h new file mode 100644 index 0000000..b820809 --- /dev/null +++ b/Example/Example/OAuthViewController.h @@ -0,0 +1,15 @@ +// +// OAuthViewController.h +// Example +// +// Created by Joseph Pintozzi on 11/17/13. +// Copyright (c) 2013 Sam Symons. All rights reserved. +// + +#import "BrowserViewController.h" + +@interface OAuthViewController : BrowserViewController + +@property (nonatomic, strong) UIWebView *webView; + +@end diff --git a/Example/Example/OAuthViewController.m b/Example/Example/OAuthViewController.m new file mode 100644 index 0000000..7e1d452 --- /dev/null +++ b/Example/Example/OAuthViewController.m @@ -0,0 +1,42 @@ +// +// OAuthViewController.m +// Example +// +// Created by Joseph Pintozzi on 11/17/13. +// Copyright (c) 2013 Sam Symons. All rights reserved. +// + +#import "OAuthViewController.h" +#import "RKOAuthClient.h" + +@implementation OAuthViewController + +- (NSArray *)toolbarItems +{ + return nil; +} + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +{ + if ([request.URL.absoluteString hasPrefix:kOAuthRedirectURI]) { + NSString *paramString = [request.URL.absoluteString componentsSeparatedByString:@"?"][1]; + NSArray *params = [paramString componentsSeparatedByString:@"&"]; + NSMutableDictionary *paramDict = [NSMutableDictionary dictionary]; + for (NSString *string in params) { + NSArray *components = [string componentsSeparatedByString:@"="]; + [paramDict setValue:components[1] forKey:components[0]]; + } + if (paramDict[@"code"]) { + [[RKOAuthClient sharedClient] signInWithAccessCode:paramDict[@"code"] redirectURI:kOAuthRedirectURI state:kOAuthState completion:^(NSError *error) { + UINavigationController *navigationController = (UINavigationController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController]; + [navigationController dismissViewControllerAnimated:YES completion:^{ + + }]; + }]; + return NO; + } + } + return YES; +} + +@end diff --git a/README.md b/README.md index 79d40c3..da7229a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RedditKit -RedditKit is a [reddit API](http://www.reddit.com/dev/api) wrapper, written in Objective-C. +RedditKit is a [reddit API](http://www.reddit.com/dev/api) wrapper, written in Objective-C. This fork has more of a focus on OAuth support than the master repo. ## Installation @@ -8,11 +8,11 @@ RedditKit is a [reddit API](http://www.reddit.com/dev/api) wrapper, written in O Add this to your Podfile: - pod 'RedditKit', '~> 1.0' + pod 'RedditKit', :git => "git@github.com:pyro2927/RedditKit.git", :branch => "master" Then run: - pod install + pod install ### Manually diff --git a/RedditKit.podspec b/RedditKit.podspec index d1b3681..a268f06 100644 --- a/RedditKit.podspec +++ b/RedditKit.podspec @@ -5,8 +5,8 @@ Pod::Spec.new do |s| s.homepage = "https://github.com/samsymons/RedditKit" s.social_media_url = "https://twitter.com/sam_symons" s.license = 'MIT' - s.author = { "Sam Symons" => "sam@samsymons.com" } - s.source = { :git => "https://github.com/samsymons/RedditKit.git", :tag => s.version.to_s } + s.authors = { "Sam Symons" => "sam@samsymons.com", "Joe Pintozzi" => "joseph@pintozzi.com" } + s.source = { :git => "https://github.com/pyro2927/RedditKit.git", :branch => "master" } s.requires_arc = true s.ios.deployment_target = '7.0'