Saturday 31 May 2014

Withings API declassified (IOS)

This blog as name suggests is about integrating Withings Api to Ios application.And why  am i writing this?because I  had very bad experience integrating this api.The documentation provided by Withings for this  API is very poor.Anyway lets go back to main topic.
1.For Accessing User’s body metrics through third party application we need to register the application.After successful  registration we will be provided with API key(CONSUMER_KEY
) and Api secrete(CONSUMER_SECRET)
2.Now for the coding part withing uses Oauth 1.0 authentication .For this step I started with “TDOauth” but soon realized that there are few bugs .So I used “sample-Oauth1” code written by Christian Hansen, thanks to him.This code needs little modifications to access Withings web services.here are those modified line of code below.
 At first  we need to change predefined variables in OAuth1Controller.m like this

#define OAUTH_CALLBACK       @"http://wbsapi.withings.net/user" //Sometimes this has to be the same as the registered app callback url
#define CONSUMER_KEY         @"4b197d27275d4bb34c28e2c19c4d4878be42e1d1d2363"
#define CONSUMER_SECRET      @"e16526c14e7783487e0b51ffc06645203b08e588df37e6e5172"
#define AUTH_URL             @"https://oauth.withings.com/"
#define REQUEST_TOKEN_URL    @"account/request_token"
#define AUTHENTICATE_URL     @"account/authorize"
#define ACCESS_TOKEN_URL     @"account/access_token"
#define API_URL              @http://wbsapi.withings.net/
#define OAUTH_SCOPE_PARAM    @""

#define REQUEST_TOKEN_METHOD @"POST"
#define ACCESS_TOKEN_METHOD  @"POST"

Now “In order to get a permanent access, the Consumer must get from the Service provider 3 items :
·         The user id of the User : unique identifier of the User in the Service provider systems (Withings)
·         The access token : the consumer will send this token when requesting protected data, it will identify the Consumer and will be check if this matches with the registered link between the User and the Consumer
·         The access token secret : the consumer will use it to sign every request, to authenticate itself but will never send it”

I am not going in detail about how the Oauth work and how the tempory request token can be used to get permanent access token as the “Simple Oauth1” is well-written and as Withings provide documentation(helpful or not) for it.
 So next modification in this page is to add a “Oauth callback” variable in the param list in the time of calling to get request token.The code Snippet is as below


+ (NSMutableDictionary *)standardOauthParameters
{
    NSString *oauth_timestamp = [NSString stringWithFormat:@"%lu", (unsigned long)[NSDate.date timeIntervalSince1970]];
    NSString *oauth_nonce = [NSString getNonce];
    NSString *oauth_consumer_key = CONSUMER_KEY;
    NSString *oauth_signature_method = @"HMAC-SHA1";
    NSString *oauth_version = @"1.0";
   
    NSMutableDictionary *standardParameters = [NSMutableDictionary dictionary];
    [standardParameters setValue:oauth_consumer_key     forKey:@"oauth_consumer_key"];
    [standardParameters setValue:oauth_nonce            forKey:@"oauth_nonce"];
    [standardParameters setValue:oauth_signature_method forKey:@"oauth_signature_method"];
    [standardParameters setValue:oauth_timestamp        forKey:@"oauth_timestamp"];
    [standardParameters setValue:oauth_version          forKey:@"oauth_version"];
    /////added by sankhadeep
    [standardParameters setValue:OAUTH_CALLBACK          forKey:@"oauth_callback"];
   
    return standardParameters;
}

***Things to remember is that we need to remove this “oauth_callback” variable when accessing user data,otherwise we will get an “Invalid parameter provided” status from Withings.

Next thing is to call the authentication method from the app page.For me i just defined this method(not to mentioned that i did necessary import previously)

/////////Withings  Authorization Calling

