iOS - Re-Attempt Failed NSURLRequests In NSURLSession - ios

In my app, 2-4 API calls to my server can be happening at the same time (asynchronously) within my API class's NSURLSession. In order to make API requests to my server, I must supply the authentication token in the HTTPHeaderField of each NSURLRequest. The token is valid for one day, and if it becomes invalid after one day, I need to refresh the token.
I do this in the following code in my API class:
/*!
* #brief sends a request as an NSHTTPURLResponse. This method is private.
* #param request The request to send.
* #param success A block to be called if the request is successful.
* #param error A block to be called if the request fails.
*/
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
//if auth token expired and getting "not authenticated" error (status 401)
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
[self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
self.authToken = response[#"token"];
//attempt to re-try the request that failed due to token expiration
[self sendTask:request successCallback:success errorCallback:errorCallback];
} errorCallback:^(NSString *error) {
//two weeks have passed and the token is no longer refreshable
NSLog(#"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
}];
}
}];
}];
[task resume];
}
This sendTask method gets executed with every API request I make in the app, so I just realized this is a bad way of doing it. If 3 API requests fail due to the token being invalid (one day passed), then all 3 of these API requests are going to attempt to make the API call to refresh the authentication token.
Is there a way for me to, in case ONE of the API requests fail, refresh the authentication token only ONCE and then re-attempt the failed API calls?
EDIT
I edited the title of the question to indicate that I'm working with NSURLSession
PROGRESS
So far, to prevent several failed API requests from trying to refresh the authentication token at the same time, I have an NSArray for all the failed requests and an NSNumber that serves as a lock to make sure that the authentication token is only trying to be refreshed once. I do this in the following code:
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
MyAPIInterface *__weak weakSelf = self;
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
if ([error isEqualToString:#"invalid_credentials"]) {
errorCallback(#"Invalid username and/or password");
}
else if ([error isEqualToString:#"Unknown error"]) {
errorCallback(error);
}
else {
if (!weakSelf.alreadyRefreshingToken.boolValue) {
//lock alreadyRefreshingToken boolean
weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:YES];
NSLog(#"NOT REFRESHING TOKEN");
// add failed request to failedRequests array
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
[mutableFailedRequests addObject:request];
weakSelf.failedRequests = [mutableFailedRequests copy];
// refresh auth token
[weakSelf refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
//store authToken
weakSelf.authToken = response[#"token"];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:weakSelf.authToken forKey:#"authToken"];
[defaults synchronize];
//attempt to re-try all requests that failed due to token expiration
for (NSURLRequest *failedRequest in weakSelf.failedRequests) {
[weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
}
//clear failedRequests array and unlock alreadyRefreshingToken boolean
[weakSelf clearFailedRequests];
weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:NO];
NSLog(#"TOKEN REFRESHING SUCCESSFUL THO");
} errorCallback:^(NSString *error) {
NSLog(#"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
//clear failedRequests array
[weakSelf clearFailedRequests];
errorCallback(#"Your login session has expired");
}];
}
else {
NSLog(#"ALREADY REFRESHING TOKEN. JUST ADD TO FAILED LIST");
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
[mutableFailedRequests addObject:request];
weakSelf.failedRequests = [mutableFailedRequests copy];
}
}
}
else {
NSLog(#"ERROR STRING THO: %#", error);
errorCallback(error);
}
}];
}];
[task resume];
}
#pragma mark Custom Methods
-(void)clearFailedRequests {
NSMutableArray *mutableFailedRequests = [self.failedRequests mutableCopy];
[mutableFailedRequests removeAllObjects];
self.failedRequests = [mutableFailedRequests copy];
}
Am I going about this correctly? One part that I'm paranoid about is that I'm not really calling the success or error callback at certain points. Can this lead to problems?

Instead of using [self sendTask:], try with [weakSelf sendTask]. Check below code:
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
__weak __typeof(self)weakSelf = self;
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
//if auth token expired and getting "not authenticated" error (status 401)
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
[self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
self.authToken = response[#"token"];
//attempt to re-try the request that failed due to token expiration
[weakSelf sendTask:request successCallback:success errorCallback:errorCallback];
} errorCallback:^(NSString *error) {
//two weeks have passed and the token is no longer refreshable
NSLog(#"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
}];
}
}];
}];
[task resume];
}

Related

Attach parameters to NSURLSession Delegate Method

