| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #import "UGClient.h" |
| #import "UGHTTPManager.h" |
| #import "SBJson.h" |
| #import "UGMultiStepAction.h" |
| #import "SSKeychain.h" |
| |
| NSString *g_deviceUUID = nil; |
| |
| @implementation UGClient |
| { |
| // the delegate for asynch callbacks |
| id m_delegate; |
| |
| // the mutex to protect the delegate variable |
| NSRecursiveLock *m_delegateLock; |
| |
| // a growing array of UGHTTPManager instances. See |
| // "HTTPMANAGER POOLING" further down in this file. |
| NSMutableArray *m_httpManagerPool; |
| |
| // the base URL for the service |
| NSString *m_baseURL; |
| |
| // the appID for the specific app |
| NSString *m_appID; |
| |
| // the appID for the specific app |
| NSString *m_orgID; |
| |
| // the cached auth token |
| UGUser *m_loggedInUser; |
| |
| // the auth code |
| NSString *m_auth; |
| |
| // the list of currently pending multi-step actions |
| NSMutableArray *m_pendingMultiStepActions; |
| |
| // logging state |
| BOOL m_bLogging; |
| } |
| |
| /************************** ACCESSORS *******************************/ |
| /************************** ACCESSORS *******************************/ |
| /************************** ACCESSORS *******************************/ |
| +(NSString *) version |
| { |
| return @"0.1.1"; |
| } |
| |
| -(NSString *)getAccessToken |
| { |
| return m_auth; |
| } |
| |
| -(UGUser *)getLoggedInUser |
| { |
| return m_loggedInUser; |
| } |
| |
| -(id) getDelegate |
| { |
| return m_delegate; |
| } |
| |
| /******************************* INIT *************************************/ |
| /******************************* INIT *************************************/ |
| /******************************* INIT *************************************/ |
| -(id)init |
| { |
| // you are not allowed to init without an organization id and application id |
| // you can't init with [UGClient new]. You must call |
| // [[UGClient alloc] initWithOrganizationId: <your UG org id> withApplicationId:<your UG app id>] |
| assert(0); |
| return nil; |
| } |
| |
| -(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID |
| { |
| self = [super init]; |
| if ( self ) |
| { |
| m_delegate = nil; |
| m_httpManagerPool = [NSMutableArray new]; |
| m_delegateLock = [NSRecursiveLock new]; |
| m_appID = applicationID; |
| m_orgID = organizationID; |
| m_baseURL = @"http://api.usergrid.com"; |
| m_pendingMultiStepActions = [NSMutableArray new]; |
| m_loggedInUser = nil; |
| m_bLogging = NO; |
| } |
| return self; |
| } |
| |
| -(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID baseURL:(NSString *)baseURL |
| { |
| self = [super init]; |
| if ( self ) |
| { |
| m_delegate = nil; |
| m_httpManagerPool = [NSMutableArray new]; |
| m_delegateLock = [NSRecursiveLock new]; |
| m_appID = applicationID; |
| m_orgID = organizationID; |
| m_baseURL = baseURL; |
| } |
| return self; |
| } |
| |
| -(BOOL) setDelegate:(id)delegate |
| { |
| // first off, clear any pending transactions |
| for ( int i=0 ; i<[m_httpManagerPool count] ; i++ ) |
| { |
| UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i]; |
| |
| // it's safe to call cancel at all times. |
| [mgr cancel]; |
| } |
| |
| // nil is a valid answer. It means we're synchronous now. |
| if ( delegate == nil ) |
| { |
| [m_delegateLock lock]; |
| m_delegate = nil; |
| [m_delegateLock unlock]; |
| return YES; |
| } |
| |
| // if it's not nil, it has to have the delegation function |
| if ( ![delegate respondsToSelector:@selector(ugClientResponse:)] ) |
| { |
| return NO; |
| } |
| |
| // if we're here, it means the delegate is valid |
| [m_delegateLock lock]; |
| m_delegate = delegate; |
| [m_delegateLock unlock]; |
| return YES; |
| } |
| |
| /************************* HTTPMANAGER POOLING *******************************/ |
| /************************* HTTPMANAGER POOLING *******************************/ |
| /************************* HTTPMANAGER POOLING *******************************/ |
| |
| // any given instance of UGHTTPManager can only manage one transaction at a time, |
| // but we want the client to be able to have as many going at once as he likes. |
| // so we have a pool of UGHTTPManagers as needed. |
| -(UGHTTPManager *)getHTTPManager; |
| { |
| // find the first unused HTTPManager |
| for ( int i=0 ; i<[m_httpManagerPool count] ; i++ ) |
| { |
| UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i]; |
| if ( [mgr isAvailable] ) |
| { |
| // tag this guy as available |
| [mgr setAvailable:NO]; |
| |
| // return him |
| return mgr; |
| } |
| } |
| |
| // if we're here, we didn't find any available managers |
| // so we'll need to make a new one |
| UGHTTPManager *newMgr = [UGHTTPManager new]; |
| |
| // mark it as in-use (we're about to return it) |
| [newMgr setAvailable:NO]; |
| |
| // tell it the auth to use |
| [newMgr setAuth:m_auth]; |
| |
| // add it to the array |
| [m_httpManagerPool addObject:newMgr]; |
| |
| // return it |
| return newMgr; |
| } |
| |
| -(void)releaseHTTPManager:(UGHTTPManager *)toRelease |
| { |
| [toRelease setAvailable:YES]; |
| } |
| |
| -(void)setAuth:(NSString *)auth |
| { |
| // note the auth for ourselves |
| m_auth = auth; |
| |
| // update all our managers |
| for ( int i=0 ; i<[m_httpManagerPool count] ; i++ ) |
| { |
| UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i]; |
| [mgr setAuth:m_auth]; |
| } |
| } |
| |
| /************************* GENERAL WORKHORSES *******************************/ |
| /************************* GENERAL WORKHORSES *******************************/ |
| /************************* GENERAL WORKHORSES *******************************/ |
| // url: the URL to hit |
| // op: a kUGHTTP constant. Example: kUGHTTPPost |
| // opData: The data to send along with the operation. Can be nil |
| -(UGClientResponse *)httpTransaction:(NSString *)url op:(int)op opData:(NSString *)opData |
| { |
| // get an http manager to do this transaction |
| UGHTTPManager *mgr = [self getHTTPManager]; |
| |
| if ( m_delegate ) |
| { |
| if ( m_bLogging ) |
| { |
| NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); |
| NSLog(@"Asynch outgoing call: '%@'", url); |
| } |
| |
| // asynch transaction |
| int transactionID = [mgr asyncTransaction:url operation:op operationData:opData delegate:self]; |
| |
| if ( m_bLogging ) |
| { |
| NSLog(@"Transaction ID:%d", transactionID); |
| NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); |
| } |
| |
| if ( transactionID == -1 ) |
| { |
| if ( m_bLogging ) |
| { |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); |
| NSLog(@"Response: ERROR: %@", [mgr getLastError]); |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); |
| } |
| |
| // there was an immediate failure in the transaction |
| UGClientResponse *response = [UGClientResponse new]; |
| [response setTransactionID:-1]; |
| [response setTransactionState:kUGClientResponseFailure]; |
| [response setResponse:[mgr getLastError]]; |
| [response setRawResponse:nil]; |
| return response; |
| } |
| else |
| { |
| // the transaction is in progress and pending |
| UGClientResponse *response = [UGClientResponse new]; |
| [response setTransactionID:transactionID]; |
| [response setTransactionState:kUGClientResponsePending]; |
| [response setResponse:nil]; |
| [response setRawResponse:nil]; |
| return response; |
| } |
| } |
| else |
| { |
| if ( m_bLogging ) |
| { |
| NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); |
| NSLog(@"Synch outgoing call: '%@'", url); |
| NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); |
| } |
| |
| // synch transaction |
| NSString *result = [mgr syncTransaction:url operation:op operationData:opData]; |
| |
| if ( m_bLogging ) |
| { |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); |
| if ( result ) |
| { |
| NSLog(@"Response:\n%@", result); |
| } |
| else |
| { |
| NSLog(@"Response: ERROR: %@", [mgr getLastError]); |
| } |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); |
| } |
| |
| // since we're doing a synch transaction, we are now done with this manager. |
| [self releaseHTTPManager:mgr]; |
| |
| if ( result ) |
| { |
| // got a valid result |
| UGClientResponse *response = [self createResponse:-1 jsonStr:result]; |
| return response; |
| } |
| else |
| { |
| // there was an error. Note the failure state, set the response to |
| // be the error string |
| UGClientResponse *response = [UGClientResponse new]; |
| [response setTransactionID:-1]; |
| [response setTransactionState:kUGClientResponseFailure]; |
| [response setResponse:[mgr getLastError]]; |
| [response setRawResponse:nil]; |
| return response; |
| } |
| } |
| } |
| |
| -(UGClientResponse *)createResponse:(int)transactionID jsonStr:(NSString *)jsonStr |
| { |
| UGClientResponse *response = [UGClientResponse new]; |
| |
| // set the raw response and transaction id |
| [response setRawResponse:jsonStr]; |
| [response setTransactionID:transactionID]; |
| |
| // parse the json |
| SBJsonParser *parser = [SBJsonParser new]; |
| NSError *error; |
| id result = [parser objectWithString:jsonStr error:&error]; |
| |
| if ( result ) |
| { |
| // first off, if the result is NOT an NSDictionary, something went wrong. |
| // there should never be an array response |
| if ( ![result isKindOfClass:[NSDictionary class]] ) |
| { |
| [response setTransactionState:kUGClientResponseFailure]; |
| [response setResponse:@"Internal error: Response parsed to something other than NSDictionary"]; |
| return response; |
| } |
| |
| // it successfully parsed. Though the result might still be an error. |
| // it could be the server returning an error in perfectly formated json. |
| NSString *err = [result valueForKey:@"error"]; |
| if ( err ) |
| { |
| // there was an error. See if there's a more detailed description. |
| // if there is, we'll use that. If not, we'll use the error value |
| // itself. |
| NSString *errDesc = [result valueForKey:@"error_description"]; |
| NSString *toReport = errDesc; |
| if ( !toReport ) toReport = err; |
| |
| [response setTransactionState:kUGClientResponseFailure]; |
| [response setResponse:toReport]; |
| return response; |
| } |
| |
| // if we're here we have a good auth. make note of it |
| NSString *auth = [result valueForKey:@"access_token"]; |
| if ( auth ) |
| { |
| [self setAuth: auth]; |
| |
| // if there's an access token, there might be a user |
| NSDictionary *dict = [result objectForKey:@"user"]; |
| if ( dict ) |
| { |
| // get the fields for the user |
| m_loggedInUser = [UGUser new]; |
| [m_loggedInUser setUsername:[dict valueForKey:@"username"]]; |
| [m_loggedInUser setUuid:[dict valueForKey:@"uuid"]]; |
| [m_loggedInUser setEmail:[dict valueForKey:@"email"]]; |
| [m_loggedInUser setPicture:[dict valueForKey:@"picture"]]; |
| } |
| } |
| |
| [response setTransactionState:kUGClientResponseSuccess]; |
| [response setResponse:result]; |
| return response; |
| } |
| else |
| { |
| // there was an error during json parsing. |
| [response setTransactionState:kUGClientResponseFailure]; |
| [response setResponse:[error localizedDescription]]; |
| return response; |
| } |
| } |
| |
| // basic URL assembly functions. For convenience |
| -(NSMutableString *)createURL:(NSString *)append1 |
| { |
| NSMutableString *ret = [NSMutableString new]; |
| [ret appendFormat:@"%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1]; |
| return ret; |
| } |
| |
| -(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 |
| { |
| NSMutableString *ret = [NSMutableString new]; |
| [ret appendFormat:@"%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2]; |
| return ret; |
| } |
| |
| -(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 |
| { |
| NSMutableString *ret = [NSMutableString new]; |
| [ret appendFormat:@"%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3]; |
| return ret; |
| } |
| |
| -(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 append4:(NSString *)append4 |
| { |
| NSMutableString *ret = [NSMutableString new]; |
| [ret appendFormat:@"%@/%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3, append4]; |
| return ret; |
| } |
| |
| -(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 append4:(NSString *)append4 append5:(NSString *)append5 |
| { |
| NSMutableString *ret = [NSMutableString new]; |
| [ret appendFormat:@"%@/%@/%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3, append4, append5]; |
| return ret; |
| } |
| |
| -(void)appendQueryToURL:(NSMutableString *)url query:(UGQuery *)query |
| { |
| if ( query ) |
| { |
| [url appendFormat:@"%@", [query getURLAppend]]; |
| } |
| } |
| |
| -(NSString *)createJSON:(NSDictionary *)data |
| { |
| NSString *ret = [self createJSON:data error:nil]; |
| |
| // the only way for ret to be nil here is for an internal |
| // function to have a bug. |
| assert(ret); |
| return ret; |
| } |
| |
| -(NSString *)createJSON:(NSDictionary *)data error:(NSString **)error |
| { |
| SBJsonWriter *writer = [SBJsonWriter new]; |
| NSError *jsonError; |
| NSString *jsonStr = [writer stringWithObject:data error:&jsonError]; |
| |
| if ( jsonStr ) |
| { |
| return jsonStr; |
| } |
| |
| // if we're here, there was an assembly error |
| if ( error ) |
| { |
| *error = [jsonError localizedDescription]; |
| } |
| return nil; |
| } |
| |
| /************************** UGHTTPMANAGER DELEGATES *******************************/ |
| /************************** UGHTTPMANAGER DELEGATES *******************************/ |
| /************************** UGHTTPMANAGER DELEGATES *******************************/ |
| -(void)httpManagerError:(UGHTTPManager *)manager error:(NSString *)error |
| { |
| // prep an error response |
| UGClientResponse *response = [UGClientResponse new]; |
| [response setTransactionID:[manager getTransactionID]]; |
| [response setTransactionState:kUGClientResponseFailure]; |
| [response setResponse:error]; |
| [response setRawResponse:nil]; |
| |
| if ( m_bLogging ) |
| { |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); |
| NSLog(@"Response: ERROR: %@", error); |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); |
| } |
| |
| // fire it off. Wrap in mutex locks to ensure we don't get |
| // race conditions that cause us to fire it off to Mr. Nil. |
| [m_delegateLock lock]; |
| if ( m_delegate ) |
| { |
| [m_delegate performSelector:@selector(ugClientResponse:) withObject:response]; |
| } |
| [m_delegateLock unlock]; |
| |
| // now that the callback is complete, it's safe to release this manager |
| [self releaseHTTPManager:manager]; |
| } |
| |
| -(void)httpManagerResponse:(UGHTTPManager *)manager response:(NSString *)response |
| { |
| if ( m_bLogging ) |
| { |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); |
| NSLog(@"Response (Transaction ID %d):\n%@", [manager getTransactionID], response); |
| NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); |
| } |
| |
| // form up the response |
| UGClientResponse *ugResponse = [self createResponse:[manager getTransactionID] jsonStr:response]; |
| |
| // if this is part of a multi-step call, we press on. |
| for ( int i=0 ; i<[m_pendingMultiStepActions count] ; i++ ) |
| { |
| UGMultiStepAction *action = [m_pendingMultiStepActions objectAtIndex:i]; |
| if ( [action transactionID] == [ugResponse transactionID] ) |
| { |
| // multi-step call. Fire off the action. |
| ugResponse = [self doMultiStepAction:action mostRecentResponse:ugResponse]; |
| if ( ![action reportToClient] ) |
| { |
| // the action is still pending. We do not report this |
| // to the user. We're done with the httpmanager we were using, |
| // though. |
| [self releaseHTTPManager:manager]; |
| return; |
| } |
| |
| // when the action is complete, we want to immediately break |
| // from this loop, then fall through to the normal reporting |
| // to the user. |
| break; |
| } |
| } |
| |
| // fire it off |
| [m_delegateLock lock]; |
| if ( m_delegate ) |
| { |
| [m_delegate performSelector:@selector(ugClientResponse:) withObject:ugResponse]; |
| } |
| [m_delegateLock unlock]; |
| |
| // now that the callback is complete, it's safe to release this manager |
| [self releaseHTTPManager:manager]; |
| } |
| |
| // multi-step follow-up function |
| -(UGClientResponse *)multiStepAction: (UGMultiStepAction *)action |
| { |
| // different behavior if synch or asynch |
| if ( m_delegate ) |
| { |
| // asynch. Fire it off and we're done |
| return [self doMultiStepAction:action mostRecentResponse:nil]; |
| } |
| else |
| { |
| // synchronous. keep calling until it finished or fails |
| UGClientResponse *response = nil; |
| do |
| { |
| response = [self doMultiStepAction:action mostRecentResponse:response]; |
| if ( [action reportToClient] ) |
| { |
| // done |
| return response; |
| } |
| } while ([response transactionState] == kUGClientResponseSuccess); |
| |
| // if we're here, there was an error |
| return response; |
| } |
| } |
| |
| -(UGClientResponse *)doMultiStepAction: (UGMultiStepAction *)action mostRecentResponse:(UGClientResponse *)mostRecentResponse |
| { |
| // clear the pending array of this object |
| [m_pendingMultiStepActions removeObject:action]; |
| |
| // assume we aren't reporting to the client |
| [action setReportToClient:NO]; |
| |
| if ( mostRecentResponse ) |
| { |
| // we don't care about pending responses |
| if ( [mostRecentResponse transactionState] == kUGClientResponsePending ) |
| { |
| // put ourselves back in the list |
| [m_pendingMultiStepActions addObject:action]; |
| return mostRecentResponse; |
| } |
| |
| // any failure is an immediate game ender |
| if ( [mostRecentResponse transactionState] == kUGClientResponseFailure ) |
| { |
| [mostRecentResponse setTransactionID:[action transactionID]]; |
| return mostRecentResponse; |
| } |
| } |
| |
| // if mostRecentRespons is nil, that means it's the first call to initiate |
| // the chain. So we continue on with processing. |
| |
| // so either we are reacting to a success or we are starting off the chain |
| UGClientResponse *result = nil; |
| if ( [action nextAction] == kMultiStepCreateActivity ) |
| { |
| // create the activity |
| result = [self createActivity:[action activity]]; |
| |
| // advance ourselves to the next step |
| [action setNextAction:kMultiStepPostActivity]; |
| } |
| else if ( [action nextAction] == kMultiStepCreateGroupActivity ) |
| { |
| // create the activity |
| result = [self createActivity:[action activity]]; |
| |
| // advance ourselves to the next step |
| [action setNextAction:kMultiStepPostGroupActivity]; |
| } |
| else if ( [action nextAction] == kMultiStepPostActivity ) |
| { |
| // we just created an activity, now we need to associate it with a user. |
| // first, we'll need the activity's uuid |
| NSDictionary *dict = [mostRecentResponse response]; // dictionary for the response |
| NSArray *entities = [dict objectForKey:@"entities"]; // array for the entities |
| NSDictionary *activity = [entities objectAtIndex:0]; // dict for the activity |
| NSString *activityUUID = [activity valueForKey:@"uuid"]; // and finally the uuid string |
| |
| // fire off the next step |
| result = [self postUserActivityByUUID:[action userID] activity:activityUUID]; |
| |
| // advance the action |
| [action setNextAction:kMultiStepCleanup]; |
| } |
| else if ( [action nextAction] == kMultiStepPostGroupActivity ) |
| { |
| // we just created an activity, now we need to associate it with a user. |
| // first, we'll need the activity's uuid |
| NSDictionary *dict = [mostRecentResponse response]; // dictionary for the response |
| NSArray *entities = [dict objectForKey:@"entities"]; // array for the entities |
| NSDictionary *activity = [entities objectAtIndex:0]; // dict for the activity |
| NSString *activityUUID = [activity valueForKey:@"uuid"]; // and finally the uuid string |
| |
| // fire off the next step |
| result = [self postGroupActivityByUUID:[action groupID] activity:activityUUID]; |
| |
| // advance the action |
| [action setNextAction:kMultiStepCleanup]; |
| } |
| else if ( [action nextAction] == kMultiStepCleanup ) |
| { |
| // all we do in cleanup is update the transaction ID of the |
| // response that was sent in. We do this to ensure that the transaction |
| // id is constant across the entire transaction |
| result = mostRecentResponse; |
| [result setTransactionID:[action outwardTransactionID]]; |
| [action setReportToClient:YES]; |
| } |
| |
| if ( !mostRecentResponse ) |
| { |
| // if mostRecentResponse is nil, it means we're on the first step. That means |
| // we need to adopt a unique outward transaction ID. We'll simply use |
| // the ID given back by the first transaction in the chain. This also means |
| // we can simply return the first transaction pending response without modification. |
| [action setOutwardTransactionID:[result transactionID]]; |
| } |
| |
| // wherever we landed, if it's a pending transaction, the action needs to |
| // know that transaction ID. Also, we need to go in to the pending array |
| if ( [result transactionState] == kUGClientResponsePending ) |
| { |
| [action setTransactionID:[result transactionID]]; |
| [m_pendingMultiStepActions addObject:action]; |
| } |
| |
| // result is now properly set up and ready to be handed to the user. |
| return result; |
| } |
| |
| /*************************** LOGIN / LOGOUT ****************************/ |
| /*************************** LOGIN / LOGOUT ****************************/ |
| /*************************** LOGIN / LOGOUT ****************************/ |
| -(UGClientResponse *)logInUser: (NSString *)userName password:(NSString *)password |
| { |
| return [self logIn:@"password" userKey:@"username" userValue:userName pwdKey:@"password" pwdValue:password]; |
| } |
| |
| -(UGClientResponse *)logInUserWithPin: (NSString *)userName pin:(NSString *)pin |
| { |
| return [self logIn:@"pin" userKey:@"username" userValue:userName pwdKey:@"pin" pwdValue:pin]; |
| } |
| |
| -(UGClientResponse *)logInUserWithFacebook: (NSString *)facebookToken |
| { |
| NSMutableString *url = [self createURL:@"auth/facebook"]; |
| UGQuery *query = [[UGQuery alloc] init]; |
| [query addURLTerm:@"fb_access_token" equals:facebookToken]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)logInAdmin: (NSString *)adminUserName secret:(NSString *)adminSecret |
| { |
| return [self logIn:@"client_credentials" userKey:@"client_id" userValue:adminUserName pwdKey:@"client_secret" pwdValue:adminSecret]; |
| } |
| |
| -(void)logOut |
| { |
| // clear out auth |
| [self setAuth: nil]; |
| } |
| |
| // general workhorse for auth logins |
| -(UGClientResponse *)logIn:(NSString *)grantType userKey:(NSString *)userKey userValue:(NSString *)userValue pwdKey:(NSString *)pwdKey pwdValue:(NSString *)pwdValue |
| { |
| // create the URL |
| NSString *url = [self createURL:@"token"]; |
| |
| // because it's read as form data, we need to escape special characters. |
| NSString *escapedUserValue = [UGHTTPManager escapeSpecials:userValue]; |
| NSString *escapedPwdValue = [UGHTTPManager escapeSpecials:pwdValue]; |
| |
| // create the post data. For auth functions, we don't use json, |
| // but instead use web form style data |
| NSMutableString *postData = [NSMutableString new]; |
| [postData appendFormat:@"grant_type=%@&%@=%@&%@=%@", grantType, userKey, escapedUserValue, pwdKey, escapedPwdValue]; |
| |
| // fire off the request |
| return [self httpTransaction:url op:kUGHTTPPostAuth opData:postData]; |
| } |
| /*************************** USER MANAGEMENT ***************************/ |
| /*************************** USER MANAGEMENT ***************************/ |
| /*************************** USER MANAGEMENT ***************************/ |
| -(UGClientResponse *)addUser:(NSString *)username email:(NSString *)email name:(NSString *)name password:(NSString *)password |
| { |
| // make the URL we'll be posting to |
| NSString *url = [self createURL:@"users"]; |
| |
| // make the post data we'll be sending along with it. |
| NSMutableDictionary *toPost = [NSMutableDictionary new]; |
| [toPost setObject:username forKey:@"username"]; |
| [toPost setObject:name forKey:@"name"]; |
| [toPost setObject:email forKey:@"email"]; |
| [toPost setObject:password forKey:@"password"]; |
| NSString *toPostStr = [self createJSON:toPost]; |
| |
| // fire it off |
| return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; |
| } |
| |
| // updates a user's password |
| -(UGClientResponse *)updateUserPassword:(NSString *)usernameOrEmail oldPassword:(NSString *)oldPassword newPassword:(NSString *)newPassword |
| { |
| // make the URL we'll be posting to |
| NSString *url = [self createURL:@"users" append2:usernameOrEmail append3:@"password"]; |
| |
| // make the post data we'll be sending along with it. |
| NSMutableDictionary *toPost = [NSMutableDictionary new]; |
| [toPost setObject:oldPassword forKey:@"oldpassword"]; |
| [toPost setObject:newPassword forKey:@"newpassword"]; |
| NSString *toPostStr = [self createJSON:toPost]; |
| |
| // fire it off |
| return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; |
| } |
| |
| -(UGClientResponse *)getGroupsForUser: (NSString *)userID; |
| { |
| // make the URL, and fire off the get |
| NSString *url = [self createURL:@"users" append2:userID append3:@"groups"]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)getUsers: (UGQuery *)query |
| { |
| // create the URL |
| NSMutableString *url = [self createURL:@"users"]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| /************************** ACTIVITY MANAGEMENT **************************/ |
| /************************** ACTIVITY MANAGEMENT **************************/ |
| /************************** ACTIVITY MANAGEMENT **************************/ |
| -(UGClientResponse *)createActivity: (NSDictionary *)activity |
| { |
| // make the URL |
| NSString *url = [self createURL:@"activity"]; |
| |
| // get the json to send. |
| // we have to json-ify a dictionary that was sent |
| // in by the client. So naturally, we can't just trust it |
| // to work. Therefore we can't use our internal convenience |
| // function for making the json. We go straight to SBJson, so |
| // we can identify and report any errors. |
| SBJsonWriter *writer = [SBJsonWriter new]; |
| NSError *jsonError; |
| NSString *toPostStr = [writer stringWithObject:activity error:&jsonError]; |
| |
| if ( !toPostStr ) |
| { |
| // error during json assembly |
| UGClientResponse *ret = [UGClientResponse new]; |
| [ret setTransactionState:kUGClientResponseFailure]; |
| [ret setTransactionID:-1]; |
| [ret setResponse:[jsonError localizedDescription]]; |
| [ret setRawResponse:nil]; |
| return ret; |
| } |
| |
| // fire it off |
| return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; |
| } |
| |
| // create an activity and post it to a user in a single step |
| -(UGClientResponse *)postUserActivity: (NSString *)userID activity:(NSDictionary *)activity |
| { |
| // prep a multi-step action |
| UGMultiStepAction *action = [UGMultiStepAction new]; |
| |
| // set it up to start the create activity / post to user chain |
| [action setNextAction:kMultiStepCreateActivity]; |
| [action setUserID:userID]; |
| [action setActivity:activity]; |
| |
| // fire it off |
| return [self multiStepAction:action]; |
| } |
| |
| -(UGClientResponse *)postUserActivityByUUID: (NSString *)userID activity:(NSString *)activityUUID |
| { |
| // make the URL and fire off the post. there is no data |
| NSString *url = [self createURL:@"users" append2:userID append3:@"activities" append4:activityUUID]; |
| return [self httpTransaction:url op:kUGHTTPPost opData:nil]; |
| } |
| |
| -(UGClientResponse *)postGroupActivity:(NSString *)groupID activity:(NSDictionary *)activity |
| { |
| // prep a multi-step action |
| UGMultiStepAction *action = [UGMultiStepAction new]; |
| |
| // set it up to start the create activity / post to user chain |
| [action setNextAction:kMultiStepCreateGroupActivity]; |
| [action setGroupID:groupID]; |
| [action setActivity:activity]; |
| |
| // fire it off |
| return [self multiStepAction:action]; |
| } |
| |
| -(UGClientResponse *)postGroupActivityByUUID: (NSString *)groupID activity:(NSString *)activityUUID |
| { |
| // make the URL and fire off the post. there is no data |
| NSString *url = [self createURL:@"groups" append2:groupID append3:@"activities" append4:activityUUID]; |
| return [self httpTransaction:url op:kUGHTTPPost opData:nil]; |
| } |
| |
| -(UGClientResponse *)getActivitiesForUser: (NSString *)userID query:(UGQuery *)query |
| { |
| NSMutableString *url = [self createURL:@"users" append2:userID append3:@"activities"]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)getActivityFeedForUser: (NSString *)userID query:(UGQuery *)query |
| { |
| NSMutableString *url = [self createURL:@"users" append2:userID append3:@"feed"]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)getActivitiesForGroup: (NSString *)groupID query:(UGQuery *)query |
| { |
| NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"activities"]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)getActivityFeedForGroup: (NSString *)groupID query:(UGQuery *)query |
| { |
| NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"feed"]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)removeActivity:(NSString *)activityUUID |
| { |
| NSString *url = [self createURL:@"activities" append2:activityUUID]; |
| return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; |
| } |
| |
| /************************** GROUP MANAGEMENT **************************/ |
| /************************** GROUP MANAGEMENT **************************/ |
| /************************** GROUP MANAGEMENT **************************/ |
| -(UGClientResponse *)createGroup:(NSString *)groupPath groupTitle:(NSString *)groupTitle |
| { |
| // make the URL |
| NSString *url = [self createURL:@"groups"]; |
| |
| // make the post data we'll be sending along with it. |
| NSMutableDictionary *toPost = [NSMutableDictionary new]; |
| [toPost setObject:groupPath forKey:@"path"]; |
| if ( groupTitle ) |
| { |
| [toPost setObject:groupTitle forKey:@"title"]; |
| } |
| NSString *toPostStr = [self createJSON:toPost]; |
| |
| // fire it off |
| return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; |
| } |
| |
| -(UGClientResponse *)addUserToGroup:(NSString *)userID group:(NSString *)groupID |
| { |
| // make the URL |
| NSString *url = [self createURL:@"groups" append2:groupID append3:@"users" append4:userID]; |
| |
| // fire it off. This is a data-less POST |
| return [self httpTransaction:url op:kUGHTTPPost opData:nil]; |
| } |
| |
| -(UGClientResponse *)removeUserFromGroup:(NSString *)userID group:(NSString *)groupID |
| { |
| // this is identical to addUserToGroup, except we use the DELETE method instead of POST |
| // make the URL |
| NSString *url = [self createURL:@"groups" append2:groupID append3:@"users" append4:userID]; |
| |
| // fire it off. This is a data-less POST |
| return [self httpTransaction:url op:kUGHTTPDelete opData:nil];} |
| |
| -(UGClientResponse *)getUsersForGroup:(NSString *)groupID query:(UGQuery *)query |
| { |
| // create the URL |
| NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"users"]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| /************************** ENTITY MANAGEMENT **************************/ |
| /************************** ENTITY MANAGEMENT **************************/ |
| /************************** ENTITY MANAGEMENT **************************/ |
| // jsonify the entity. If there's an error, it creates a UGClientResponse and |
| // returns it. If there's no error, it returns nil, and the outJson field and |
| // the type field will be set correctly. |
| // yes, it's odd to have a function return nil on success, but it's internal. |
| -(UGClientResponse *)validateEntity:(NSDictionary *)newEntity outJson:(NSString **)jsonStr outType:(NSString **)type |
| { |
| // validation |
| NSString *error = nil; |
| |
| // the entity must exist |
| if ( !newEntity ) |
| { |
| error =@"entity is nil"; |
| } |
| |
| // the entity must have a "type" field |
| *type = [newEntity valueForKey:@"type"]; |
| if ( !*type ) |
| { |
| error = @"entity is missing a type field"; |
| } |
| |
| // make sure it can parse to a json |
| SBJsonWriter *writer = [SBJsonWriter new]; |
| NSError *jsonError; |
| *jsonStr = [writer stringWithObject:newEntity error:&jsonError]; |
| if ( !*jsonStr ) |
| { |
| error = [jsonError localizedDescription]; |
| } |
| |
| // if error got set to anything, it means we failed |
| if ( error ) |
| { |
| UGClientResponse *ret = [UGClientResponse new]; |
| [ret setTransactionState:kUGClientResponseFailure]; |
| [ret setTransactionID:-1]; |
| [ret setResponse:error]; |
| [ret setRawResponse:nil]; |
| return ret; |
| } |
| |
| // if we're here, it's a good json and we're done |
| return nil; |
| } |
| |
| -(UGClientResponse *)createEntity:(NSDictionary *)newEntity |
| { |
| NSString *jsonStr; |
| NSString *type; |
| UGClientResponse *errorRet = [self validateEntity:newEntity outJson:&jsonStr outType:&type]; |
| if ( errorRet ) return errorRet; |
| |
| // we have a valid entity, ready to post. Make the URL |
| NSString *url = [self createURL:type]; |
| |
| // post it |
| return [self httpTransaction:url op:kUGHTTPPost opData:jsonStr]; |
| } |
| |
| -(UGClientResponse *)getEntities: (NSString *)type query:(UGQuery *)query |
| { |
| NSMutableString *url = [self createURL:type]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)updateEntity: (NSString *)entityID entity:(NSDictionary *)updatedEntity |
| { |
| NSString *jsonStr; |
| NSString *type; |
| UGClientResponse *errorRet = [self validateEntity:updatedEntity outJson:&jsonStr outType:&type]; |
| if ( errorRet ) return errorRet; |
| |
| // we have a valid entity, ready to post. Make the URL |
| NSString *url = [self createURL:type append2:entityID]; |
| |
| // post it |
| return [self httpTransaction:url op:kUGHTTPPut opData:jsonStr]; |
| } |
| |
| -(UGClientResponse *)removeEntity: (NSString *)type entityID:(NSString *)entityID |
| { |
| // Make the URL, then fire off the delete |
| NSString *url = [self createURL:type append2:entityID]; |
| return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; |
| } |
| |
| |
| -(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType connecteeType:(NSString *)connecteeType connecteeID:(NSString *)connecteeID |
| { |
| NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeType append5:connecteeID]; |
| return [self httpTransaction:url op:kUGHTTPPost opData:nil]; |
| } |
| |
| -(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID |
| { |
| NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeID]; |
| return [self httpTransaction:url op:kUGHTTPPost opData:nil]; |
| } |
| |
| -(UGClientResponse *)disconnectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID |
| { |
| NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeID]; |
| return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; |
| } |
| |
| -(UGClientResponse *)getEntityConnections: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType query:(UGQuery *)query |
| { |
| NSMutableString *url = [self createURL:connectorType append2:connectorID append3:connectionType]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPPost opData:nil]; |
| } |
| |
| /************************** MESSAGE MANAGEMENT **************************/ |
| /************************** MESSAGE MANAGEMENT **************************/ |
| /************************** MESSAGE MANAGEMENT **************************/ |
| -(UGClientResponse *)postMessage: (NSString *)queuePath message:(NSDictionary *)message |
| { |
| // because the NSDictionary is from the client, we can't trust it. We need |
| // to go through full error checking |
| NSString *error; |
| NSString *jsonStr = [self createJSON:message error:&error]; |
| |
| if ( !jsonStr ) |
| { |
| // report the error |
| UGClientResponse *ret = [UGClientResponse new]; |
| [ret setTransactionID:-1]; |
| [ret setTransactionState:kUGClientResponseFailure]; |
| [ret setResponse:error]; |
| [ret setRawResponse:nil]; |
| return ret; |
| } |
| |
| // make the path and fire it off |
| NSString *url = [self createURL:@"queues" append2:queuePath]; |
| return [self httpTransaction:url op:kUGHTTPPost opData:jsonStr]; |
| } |
| |
| -(UGClientResponse *)getMessages: (NSString *)queuePath query:(UGQuery *)query; |
| { |
| NSMutableString *url = [self createURL:@"queues" append2:queuePath]; |
| [self appendQueryToURL:url query:query]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| -(UGClientResponse *)addSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath |
| { |
| NSString *url = [self createURL:@"queues" append2:queuePath append3:@"subscribers" append4:subscriberPath]; |
| return [self httpTransaction:url op:kUGHTTPPost opData:nil]; |
| } |
| |
| -(UGClientResponse *)removeSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath |
| { |
| NSString *url = [self createURL:@"queues" append2:queuePath append3:@"subscribers" append4:subscriberPath]; |
| return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; |
| } |
| |
| /*************************** REMOTE PUSH NOTIFICATIONS ***************************/ |
| /*************************** REMOTE PUSH NOTIFICATIONS ***************************/ |
| /*************************** REMOTE PUSH NOTIFICATIONS ***************************/ |
| |
| - (UGClientResponse *)setDevicePushToken:(NSData *)newDeviceToken forNotifier:(NSString *)notifier |
| { |
| // Pull the push token string out of the device token data |
| NSString *tokenString = [[[newDeviceToken description] |
| stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] |
| stringByReplacingOccurrencesOfString:@" " withString:@""]; |
| |
| // Register device and push token to App Services |
| NSString *deviceId = [UGClient getUniqueDeviceID]; |
| |
| // create/update device - use deviceId for App Services entity UUID |
| NSMutableDictionary *entity = [[NSMutableDictionary alloc] init]; |
| [entity setObject: @"device" forKey: @"type"]; |
| [entity setObject: deviceId forKey: @"uuid"]; |
| |
| NSString *notifierKey = [notifier stringByAppendingString: @".notifier.id"]; |
| [entity setObject: tokenString forKey: notifierKey]; |
| |
| return [self updateEntity: deviceId entity: entity]; |
| } |
| |
| - (UGClientResponse *)pushAlert:(NSString *)message |
| withSound:(NSString *)sound |
| to:(NSString *)path |
| usingNotifier:(NSString *)notifier |
| { |
| NSDictionary *apsDict = [NSDictionary dictionaryWithObjectsAndKeys: |
| message, @"alert", |
| sound, @"sound", |
| nil]; |
| |
| NSDictionary *notifierDict = [NSDictionary dictionaryWithObjectsAndKeys: |
| apsDict, @"aps", |
| nil]; |
| |
| NSDictionary *payloadsDict = [NSDictionary dictionaryWithObjectsAndKeys: |
| notifierDict, notifier, |
| nil]; |
| |
| NSString *notificationsPath = [path stringByAppendingString: @"/notifications"]; |
| |
| NSMutableDictionary *entity = [[NSMutableDictionary alloc] init]; |
| [entity setObject: notificationsPath forKey: @"type"]; |
| [entity setObject: payloadsDict forKey: @"payloads"]; |
| |
| return [self createEntity: entity]; |
| } |
| |
| |
| /*************************** SERVER-SIDE STORAGE ***************************/ |
| /*************************** SERVER-SIDE STORAGE ***************************/ |
| /*************************** SERVER-SIDE STORAGE ***************************/ |
| +(NSString *)getUniqueDeviceID |
| { |
| // cached? |
| if (g_deviceUUID) return g_deviceUUID; |
| |
| // in our keychain? |
| g_deviceUUID = [SSKeychain passwordForService:@"Usergrid" account:@"DeviceUUID"]; |
| if (g_deviceUUID) return g_deviceUUID; |
| |
| // in the (legacy) app defaults? |
| NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; |
| g_deviceUUID = [defaults valueForKey:@"UGClientDeviceUUID"]; |
| |
| // if none found in storage, generate one |
| if (!g_deviceUUID) { |
| |
| if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")) { |
| // use identifierForVendor where possible |
| g_deviceUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; |
| } |
| else { |
| // otherwise, create a UUID (legacy method) |
| CFUUIDRef uuidRef = CFUUIDCreate(nil); |
| CFStringRef uuidStringRef = CFUUIDCreateString(nil, uuidRef); |
| CFRelease(uuidRef); |
| g_deviceUUID = [NSString stringWithString:(__bridge NSString *)uuidStringRef]; |
| } |
| } |
| |
| // store in keychain for future reference |
| [SSKeychain setPassword:g_deviceUUID forService:@"Usergrid" account:@"DeviceUUID"]; |
| |
| return g_deviceUUID; |
| } |
| |
| -(UGClientResponse *)setRemoteStorage: (NSDictionary *)data |
| { |
| // prep and validate the sent-in dict |
| NSString *error; |
| NSString *jsonStr = [self createJSON:data error:&error]; |
| if ( !jsonStr ) |
| { |
| // report the error |
| UGClientResponse *ret = [UGClientResponse new]; |
| [ret setTransactionID:-1]; |
| [ret setTransactionState:kUGClientResponseFailure]; |
| [ret setResponse:error]; |
| [ret setRawResponse:nil]; |
| return ret; |
| } |
| |
| NSString *handsetUUID = [UGClient getUniqueDeviceID]; |
| NSString *url = [self createURL:@"devices" append2:handsetUUID]; |
| |
| // this is a put. We replace whatever was there before |
| return [self httpTransaction:url op:kUGHTTPPut opData:jsonStr]; |
| } |
| |
| -(UGClientResponse *)getRemoteStorage |
| { |
| NSString *handsetUUID = [UGClient getUniqueDeviceID]; |
| NSString *url = [self createURL:@"devices" append2:handsetUUID]; |
| return [self httpTransaction:url op:kUGHTTPGet opData:nil]; |
| } |
| |
| /***************************** OBLIQUE USAGE ******************************/ |
| -(UGClientResponse *)apiRequest: (NSString *)url operation:(NSString *)op data:(NSString *)opData |
| { |
| // work out the op to use |
| int opID = kUGHTTPGet; |
| if ( [op isEqualToString:@"GET"] ) opID = kUGHTTPGet; |
| if ( [op isEqualToString:@"POST"] ) opID = kUGHTTPPost; |
| if ( [op isEqualToString:@"POSTFORM"] ) opID = kUGHTTPPostAuth; |
| if ( [op isEqualToString:@"PUT"] ) opID = kUGHTTPPut; |
| if ( [op isEqualToString:@"DELETE"] ) opID = kUGHTTPDelete; |
| |
| // fire it off. The data, formatting, etc. is all the client's problem. |
| // That's the way oblique functionality is. |
| return [self httpTransaction:url op:opID opData:opData]; |
| } |
| |
| /**************************** LOGGING ************************************/ |
| -(void)setLogging: (BOOL)loggingState |
| { |
| m_bLogging = loggingState; |
| } |
| |
| @end |
| |
| |