-(void)authorizeApp{
    LoginWebViewController *loginWebViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"loginWebViewController"];
   
    [self presentViewController:loginWebViewController
                       animated:YES
                     completion:^{
                        
                         [self.oauth1Controller loginWithWebView:loginWebViewController.webView completion:^(NSDictionary *oauthTokens, NSError *error) {
                            
                             if (!error) {
                                
                                 // Store your tokens for authenticating your later requests, consider storing the tokens in the Keychain
                                 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                                
                                 NSLog(@"self.oauthToken=%@,self.oauthTokenSecret",oauthTokens);
                                 self.oauthToken = oauthTokens[@"oauth_token"];
                                 self.oauthTokenSecret = oauthTokens[@"oauth_token_secret"];
                                 userId=oauthTokens[@"userid"];
           
                                 [defaults setObject:self.oauthToken forKey:@"oauth_token"];
                                 [defaults setObject:self.oauthTokenSecret forKey:@"oauth_token_secret"];
                                 [defaults setObject:userId forKey:@"userid"];
                                 [defaults synchronize];
                                 int indexNumber=0;
                               
                                
                                // NSLog(@"TTTT %@ %@ %@",[defaults objectForKey:@"oauth_token"],[defaults objectForKey:@"oauth_token_secret"],[defaults objectForKey:@"userid"]);
                                
                             }
                             else
                             {
                                 NSLog(@"Error authenticating: %@", error.localizedDescription);
                             }
                             [self dismissViewControllerAnimated:YES completion: ^{
                                 self.oauth1Controller = nil;
                               
                             }];
                         }];
                     }];
   
}
/////////////////////////////////////////////

- (OAuth1Controller *)oauth1Controller
{
    if (_oauth1Controller == nil) {
        _oauth1Controller = [[OAuth1Controller alloc] init];
    }
    return _oauth1Controller;
}

This method will store the Access token after successful authentication into NSUserdefault.

This is the end of Authenication part.Now it is time to access User body metrics data.
Here is the sample calling for this ,but before calling withins service we need to make change to a method in OAuth1Controller.m  ,here we will remove the oauth_callback” parameters as it will a generate invalid Signature value.value. Withings expect all the Oauth related parameters except the "callback" one with every web service call.and the modified method will look like this :-

#pragma mark - Step 1 Obtaining a request token

- (void)obtainRequestTokenWithCompletion:(void (^)(NSError *error, NSDictionary *responseParams))completion
{
    NSString *request_url = [AUTH_URL stringByAppendingString:REQUEST_TOKEN_URL];
    NSString *oauth_consumer_secret = CONSUMER_SECRET;
   
    NSMutableDictionary *allParameters = [self.class standardOauthParameters];
    if ([OAUTH_SCOPE_PARAM length] > 0) [allParameters setValue:OAUTH_SCOPE_PARAM forKey:@"scope"];

    NSString *parametersString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
   
    NSString *baseString = [REQUEST_TOKEN_METHOD stringByAppendingFormat:@"&%@&%@", request_url.utf8AndURLEncode, parametersString.utf8AndURLEncode];
    NSString *secretString = [oauth_consumer_secret.utf8AndURLEncode stringByAppendingString:@"&"];
    NSString *oauth_signature = [self.class signClearText:baseString withSecret:secretString];
    [allParameters setValue:oauth_signature forKey:@"oauth_signature"];
    /////modified by sankhadeep sending allparameter instead of param dictionary for Withings
    parametersString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
   
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:request_url]];
    request.HTTPMethod = REQUEST_TOKEN_METHOD;
   
    NSMutableArray *parameterPairs = [NSMutableArray array];
    for (NSString *name in allParameters) {
        NSString *aPair = [name stringByAppendingFormat:@"=\"%@\"", [allParameters[name] utf8AndURLEncode]];
        [parameterPairs addObject:aPair];
    }
    NSString *oAuthHeader = [@"OAuth " stringByAppendingFormat:@"%@", [parameterPairs componentsJoinedByString:@", "]];
    [request setValue:oAuthHeader forHTTPHeaderField:@"Authorization"];
   
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               NSString *reponseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                               completion(nil, CHParametersFromQueryString(reponseString));
                           }];
}

