Amazon Web Services Objective-C SDK to Sign a Query Request - ios

I am trying to create a signature for AWS signature as described in this doc.
The Doc has Java code i need equivalent Objective-c Code
http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
I have made a class AWSRequest
// AWSRequest.h
#import <Foundation/Foundation.h>
#import "../AmazonServiceRequest.h"
#interface AWSRequest : AmazonServiceRequest
- (void)makeSignature;
#end
Implement
// AWSRequest.m
#import "AWSRequest.h"
#import <AWSiOSSDK/AWSRuntime.h>
#import "AmazonAuthUtils.h"
#implementation AWSRequest
-(void)makeSignature
{
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970];
float finalTime = timeInterval;
NSString *sendTimeStamp = F(#"%0.f%#", finalTime , #"000");
[self setParameterValue:credentials.accessKey forKey:#"AWSAccessKeyId"];
[self setParameterValue:#"2" forKey:#"SignatureVersion"];
[self setParameterValue:sendTimeStamp forKey:#"Timestamp"];
[self setParameterValue:#"HmacSHA256" forKey:#"SignatureMethod"];
NSData *dataToSign = [[AmazonAuthUtils getV2StringToSign:[NSURL URLWithString:self.endpoint] request:self] dataUsingEncoding:NSUTF8StringEncoding];
NSString *signature = [AmazonAuthUtils HMACSign:dataToSign withKey:credentials.secretKey usingAlgorithm:kCCHmacAlgSHA256];
[self setParameterValue:signature forKey:#"Signature"];
}
Then the following methods calls the service
NSString *accessKey = #"Q_____O";
NSString *secretKey = #"2____2";
AmazonCredentials *credentials = [[AmazonCredentials alloc] initWithAccessKey:accessKey withSecretKey:secretKey];
AmazonServiceRequest *serviceRequest = [[AmazonServiceRequest alloc] init];
serviceRequest.credentials = credentials;
AWSRequest *request = [[AWSRequest alloc] init];
request.credentials = credentials;
request.delegate = self;
[request makeSignature];
Is there something wrong in the code ? Like using the TimeStamp etc as i am always getting Unauthorized error from web service.
Following links might be helpful
http://www.cocoanetics.com/2013/07/talking-to-amazon-web-services/
http://mobile.awsblog.com/post/Tx296UMHAW17ZOV/Using-Different-AWS-Regions-with-the-AWS-Mobile-SDKs
http://mobile.awsblog.com/post/Tx31X75XISXHRH8/Managing-Credentials-in-Mobile-Applications

Which service are you using? Most services now require signature version 4.
You link to our blog, but are you aware of the AWS SDK for iOS? Even if you don't want to use the SDK directly, the source code is available on GitHub so you can compare your implementation versus our official one.

Related

iOS : AWSAPIGateWay - Missing authentication issue

The app is not getting successful API call after hours of inactivity.
Steps to reproduce the behavior:
Creation of Amazon Cognito ID.
API calls.
Put the app in the background.
Refresh code after 8 hours. (initializeAmazonCongnitoProviderWithCompletionBlock mentioned below is called first when the app comes to foreground only after 8 hours)
Missing auth token will appear as suggested in the screenshot.
Which AWS service(s) are affected?
- (void)initializeAmazonCognitoProviderWithCompletionBlock:(void (^)(void))completion {
[[UIApplication sharedApplication].keyWindow setUserInteractionEnabled:FALSE];
NSString* AWSCognitoPoolID = [[[NSBundle mainBundle] infoDictionary] valueForKey:#"AWSCognitoID"];
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AMAZON_COGNITO_REGION identityPoolId:AWSCognitoPoolID];
[credentialsProvider clearCredentials];
AWSServiceConfiguration *configuration =[[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
[self getCognitoID:credentialsProvider CompletionBlock:^{
[self expirationHandler:credentialsProvider CompletionBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication].keyWindow setUserInteractionEnabled:TRUE];
});
completion();
}];
}];
}
- (void)expirationHandler:(AWSCognitoCredentialsProvider *)creds CompletionBlock: (void (^)(void))completion {
[[creds credentials] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
[self initializeAmazonCognitoProviderWithCompletionBlock:^{}];
} else {
AWSCredentials *cred = (AWSCredentials*) task.result;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]init];
[dateFormat setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZ"];
/* https://aws.amazon.com/premiumsupport/knowledge-center/security-token-expired/
https://forums.aws.amazon.com/thread.jspa?threadID=166398
We should fire timer before 5 minutes of expiration.
NSString *expF = [dateFormat stringFromDate:cred.expiration];
*/
[NSTimer scheduledTimerWithTimeInterval:cred.expiration.timeIntervalSinceNow - 300 target:self selector:#selector(initializeAmazonCognitoProviderWithCompletionBlock:) userInfo:nil repeats:NO];
completion();
}
return nil;
}];
}
- (void)getCognitoID:(AWSCognitoCredentialsProvider *)creds CompletionBlock: (void (^)(void))completion {
[[creds getIdentityId] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
[self initializeAmazonCognitoProviderWithCompletionBlock:^{}];
} else {
NSString *cognitoId = task.result;
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (![[standardDefaults valueForKey:#"UserCognitoID"] isEqualToString:cognitoId]) {
[standardDefaults setObject:#"" forKey:BainPreferenceToken];
[standardDefaults setInteger:0 forKey:#"registrationPostComplete"];
}
[standardDefaults setObject:cognitoId forKey:#"UserCognitoID"];
[standardDefaults synchronize];
completion();
}
return nil;
}];
}
AWSCognitoCredentialsProvider is an object that retrieves the credentials from Amazon Cognito and supplies credentials to sign the requests to AWS (for example to upload file to S3).
AWSCognitoIdentityProvider object in the SDK provides the abstraction for Amazon Cognito UserPools and helps you sign-in a user and retrieve the tokens that proves that the user is authenticated.
When a user is signed-in and AWSCognitoIdentityProvider has retrieved the tokens from Amazon Cognito UserPools, the tokens can be federated into the AWSCognitoCredentialsProvider in order to retrieve AWSCredentials and Cognito Identity Id for this authenticated user. To do so, you need to supply the token in the logins map and pass the logins map to AWSCognitoCredentialsProvider.
To accomplish this, you need to
1) create a class that confirms to the AWSIdentityProviderManager protocol and implements the logins method that returns a map of { providerLoginKey -> token }.
#interface AWSFederationManager : NSObject<AWSIdentityProviderManager>
#property (nonatomic, readonly, strong) AWSCognitoCredentialsProvider *credentialsProvider;
#end
#interface AWSFederationManager()
#property (nonatomic, readwrite, strong) AWSCognitoCredentialsProvider *credentialsProvider;
#end
#implementation AWSFederationManager
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
return [AWSTask taskWithResult:#{[<identityProviderName> : <token>}];
}
#end
2) Set the delegate of the AWSCognitoCredentialsProvider object to the class created in step (1).
#implementation AWSFederationManager
- (instancetype) initialize {
[self.credentialsProvider setIdentityProviderManagerOnce:self];
}
#end
Now once the federation is successful, you will be able to retrieve the AWSCredentials and Cognito Identity Id.
You can refer Source for the reference implementation.

Correct way to store logs for iOS

I have an in-home app that will be supported by IT team.
When they support desktop apps, they may read logs to find any troubles (like server returned 404 on sync request).
I can use NSLog for iOS, but how can user access them with out of Xcode?
I can't ask any user "please give me your phone to investigate what has happened".
Does there is some tool any IT person with out of Xcode and Mac may use to read iOS logs?
Does there is some tool any IT person with out of Xcode and Mac may use to read iOS logs?
Unfortunately not. It used to be that you could run an app on your device that would read the Console log, but Apple took that ability away; I guess they saw it as a security breach.
If your user can get to a Mac running Xcode, they can view the console log directly in Xcode.
Otherwise, as others have suggested, you will have to build into your app the capacity to keep a log in a place you can get to. For example you can write to a file and then offer (within the app) to email that file to yourself. Many apps have an interface to a facility like this in their Settings bundle.
I've been using the combination of CocoaLumberjack, Antenna & DDAntennalogger at work for remote logging. Basically, you have to set up an end-point at your server and Antenna will be used to send the logs remotely.
Here's the reference that when configuring it on my project:
Remote logging using CocoaLumberjack, Antenna & DDAntennaLogger
This is how you can do it:
Step 1: Redirect your NSLog statements to a text file in file system. This you can do on specific user action or always enable it and delete it periodically.
Step 2: Have a web service which will allow you to upload the saved logs in the file system. You could trigger this on user action or may be a timer based job.
Step 3: Delete the logs from file system once upload is successful.
Here is a example of such a custom logger:
#import "MyCustomLogging.h"
#define kMyCustomLoggingFile #"NSLogging.txt"
static NSString *const kMyDeviceLogUploadURL = #"uploadDeviceLogURL";
#interface MyCustomLogging ()
#property (nonatomic, strong) MyRequestHandler *requestHandler;
#property (nonatomic, assign, getter = isNsLogRedirected) BOOL nsLogRedirected;
#property (nonatomic, assign) BOOL shouldStopLogging;
#property (nonatomic, strong) NSString *pathForLogging;
#end
#implementation MyCustomLogging
static int savedStdErr = 0;
static MyCustomLogging *sharedMyCustomLogging = nil;
+ (MyCustomLogging *)sharedMyCustomLogging {
static dispatch_once_t pred = 0;
dispatch_once(&pred, ^{
sharedMyCustomLogging = [[self alloc] init];
});
return sharedMyCustomLogging;
}
#pragma mark -
#pragma mark Start Method
- (void)startLogging {
// Starting the Redirection of the Logs
if (!self.isNsLogRedirected) {
[self nsLogRedirectedToFile];
}
}
#pragma mark -
#pragma mark Stop Method
- (void)stopLogging {
NSLog(#"Stopping the logging");
NSString *aLoggingPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
self.pathForLogging = [aLoggingPath stringByAppendingPathComponent:kMyCustomLoggingFile];
// If file already exists & logging was not redirected then directly upload the logs::A Possible case of app quit/crash without uploading previous logs
if ([self isLogFilePresent] && !self.nsLogRedirected) {
[self uploadLogs];
} else if (self.isNsLogRedirected) { //Check for Successive Stop Notifications
self.shouldStopLogging = YES;
[self restoreNSLog];
} else {
NSDictionary *anUserInfo = #{kMyDeviceLogUplodStatusKey: kMyValueOne};
[[NSNotificationCenter defaultCenter] postNotificationName:kMyDeviceLogsUploadNotification object:nil userInfo:anUserInfo];
}
}
#pragma mark -
#pragma mark Private Method
- (void)nsLogRedirectedToFile {
if (!self.isNsLogRedirected) {
NSLog(#"Redirecting NSLogs to a file.....");
self.nsLogRedirected = YES;
savedStdErr = dup(STDERR_FILENO);
NSString *aLoggingPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
self.pathForLogging = [aLoggingPath stringByAppendingPathComponent:kMyCustomLoggingFile];
NSLog(#"Logging Path: %#", self.pathForLogging);
freopen([self.pathForLogging cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
NSLog(#"NSLog Redirected to a file Succesfully");
[MySessionObject setLoggingOn:YES];
}
}
- (void)restoreNSLog {
if (self.isNsLogRedirected) {
[MySessionObject setLoggingOn:NO];
NSLog(#"NSLog Will be Restored now....");
self.nsLogRedirected = NO;
fflush(stderr);
dup2(savedStdErr, STDERR_FILENO);
close(savedStdErr);
savedStdErr = 0;
}
[self uploadLogs];
NSLog(#"NSLog Restored Successfully");
}
- (void)uploadLogs {
NSLog(#"Now uploading files");
// Disable logging before files are uploading
MySessionObject.enableLogging = NO;
NSError *anError = nil;
NSData *aLogData = [NSData dataWithContentsOfFile:self.pathForLogging options:NSDataReadingUncached error:&anError];
//Converting to String
NSString *aLogString = [[NSString alloc] initWithData:aLogData encoding:NSUTF8StringEncoding];
NSMutableDictionary *aPostBody = [[NSMutableDictionary alloc] initWithCapacity:3];
[aPostBody setValue:aLogString forKey:#"logData"];
[aPostBody setValue:MySessionObject.wifiMACAddress forKey:#"deviceMACAddress"];
[aPostBody setValue:MySessionObject.deviceToken forKey:#"deviceID"];
__weak MyCustomLogging *aBlockSelf = self;
self.requestHandler = [[MyRequestHandler alloc] initWithEndPoint:#"/uploadLogs" body:aPostBody container:nil loadingOverlayTitle:nil successHandler:^(NSDictionary *iResponse) {
if (iResponse) {
//Remove the File From the Path
NSError *aFileError = nil;
BOOL aFileRemoveSuccess = [[NSFileManager defaultManager] removeItemAtPath:self.pathForLogging error:&aFileError];
if (!aFileRemoveSuccess) {
//Tracking the Event
NSString *aDescription = [NSString stringWithFormat:#"Error Code:%ld Error Description:%#", (long)[aFileError code], [aFileError localizedDescription]];
NSLog(#"Error occured while deleting log file:%#", aDescription);
}
// Clearing all
aBlockSelf.pathForLogging = nil;
NSDictionary *anUserInfo = #{kMyDeviceLogUplodStatusKey: kMyValueOne};
[[NSNotificationCenter defaultCenter] postNotificationName:kMyDeviceLogsUploadNotification object:nil userInfo:anUserInfo];
}
} andErrorHandler:^(NSString *iMessage, NSString *iKey, NSInteger iErrorCode, BOOL iIsNetworkError) {
NSDictionary *anUserInfo = #{kMyDeviceLogUplodStatusKey: kMyValueZero};
[[NSNotificationCenter defaultCenter] postNotificationName:kMyDeviceLogsUploadNotification object:nil userInfo:anUserInfo];
}];
[self.requestHandler executeRequest];
}
- (BOOL)isLogFilePresent {
NSFileManager *aFileManager = [[NSFileManager alloc] init];
BOOL aFilePresent = [aFileManager fileExistsAtPath:self.pathForLogging];
return aFilePresent;
}
#end

AFNetworking getting response JSON from the wrong endpoint

I am seeing a really weird and random issue in my code that I can't track down. I am getting crashes in my data model init methods when returning from AFNetworking JSON request methods. When the app does crash I am able to step back in the call stack to debug the what the JSON request/response was. The weird part is when I check the URL, request, and resonseJSON. The responseJSON does not match the expected result of the URL/request. It's like I am getting some other API methods call and data. Because the data/JSON is not what I expect the app will crash on model init.
The data I get back is usually different and not always the same. Sometimes the data is from endpoint A and sometimes it is from B, it's never consistent. It does however seem to crash consistently in the same model object.
Request endpoint A data but I get back endpoint B data. When I debug the AFHttpOperation when it crashes I see this is the result. It's almost like 2 calls are getting crossed and is some type of race condition. Below is a sample of my model object, Rest client, and model access layer.
Model Object
#implementation Applications
- (id)initWithData:(NSDictionary *)appData forLocation:(Location *)location inCategory:(Category *)category {
// appData is the JSON returned by The Rest Client and AFNetworking
self = [super init];
DDLogVerbose(#"appData = %#", appData);
if (self) {
_location = location;
_listeners = [NSMutableArray mutableArrayUsingWeakReferences];
_devices = [[NSMutableDictionary alloc] init];
_category = category;
_subscriptions = [Utility sanitizeArray:appData[#"Subscriptions"]];
}
return self;
}
#end
#implementation Location
- (void)refreshApplications {
[[Model shared] appsForLocation:self
success:^(NSObject *obj) {
self.apps = nil; //we have to get our apps again
self.apps = [NSMutableArray array];
NSArray *newApps = (NSArray *) obj;
for (NSDictionary *app in newApps) {
**// This is where it's crashing!**
Applications *newApp = [[Applications alloc] initWithData:app
forLocation:self
inCategory:[[SmartAppCategory alloc] init]];
[self.apps addObject:newApp];
}
[self notifyListeners];
}
error:nil];
}
#end
Rest Client
#interface Rest
+ (Rest *)sharedClient;
- (void)GET:(NSString *)path parameters:(NSDictionary *)params success:(SuccessCallback)sCallback error:(ErrorCallback)eCallback;
#end
#implementation Rest
+ (Rest *)sharedClient {
static dispatch_once_t token;
static Rest *shared = nil;
dispatch_once(&token, ^{
shared = [[Rest alloc] init];
});
return shared;
}
- (id)init {
self = [super init];
if (self) {
[self createClients];
}
return self;
}
- (void)createClients {
// Setup the Secure Client
// Private implementation properties
self.secureClient = [[AFOAuth2Client alloc] initWithBaseURL:baseUrl clientID:OAUTH2_CLIENT_ID secret:OAUTH2_CLIENT_SECRET];
[self.secureClient setParameterEncoding:AFJSONParameterEncoding];
AFOAuthCredential *credential = (AFOAuthCredential *) [NSKeyedUnarchiver unarchiveObjectWithData:[KeyChainStore dataForKey:KEYCHAIN_SETTINGS_AFOAuthCredential]];
if (credential) {
[self.secureClient setAuthorizationHeaderWithToken:credential.accessToken];
}
// Setup the Anonymous Client
self.anonymousClient = [[AFHTTPClient alloc] initWithBaseURL:baseUrl];
[self.anonymousClient setParameterEncoding:AFJSONParameterEncoding];
[self.anonymousClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
}
- (void)GET:(NSString *)path parameters:(NSDictionary *)params success:(SuccessCallback)sCallback error:(ErrorCallback)eCallback {
[_secureClient getPath:path
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
DDLogVerbose(#"Success Path: %# JSON: %#", path, responseObject);
if (sCallback) sCallback(responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[Rest callErrorBlock:eCallback withOperation:operation];
}];
}
#end
Model Access Layer
#interface Model
+ (Model *)shared;
- (void)appsForLocation:(Location *)location success:(SuccessCallback)success error:(ErrorCallback)error;
#end
#implementation Model
- (void)appsForLocation:(Location *)location success:(SuccessCallback)success error:(ErrorCallback)error {
NSString *path = [NSString stringWithFormat:#"/api/locations/%#/apps/", location.locationId];
[[Rest sharedClient] GET:path parameters:nil success:success error:error];
}
#end
A Location is a root object in the application and it will be told to refresh often. Either through UI interaction, events, or data Deserialization the the refreshApplications will execute to get more data from the server. Meanwhile other requests and events are going on in the application to get and send data to the API is JSON. Some of these GET calls to other endpoints seem to be messing with the response data.
Questions
How could this be happening with AFNetworking?
Am I being too quick to blame AFNetowrking and should I be looking for other places in my system that could be crossing the responses? I do have a load balanced backend hosted at Amazon.
Is this an endpoint issue?
How can I better debug and reproduce this issue? It only comes up at random times and is very hard to replicate. I have to continually run and re-run the application in hopes that it is crash.
Are there any advanced debugging techniques that I can use to back trace this call/crash using xcode?
I recommend that you use Charles proxy to double-check that the data you're receiving is correct. There's a trial version available that works identically to the registered version for 30 days. My first guess is that there's either some sort of buggy cache layer between you and your server, or your server is buggy. An HTTP proxy like Charles will allow you to confirm or reject this hypothesis.
This page explains how to set up Charles to proxy non-HTTPS connections from iOS devices.
To debug non-HTTPS as well as HTTPS Traffic use the mitmproxy
It allows you to inspect all packages and also resend them and much more.
With this you can check what really happens and if the backend is the problem or if AFNetworking has a Bug.
And as a cool side effect mitmproxy is totally free and Open-Sourced under the MIT Licensed.
On their website you will find some handy tutorials specific for iOS.

iOS Twitter Reverse OAuth

I have been pouring over the internet for days now trying to figure out how to implement this.
I need to request the access token and secret from twitter in order to pass this to a server that will process the users tweets for my application.
I have been following this link https://dev.twitter.com/docs/ios/using-reverse-auth
The problem is step 1. They dont give you an example of step 1.
Here is my code:
NSURL *url = [NSURL URLWithString:TW_OAUTH_URL_REQUEST_TOKEN];
NSDictionary *parameters = #{TW_X_AUTH_MODE_KEY:TW_X_AUTH_MODE_REVERSE_AUTH};
SLRequest *getTwitterAuth = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:parameters];
// Assume that we stored the result of Step 1 into a var 'resultOfStep1'
NSString *S = resultOfStep1;
NSDictionary *step2Params = [[NSMutableDictionary alloc] init];
[step2Params setValue:#"kfLxMJsk7fqIuy8URhleFg" forKey:#"x_reverse_auth_target"];
[step2Params setValue:S forKey:#"x_reverse_auth_parameters"];
NSURL *url2 = [NSURL URLWithString:#"https://api.twitter.com/oauth/access_token"];
SLRequest *stepTwoRequest =
[SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:url2 parameters:step2Params];
// You *MUST* keep the ACAccountStore alive for as long as you need an ACAccount instance
// See WWDC 2011 Session 124 for more info.
self.accountStore = [[ACAccountStore alloc] init];
// We only want to receive Twitter accounts
ACAccountType *twitterType =
[self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Obtain the user's permission to access the store
[self.accountStore requestAccessToAccountsWithType:twitterType
withCompletionHandler:^(BOOL granted, NSError *error) {
if (!granted) {
// handle this scenario gracefully
} else {
// obtain all the local account instances
NSArray *accounts =
[self.accountStore accountsWithAccountType:twitterType];
// for simplicity, we will choose the first account returned - in your app,
// you should ensure that the user chooses the correct Twitter account
// to use with your application. DO NOT FORGET THIS STEP.
[stepTwoRequest setAccount:[accounts objectAtIndex:0]];
// execute the request
[stepTwoRequest performRequestWithHandler:
^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSString *responseStr =
[[NSString alloc] initWithData:responseData
encoding:NSUTF8StringEncoding];
// see below for an example response
NSLog(#"The user's info for your server:\n%#", responseStr);
}];
}
}];
I have been trying to figure out how I process the SLRequest in oder to pass it to step 2 from the twitter docs.
I have also used this here: https://github.com/seancook/TWReverseAuthExample
This code is great but very complex. Any help would be greatly appreciated! Thanks!
The reason step one doesn't have any code is that they assume you will do this on your server or before hand or something like that. Basically you need to generate a key that your app will use to convert iOS tokens to normal tokens.
There is a script that will make the request for you here: http://www.ananseproductions.com/twitter-reverse-auth-headaches/ Its written in ruby so you could use something similar if you have a ruby server.
Personally I would have my app request this token from my server, then make the request to twitter, then post the new token back to my server.
Here is a class to help accomplish just this with a single method call that returns a dictionary with the token and token secret.
https://github.com/kbegeman/Twitter-Reverse-Auth
Hope this helps others out!
As of this code https://github.com/seancook/TWReverseAuthExample , it's fairly simple to implement in your own application. I prefer to create reusable classes, so I don't have to implement the same code multiple times. Normally you would create some singleton and work with it on the following tutorial. However the point of this instruction is not to teach you how to create singletons, so for the simplicity sake, we will use AppDelegate.h/m which is easily accessible from all over the application.
All you have to do is the following:
Open yours and Sean Cook's project (the one which URL is above)
Drag and copy Source->Vendor->ABOauthCore group into your project
Select TWAPIManager.h/m, TWSignedRequest.h/m and copy them into your project
Add the below code into your AppDelegate.h file
#property (nonatomic, strong) ACAccountStore* store;
#property (nonatomic, strong) TWAPIManager *apiManager;
#property (nonatomic, strong) NSArray *accounts;
-(void)storeAccountWithAccessToken:(NSString *)token secret:(NSString *)secret;
-(void)performReverseAuth:(id)sender inView:(UIView*)viewToDisplaySheet;
-(void)_refreshTwitterAccounts;
Now paste the following methods into your AppDelegate.m file
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex;
-(void)_refreshTwitterAccounts;
-(void)_obtainAccessToAccountsWithBlock:(void (^)(BOOL))block;
-(void)performReverseAuth:(id)sender inView:(UIView*)viewToDisplaySheet;
In some initialization method of your file, or as of this example in: `application: didFinishLaunchingWithOptions' paste the following code:
_store = [[ACAccountStore alloc] init];
_apiManager = [[TWAPIManager alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_refreshTwitterAccounts) name:ACAccountStoreDidChangeNotification object:nil];
Remember to remove observer using the following code. Paste it in AppDelegate.m:
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Open your app-Info.plist file and add 2 string keys. Take their values from: https://apps.twitter.com/
TWITTER_CONSUMER_KEY
TWITTER_CONSUMER_SECRET
In the View Controller that you want to use to implement twitter features, in the viewDidLoad method, add the following code:
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate _refreshTwitterAccounts];
OK, finally you are ready to start the whole machine. In the View Controller that you want to use to implement twitter features, create UIButton called _reverseAuthBtn and create an IBAction to it. Then in your IBAction paste the following code:
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate performReverseAuth:sender inView:self.view];
Whew, I guess that's it! If I haven't forgotten about anything, you have got Twitter Reverse Oauth implementation, and if you want to use it in multiple view controllers, all you have to do is do steps 1-8, and then paste the code from the steps 9 and 10 into your view controller.
Best regards!
Use this lib, it works perfectly!
https://github.com/nst/STTwitter
Info how to implement: https://github.com/nst/STTwitter#reverse-authentication
:)

Suggestion on how to link APN's device token to a registered user (through phonegap or UIWebView)

Similar question here: jQueryMobile, Phonegap and Device Token - iOS
The scenario is, I have this PhoneGap web based application, and the native iOS help me registered the device on APN and I received the device token in my server database.
Question 1: How do you associate a registered user (through the UIWebView) to this device token using PhoneGap?
What's in my mind now is to write a custom plugin and pass the device token along during user registration. Is there any better alternative?
Question 2: Since device_token can be changed from time to time, how should I re-link this user to this device_token?
Perhaps whenever the user login, I do a window.plugins.PluginName.getDeviceToken and sync it?
{user_id:123, old_device_token: 'xxxx..', new_device_token: 'xxx...'}?
Fyi, this application is built for an event and the client has requested people to people messaging on this mobile app. How do you push a new message notification to "John Doe" when he received a message from his friend? - Question is how to link "John Doe" to a specific device_token?
This could not be too iOS specific as this application has to be deployed on Android as well (C2DM).
Any help is welcomed!
Edit: Possible solution?
Restless research emerges this possible solution:
[Native] Application started - APN registration started and device_token is received
[Native] Store this device_token into local store (CoreData/SqlLite or Property Lists?) and send it to server to do device_token registration
[WebView] Whenever a user is login or signup, this device_token will be queried through PhoneGap, hashed and sent over to the server to do a sign in, comparison and link up.
Any unforeseen scenario is problematic?
EDIT: Answer
I have my complete working solution posted in the answer. Check it out here: https://stackoverflow.com/a/9628592/534862
For the sake of completeness, this is my solution after using #TDeBailleul solution. Ported over to Cordova. (Tested on iOS only. After I have finished Android version I will post a plugin for this:
Environment
Cordova 1.5.0 (Formerly PhoneGap)
Xcode 4.3.1
iOS 5.1
Mac OS X 10.7.3
Do not use Automatic Reference Counting (ARC) to avoid compilation error
Go through this freaking long tutorial to have your Push Notification certificate ready
Working Flow
[Native] Application started - APN registration started and device_token is received at server side
[Native] Application store the token in AppDelegate, and to get the token, use the PushToken in the following codes
[WebView] Whenever a user is login or signup, the token will be called through Cordova (formerly PhoneGap) plugin, hashed and being sent over to the server to do a sign in, matching and link up the user to a particular device.
So below is my complete working codes that do the native pulling of token. Server part is nothing but just linking accounts to devices. You should know how to do it through your favourite server side application.
AppDelegate.h
#interface AppDelegate : NSObject < UIApplicationDelegate, UIWebViewDelegate, CDVCommandDelegate > {
...
NSString* token;
...
}
...
...
...
#property (retain, nonatomic) NSString* token;
AppDelegate.m
Note: I'm a Obj-C newbie and I have included my attempt to post token to my server as well. If you do not need the posting mechanism, retain the first 5 lines (til' self.token = dt) in the didRegisterForRemoteNotificationsWithDeviceToken
...
#synthesize token;
...
- (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
...
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(#"Did finish launching with device token %#", deviceToken);
NSString *dt = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
dt = [dt stringByReplacingOccurrencesOfString:#" " withString:#""];
self.token = dt;
NSString *dv = [[UIDevice currentDevice] systemVersion];
NSString *dn = [[UIDevice currentDevice] systemName];
NSString *nick = [[UIDevice currentDevice] name];
NSString *model = [[UIDevice currentDevice] model];
NSString *uniqueIdentifier = [[UIDevice currentDevice] uniqueIdentifier];
NSMutableString *postData = [NSMutableString stringWithString: #"&deviceToken="];
[postData appendString:dt];
[postData appendFormat:#"&uniqueIdentifier=%#&application_uuid=5ade8400-e29c-41d4-a716-3641972a2ec6", uniqueIdentifier];
[postData appendFormat:#"&source=ios&name=%#&model=%#&systemName=%#&systemVersion=%#", nick, model, dn, dv];
NSString* escapedURLString = [postData stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
#try {
NSData *postData = [escapedURLString dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL: [NSURL URLWithString:#"{YOUR URL TO POST TOKEN TO SERVER}"]
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval: 180];
NSString *postLength = [[NSString alloc] initWithFormat: #"%d", [postData length]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (conn) {
//??
}else{
//??
}
}
#catch (NSException *exception) {
NSLog(#"Exception %#", exception);
}
}
...
PushToken.h
#import <Foundation/Foundation.h>
#import <CORDOVA/CDVPlugin.h>
#interface PushToken : CDVPlugin {
NSString* callbackID;
}
#property (nonatomic, copy) NSString* callbackID;
- (void) getToken:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
#end
PushToken.m
#import "PushToken.h"
#import "AppDelegate.h"
#implementation PushToken
#synthesize callbackID;
- (void)getToken:(NSMutableArray *)arguments withDict:(NSMutableDictionary *)options {
NSLog(#"Did called getToken");
self.callbackID = [arguments pop];
NSString *token = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).token;
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[token stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
if (token.length != 0) {
[self writeJavascript: [pluginResult toSuccessCallbackString:self.callbackID]];
}else{
[self writeJavascript: [pluginResult toErrorCallbackString:self.callbackID]];
}
}
#end
PushToken.js
var PushToken = {
/**
* Get token from the device
* #param {array} types - By default is ['getToken']
* #param {function} success Success callback, with token
* #param {function} fail Failure callback, with null
*/
getToken: function(types, success, fail) {
return Cordova.exec(success, fail, "PushToken", "getToken", types);
},
/**
* For the sake of iOS, we need an install function
*/
install: function() {
window.plugins = window.plugins || {};
window.plugins.PushToken = PushToken;
}
};
/**
* For the rest of the devices
*/
window.plugins = window.plugins || {};
window.plugins.PushToken = PushToken;
Usage
document.addEventListener('deviceready', function() {
if (typeof PushToken == 'object') {
PushToken.install();
}
PushToken.getToken(['getToken'], function(token) {
callback(token);
}, function() {
callback(null);
});
});
And you should be able to get your token from your Javascript side ;)
Have fun!
Cheers
The previous answers are really great ways to solve this problem and they do it in a very formal fashion. However, if you want a quick and dirty method of solving the problem, you can simply do this:
at the bottom of didRegisterForRemoteNotificationsWithDeviceToken add
NSString* jsString = [NSString stringWithFormat:#"var deviceToken = \"%#\";", deviceToken];
[self.viewController.webView stringByEvaluatingJavaScriptFromString:jsString];
In your javascript
deviceId = (typeof deviceToken !== "undefined") ? deviceToken : null;
Ok, I finally made a plugin that seems to work
1 - Make sure your PhoneGap Xcode project has been updated for the iOS 4 SDK.
2 - Create a PushToken folder in your Plugins folder add the following PushToken.m and PushToken.h files to it and then drag the folder to the Plugin folder in XCode, using "Create groups for any added folders"
3 - Add the PushToken.js files to your www folder on disk, and add reference(s) to the .js files as tags in your html file(s)
4 - Add new entry with key PushToken and string value PushToken to Plugins in PhoneGap.plist
PushToken.h
#import <Foundation/Foundation.h>
#import <PhoneGap/PGPlugin.h>
#interface PushToken : PGPlugin{
NSString* callbackID;
}
#property (nonatomic, copy) NSString* callbackID;
- (void) getToken:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
#end
PushToken.m
#import "PushToken.h"
#import "AppDelegate.h"
#implementation PushToken
#synthesize callbackID;
-(void)getToken:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
self.callbackID = [arguments pop];
NSString *token = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).token;
PluginResult* pluginResult = [PluginResult resultWithStatus:PGCommandStatus_OK messageAsString:[token stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
if(token.length != 0)
{
[self writeJavascript: [pluginResult toSuccessCallbackString:self.callbackID]];
}else {
[self writeJavascript: [pluginResult toErrorCallbackString:self.callbackID]];
}
}
#end
PushToken.js
var PushToken = {
getToken: function(types, success, fail) {
return PhoneGap.exec(success, fail, "PushToken", "getToken", types);
}
};
How to use
PushToken.getToken(
["getToken"] ,
function(token) {
console.log("Token : "+token);
},
function(error) {
console.log("Error : \r\n"+error);
}
);
AppDelegate.h
#interface AppDelegate : PhoneGapDelegate {
NSString* invokeString;
NSString* token;
}
#property (copy) NSString* invokeString;
#property (retain, nonatomic) NSString* token;
AppDelegate.m
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
self.token = [[[[deviceToken description]
stringByReplacingOccurrencesOfString: #"<" withString: #""]
stringByReplacingOccurrencesOfString: #">" withString: #""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
NSLog(#"My token is: %#", self.token);
}
I made it work on PhoneGap 1.1
Hope this helps

Resources