sdks/other/ios/UGAPI/UGClient.m (816 lines of code) (raw):

/* * 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