####And the actual calling for getting data for perticular user is:-
-(void)getUserDataFromWithings:(NSString*)deviceType{
   
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    self.oauthToken=[defaults objectForKey:@"oauth_token"];
    self.oauthTokenSecret=[defaults objectForKey:@"oauth_token_secret"];
    userId=[defaults objectForKey:@"userid"];
   // NSLog(@"UserId %@",userId);
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:@"getmeas" forKey:@"action"];
    [dict setObject:userId forKey:@"userid"];
    [dict setObject:deviceType forKey:@"devtype"];
    [dict setObject:@"500" forKey:@"limit"];
   // [dict setObject:@"1" forKey:@"meastype"];
           [dict setObject:[NSString stringWithFormat:@"%d",(int)[[NSDate date] timeIntervalSince1970]] forKey:@"enddate"];    NSLog(@"self.oauthToken=%@,self.oauthTokenSecret=%@",self.oauthToken,self.oauthTokenSecret);
    NSURLRequest *request =
    [OAuth1Controller preparedRequestForPath:@"measure"
                                  parameters:dict
                                  HTTPmethod:@"GET"
                                  oauthToken:self.oauthToken
                                 oauthSecret:self.oauthTokenSecret];
   
   
    NSLog(@"RRRRR %@",request.URL);
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response,
                                               NSData *data, NSError *connectionError)
     {
         if (data.length > 0 && connectionError == nil)
         {
             NSDictionary *withingsData = [NSJSONSerialization JSONObjectWithData:data
                                                                      options:0
                                                                        error:NULL];
             //NSLog(@"HHHHHH %@===", withingsData);
            if ([[withingsData valueForKey:@"status"] intValue]==0) {
           
            
             NSLog(@"HHHHHH %@===",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
            

           }
         }
     }];
}
The “withingsData” variable will contain user data.That is all about integrating Withings. 

Also don't forget to remove the oauth_callback key from allParameters in the method
#pragma mark build authorized API-requests
+ (NSURLRequest *)preparedRequestForPath:(NSString *)path
                              parameters:(NSDictionary *)queryParameters
                              HTTPmethod:(NSString *)HTTPmethod
                              oauthToken:(NSString *)oauth_token
                             oauthSecret:(NSString *)oauth_token_secret
{
    if (!HTTPmethod
        || !oauth_token) return nil;
    
    NSMutableDictionary *allParameters = [self standardOauthParameters];
     [allParameters removeObjectForKey:@"oauth_callback"];///removed as it is not necessary for withings Api
    
    allParameters[@"oauth_token"] = oauth_token;
    if (queryParameters) [allParameters addEntriesFromDictionary:queryParameters];
    
    NSString *parametersString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
    
    NSString *request_url = API_URL;
    if (path) request_url = [request_url stringByAppendingString:path];
    NSString *oauth_consumer_secret = CONSUMER_SECRET;
    NSString *baseString = [HTTPmethod stringByAppendingFormat:@"&%@&%@", request_url.utf8AndURLEncode, parametersString.utf8AndURLEncode];
    NSString *secretString = [oauth_consumer_secret.utf8AndURLEncode stringByAppendingFormat:@"&%@", oauth_token_secret.utf8AndURLEncode];
    NSString *oauth_signature = [self.class signClearText:baseString withSecret:secretString];
    allParameters[@"oauth_signature"] = oauth_signature;
    
    NSString *queryString;
    
    
    NSLog(@"GGGGG %@",allParameters);
     if (allParameters) queryString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
    //if (queryParameters) queryString = CHQueryStringFromParametersWithEncoding(queryParameters, NSUTF8StringEncoding);
    if (queryString) request_url = [request_url stringByAppendingFormat:@"?%@", queryString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:request_url]];
    request.HTTPMethod = HTTPmethod;
    
    NSMutableArray *parameterPairs = [NSMutableArray array];
    [allParameters removeObjectsForKeys:queryParameters.allKeys];
    for (NSString *name in allParameters) {
        NSString *aPair = [name stringByAppendingFormat:@"=\"%@\"", [allParameters[name] utf8AndURLEncode]];
        [parameterPairs addObject:aPair];
    }
    NSString *oAuthHeader = [@"OAuth " stringByAppendingFormat:@"%@", [parameterPairs componentsJoinedByString:@", "]];
    [request setValue:oAuthHeader forHTTPHeaderField:@"Authorization"];
    if ([HTTPmethod isEqualToString:@"POST"]
        && queryParameters != nil) {
        NSData *body = [queryString dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody:body];
    }
    NSLog(@"GGGGRRRG %@",request);
    return request;

}