I would like to pass a parameter into my delegate to determine to continue or check for cert.
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
NSLog(#"Parameter 1 %#", parameter);
}
NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if ([data length]>0 && error == nil) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", json);
resolve(json);
} else if ([data length]==0 && error ==nil) {
NSError *error = [NSError errorWithDomain:#"xxxx" code:400 userInfo:#{#"Error reason": #"No data returned."}];
reject(#"error", #"error description", error);
} else if( error!=nil) {
NSError *error = [NSError errorWithDomain:#"xxxx" code:400 userInfo:#{#"Error reason": #"Invalid request."}];
reject(#"error", #"error description", error);
}
}];
// Start The Task
[dataTask resume];
How can I pass a parameter from my URLSession into this delegate. I looked for a few hours and found nothing online about this. No surprise. Most Obj-c things I find no good references or examples or walkthroughs. Everything is extracted.
My solution was to store and retrieve the cert using SecureKey, if does not exist, just continue on, with the datatask, and it handled the message, authorized or not.
Depending on if the target route needed the cert.

Why is background NSURLConnection having error when enter background?

I am having this error when my app enter background.
NSURLConnection finished with error - code -1001 Task
<09B84034-9F73-4DB6-A685-D891B1B1068A>.<2> finished with error - code:
-1001
I am using this code
- (id<XCDYouTubeOperation>) getVideoWithIdentifier:(NSString *)videoIdentifier completionHandler:(void (^)(XCDYouTubeVideo * __nullable video, NSError * __nullable error))completionHandler
{
NSLog(#"Getting Video Identfifier");
if (!completionHandler)
#throw [NSException exceptionWithName:NSInvalidArgumentException reason:#"The `completionHandler` argument must not be nil." userInfo:nil];
XCDYouTubeVideoOperation *operation = [[XCDYouTubeVideoOperation alloc] initWithVideoIdentifier:videoIdentifier languageIdentifier:self.languageIdentifier];
operation.completionBlock = ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
if (operation.video || operation.error)
{
NSAssert(!(operation.video && operation.error), #"One of `video` or `error` must be nil.");
completionHandler(operation.video, operation.error);
}
else
{
NSAssert(operation.isCancelled, #"Both `video` and `error` can not be nil if the operation was not canceled.");
}
operation.completionBlock = nil;
#pragma clang diagnostic pop
}];
};
NSLog(#"Operation - %#", operation ) ;
[self.queue addOperation:operation];
return operation;
}`
- (void) startRequestWithURL:(NSURL *)url type:(XCDYouTubeRequestType)requestType
{
if (self.isCancelled)
return;
// Max (age-restricted VEVO) = 2×GetVideoInfo + 1×WatchPage + 1×EmbedPage + 1×JavaScriptPlayer + 1×GetVideoInfo
if (++self.requestCount > 6)
{
// This condition should never happen but the request flow is quite complex so better abort here than go into an infinite loop of requests
[self finishWithError];
return;
}
XCDYouTubeLogDebug(#"Starting request: %#", url);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[request setValue:self.languageIdentifier forHTTPHeaderField:#"Accept-Language"];
NSLog(#"Request Type - ",requestType);
// NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request];
// [task resume];
self.dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (self.isCancelled)
return;
if (error)
[self handleConnectionError:error];
else
[self handleConnectionSuccessWithData:data response:response requestType:requestType];
}];
[self.dataTask resume];
self.requestType = requestType;
}
#pragma mark - Response Dispatch
- (void) handleConnectionSuccessWithData:(NSData *)data response:(NSURLResponse *)response requestType:(XCDYouTubeRequestType)requestType
{
NSLog(#"XCDDRequestType - ",requestType);
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)response.textEncodingName ?: CFSTR(""));
// Use kCFStringEncodingMacRoman as fallback because it defines characters for every byte value and is ASCII compatible. See https://mikeash.com/pyblog/friday-qa-2010-02-19-character-encodings.html
NSString *responseString = CFBridgingRelease(CFStringCreateWithBytes(kCFAllocatorDefault, data.bytes, (CFIndex)data.length, encoding != kCFStringEncodingInvalidId ? encoding : kCFStringEncodingMacRoman, false)) ?: #"";
NSAssert(responseString.length > 0, #"Failed to decode response from %# (response.textEncodingName = %#, data.length = %#)", response.URL, response.textEncodingName, #(data.length));
XCDYouTubeLogVerbose(#"Response: %#\n%#", response, responseString);
switch (requestType)
{
case XCDYouTubeRequestTypeGetVideoInfo:
[self handleVideoInfoResponseWithInfo:XCDDictionaryWithQueryString(responseString) response:response];
break;
case XCDYouTubeRequestTypeWatchPage:
[self handleWebPageWithHTMLString:responseString];
break;
case XCDYouTubeRequestTypeEmbedPage:
[self handleEmbedWebPageWithHTMLString:responseString];
break;
case XCDYouTubeRequestTypeJavaScriptPlayer:
[self handleJavaScriptPlayerWithScript:responseString];
break;
}
}
This code will automatically run in background but after a few minutes it will stop and gives me the above error. How to fix this ?
EDIT 1 (Vinay Kiran Method) #
i changed the nsurlsessionconfiguration to background.
- (instancetype) initWithVideoIdentifier:(NSString *)videoIdentifier languageIdentifier:(NSString *)languageIdentifier
{
if (!(self = [super init]))
return nil; // LCOV_EXCL_LINE
_videoIdentifier = videoIdentifier ?: #"";
_languageIdentifier = languageIdentifier ?: #"en";
// _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"YouTubeID"];
_session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
_operationStartSemaphore = dispatch_semaphore_create(0);
NSLog(#"Initialize the Video Identifier");
return self;
}
then change the completion handler since background it will give this error if i use handler
Swift: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'
- (void) startRequestWithURL:(NSURL *)url type:(XCDYouTubeRequestType)requestType
{
if (self.isCancelled)
return;
// Max (age-restricted VEVO) = 2×GetVideoInfo + 1×WatchPage + 1×EmbedPage + 1×JavaScriptPlayer + 1×GetVideoInfo
if (++self.requestCount > 6)
{
// This condition should never happen but the request flow is quite complex so better abort here than go into an infinite loop of requests
[self finishWithError];
return;
}
XCDYouTubeLogDebug(#"Starting request: %#", url);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[request setValue:self.languageIdentifier forHTTPHeaderField:#"Accept-Language"];
NSLog(#"Request Type - ",requestType);
// NEWLY ADDED
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request];
[task resume];
// self.dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
// {
// if (self.isCancelled)
// return;
//
// if (error)
// [self handleConnectionError:error];
// else
// [self handleConnectionSuccessWithData:data response:response requestType:requestType];
// }];
// [self.dataTask resume];
self.requestType = requestType;
}
the problem now is that i originally use this
self.dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (self.isCancelled)
return;
if (error)
[self handleConnectionError:error];
else
[self handleConnectionSuccessWithData:data response:response requestType:requestType];
}];
[self.dataTask resume];
which handleConnectionSuccessWithData will take in data, response and request type. Now i don't know where can i get the data, response and request type if i use backgroundSessionConfigurationWithIdentifier.
Use background thread instead of the main queue
backgroundSessionConfigurationWithIdentifier:
For reference
https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1407496-backgroundsessionconfigurationwi?language=objc

How to call a method once the completion block finish?

I want to call the signUp method first, once I got the userID, I need to call the another method normalSignupMethod.
[ConnectionObj signUp:user];
[helper normalSignupMethod:dict];
signUp Method:
[MYRequest signUp:user successBlock:^(QBResponse *response, QBUUser *user) {
// Sign up was successful
// Store user id
[SingletonClass sharedMySingleton].userID = [NSString stringWithFormat:#"%#",response.data[#"id"]];
} errorBlock:^(QBResponse *response) {
// Handle error here
NSLog(#" error in creating session %#", response.error);
[SVProgressHUD showErrorWithStatus:NSLocalizedString(#"SignUp to Chat error!", nil)];
}];
This I how I have called:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
NSLog(#"Block1");
[ConnectionObj signUp:user];
});
dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
NSLog(#"Group notify");
[helper normalSignupMethod:dict];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
});
Block 1 executed first, and then group notify called. But I'm getting the userID after the normalSignupMethod is finished. How to wait for a signUp method to get userID before calling the normalSignupMethod?
You can create a block with your signUp method like this and pass the Bool completion value to check is it called successfully or not. So change your method declaration like this.
-(void)signUp:(QBUser*)user andHandler:(void (^)(BOOL result))completionHandler;
And its definition
-(void)signUp:(QBUser*)user andHandler:(void (^)(BOOL result))completionHandler {
[MYRequest signUp:user successBlock:^(QBResponse *response, QBUUser *user) {
[SingletonClass sharedMySingleton].userID = [NSString stringWithFormat:#"%#",response.data[#"id"]];
completionHandler(YES);
} errorBlock:^(QBResponse *response) {
// Handle error here
NSLog(#" error in creating session %#", response.error);
[SVProgressHUD showErrorWithStatus:NSLocalizedString(#"SignUp to Chat error!", nil)];
completionHandler(NO);
}];
}
Now call this method like this.
[ConnectionObj signUp:user andHandler:^(BOOL result) {
if(result) {
[helper normalSignupMethod:dict];
}
}];
You can call the normalSignupMethod once the signUp:successBlock request returns to successBlock
[MYRequest signUp:user successBlock:^(QBResponse *response, QBUUser *user) {
// Sign up was successful
// Store user id
[SingletonClass sharedMySingleton].userID = [NSString stringWithFormat:#"%#",response.data[#"id"]];
//call the signup method
[helper normalSignupMethod:dict];
} errorBlock:^(QBResponse *response) {
// Handle error here
NSLog(#" error in creating session %#", response.error);
[SVProgressHUD showErrorWithStatus:NSLocalizedString(#"SignUp to Chat error!", nil)];
}];

FBSession Must Be Specified Parse

I am trying to follow the Parse tutorial for Logging in With Facebook. However, the sample code does not match up with the guide, so the code there is useless. I have followed the guide completely, but after I login, it directs me to Facebook app, I give permission, it goes back to the app I am building, but I get the following error
FBSDKLog: Error for request to endpoint 'me': An open FBSession must be specified for calls to this endpoint.
What is going on? In Login controller:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
// handle successful response
} else if ([[[[error userInfo] objectForKey:#"error"] objectForKey:#"type"]
isEqualToString: #"OAuthException"]) { // Since the request failed, we can check if it was due to an invalid session
NSLog(#"The facebook session was invalidated");
[self logoutButtonAction:nil];
} else {
NSLog(#"Some other error: %#", error);
}
}];
if ([PFUser currentUser] && // Check if user is cached
[PFFacebookUtils isLinkedWithUser:[PFUser currentUser]]) { // Check if user is linked to Facebook
// Present the next view controller without animation
[self _presentUserDetailsViewControllerAnimated:NO];
}
}
- (IBAction)loginButtonTouchHandler:(id)sender {
// Set permissions required from the facebook user account
NSArray *permissionsArray = #[ #"user_about_me", #"user_relationships", #"user_birthday", #"user_location"];
[PFFacebookUtils initializeFacebook];
// Login PFUser using Facebook
[PFFacebookUtils logInWithPermissions:permissionsArray block:^(PFUser *user, NSError *error) {
[_activityIndicator stopAnimating]; // Hide loading indicator
if (!user) {
NSString *errorMessage = nil;
if (!error) {
NSLog(#"Uh oh. The user cancelled the Facebook login.");
errorMessage = #"Uh oh. The user cancelled the Facebook login.";
} else {
NSLog(#"Uh oh. An error occurred: %#", error);
errorMessage = [error localizedDescription];
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Log In Error"
message:errorMessage
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"Dismiss", nil];
[alert show];
} else {
if (user.isNew) {
NSLog(#"User with facebook signed up and logged in!");
} else {
NSLog(#"User with facebook logged in!");
}
[self _presentUserDetailsViewControllerAnimated:YES];
}
}];
[_activityIndicator startAnimating]; // Show loading indicator until login is finished
}
- (void)_presentUserDetailsViewControllerAnimated:(BOOL)animated {
UserDetailsViewController *detailsViewController = [[UserDetailsViewController alloc] init];
[self.navigationController pushViewController:detailsViewController animated:YES];
}
In my UserDetailsViewController:
- (void)viewDidLoad {
// ...
[self _loadData];
}
- (void)_loadData {
// ...
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
// result is a dictionary with the user's Facebook data
NSDictionary *userData = (NSDictionary *)result;
NSString *facebookID = userData[#"id"];
NSString *name = userData[#"name"];
NSString *location = userData[#"location"][#"name"];
NSString *gender = userData[#"gender"];
NSString *birthday = userData[#"birthday"];
NSString *relationship = userData[#"relationship_status"];
NSURL *pictureURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large&return_ssl_resources=1", facebookID]];
// URL should point to https://graph.facebook.com/{facebookId}/picture?type=large&return_ssl_resources=1
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:pictureURL];
// Run network request asynchronously
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:[NSOperationQueue mainQueue]
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError == nil && data != nil) {
// Set the image in the header imageView
self.headerImageView.image = [UIImage imageWithData:data];
}
}];
// Now add the data to the UI elements
// ...
}
}];
}
We figured it out, when trying to create an auto-login feature with this function:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
// handle successful response
} else if ([[[[error userInfo] objectForKey:#"error"] objectForKey:#"type"]
isEqualToString: #"OAuthException"]) { // Since the request failed, we can check if it was due to an invalid session
NSLog(#"The facebook session was invalidated");
[self logoutButtonAction:nil];
} else {
NSLog(#"Some other error: %#", error);
}
}];
if ([PFUser currentUser] && // Check if user is cached
[PFFacebookUtils isLinkedWithUser:[PFUser currentUser]]) { // Check if user is linked to Facebook
// Present the next view controller without animation
[self _presentUserDetailsViewControllerAnimated:NO];
}
}
We actually end up skipping the [PFFacebookUtils initializeFacebook] call, since it only happens when you push the login button. The solution is to put this call in the appDelegate in the method application:didFinishLaunchingWithOptions:

iOS Twitter SLRequest returning url domain error -1012

I am attempting to perform reverse oauth to get twitter access tokens for a server.
I have figured out how to submit the request and receive a response, but when I do, it gives me this error:
Error: The operation couldn’t be completed. (NSURLErrorDomain error -1012.)
I looked this up, and it says that it means the user has canceled the request. I am not sure how this is possible, and I cannot figure out how to fix it.
Here is my code:
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp];
NSString *oauth_nonce = [self genRandStringLength:32];
NSString *oauth_timestamp = [timeStampObj stringValue];
NSURL *feedURL = [NSURL URLWithString:#"https://api.twitter.com/oauth/request_token"];
NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys: #"my key here", #"oauth_consumer_key", oauth_nonce, #"oauth_nonce", #"HMAC-SHA1", #"oauth_signature_method", oauth_timestamp, #"oauth_timestamp", #"1.0", #"oauth_version", #"reverse_auth", #"x_auth_mode", nil];
SLRequest *twitterFeed = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:feedURL parameters:parameters];
twitterFeed.account = self.userAccount;
// Making the request
[twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// Check if we reached the reate limit
if ([urlResponse statusCode] == 429) {
NSLog(#"Rate limit reached");
return;
}
// Check if there was an error
if (error) {
NSLog(#"The Error is: %#", error.localizedDescription);
return;
}
// Check if there is some response data
if (responseData) {
NSLog(#"%#", responseData);
}
});
}];
There must be something simple I am missing, and this is keeping me from finishing a project. Any help would be great, thanks!
Error code -1012 can be due to an authentication challenge. In my case, a Twitter account existed in Settings, but was not logged in for some reason. Once I entered the password for the account, everything worked perfectly.
I got this problem when I sent a request to https://api.twitter.com/oauth/request_token with an extra nonce and signature in the header. Specifically, the following code gave me a 1012, but the next chunk of code succeeded. This code is adapted from Sean Cook's Reverse Twitter Auth example.
/**
* The first stage of Reverse Auth.
*
* In this step, we sign and send a request to Twitter to obtain an
* Authorization: header which we will use in Step 2.
*
* #param completion The block to call when finished. Can be called on any thread.
*/
- (void)_step1WithCompletion:(TWAPIHandler)completion
{
NSURL *url = [NSURL URLWithString:TW_OAUTH_URL_REQUEST_TOKEN];
NSDictionary *dict = #{TW_X_AUTH_MODE_KEY: TW_X_AUTH_MODE_REVERSE_AUTH,
TW_OAUTH_NONCE:[self nonce],
TW_SIGNATURE_METHOD: TW_SIGNATURE_METHOD_VALUE,
};
TWSignedRequest *step1Request = [[TWSignedRequest alloc] initWithURL:url parameters:dict requestMethod:TWSignedRequestMethodPOST];
TWDLog(#"Step 1: Sending a request to %#\nparameters %#\n", url, dict);
[step1Request performRequestWithHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(data, error);
});
}
The following works. Note the change in the dict.
/**
* The first stage of Reverse Auth.
*
* In this step, we sign and send a request to Twitter to obtain an
* Authorization: header which we will use in Step 2.
*
* #param completion The block to call when finished. Can be called on any thread.
*/
- (void)_step1WithCompletion:(TWAPIHandler)completion
{
NSURL *url = [NSURL URLWithString:TW_OAUTH_URL_REQUEST_TOKEN];
NSDictionary *dict = #{TW_X_AUTH_MODE_KEY: TW_X_AUTH_MODE_REVERSE_AUTH};
TWSignedRequest *step1Request = [[TWSignedRequest alloc] initWithURL:url parameters:dict requestMethod:TWSignedRequestMethodPOST];
TWDLog(#"Step 1: Sending a request to %#\nparameters %#\n", url, dict);
[step1Request performRequestWithHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(data, error);
});
}

Resources