The SparkAPI
object is designed as a standalone Objective-C interface for use with the Spark API. It implements Spark authentication via the Hybrid or OpenID methods. API calls per HTTP method provide a high-level Spark API interface and return a JSON results array on success while handling errors like session expiration for the client.
This project includes an example iPad and iPhone app that makes use of SparkAPI
object to authenticate via Hybrid or OpenID methods, search listings, view listings, view an individual listing with photos and standard fields, and view a user account. View app [screenshots](./Spark iOS Screenshots.pdf) for iPad and iPhone.
- iOS 5 or later
- XCode IDE
- Running on an iOS device requires an active Apple Developer account
Once you register as a Spark developer and receive your Spark Client Id and Client Secret, open the SparkAPI.m file and set the sparkClientId
and sparkClientSecret
class variables. You must also set the sparkAPIUserAgent
with the name of your app or your API requests will not be accepted. The sparkCallbackURL
can also be customized but you most likely will want to use the default value to start.
@implementation SparkAPI
// configuration ***************************************************************
static NSString* sparkClientKey = @"<YOUR OAUTH2 CLIENT KEY>";
static NSString* sparkClientSecret = @"<YOUR OAUTH2 CLIENT SECRET>";
static NSString* sparkAPIUserAgent = nil; // set or your API requests will not be successful
static NSString* sparkCallbackURL = @"https://sparkplatform.com/oauth2/callback";
The SparkAPI
object is designed to work with UIWebView
and UIWebViewDelegate
objects to initiate and process Spark authentication.
Initiating an Authentication Request:
-
To initiate a Hybrid authentication request, encapsulate the
getSparkHybridOpenIdURL
in aNSURLRequest
and callUIWebView loadRequest:
. -
To initiate an OpenID authentication request, encapsulate the
getSparkOpenIdURL
orgetSparkOpenIdAttributeExchangeURL
in aNSURLRequest
and callUIWebView loadRequest:
.
Processing Authentication:
SparkAPI provides two class methods for processing authentication and returning a SparkAPI object upon success:
- hybridAuthenticate implements the Spark OpenID+OAuth 2 Hybrid Protocol.
- openIdAuthenticate implements the Spark OpenID Simple Registration Extension or OpenID Attribute Exchange Extension.
Both utilize callback blocks that receive asynchronous responses from the Spark API on success or failure.
+ (BOOL) hybridAuthenticate:(NSURLRequest*)request
success:(void(^)(SparkAPI* sparkAPI))success
failure:(void(^)(NSString* openIdMode, NSString* openIdError, NSError *httpError))failure;
+ (BOOL) openIdAuthenticate:(NSURLRequest*)request
success:(void(^)(SparkAPI* sparkAPI, NSDictionary* parameters))success
failure:(void(^)(NSString* openIdMode, NSString* openIdError))failure;
These authentication methods are typically placed in a UIWebViewDelegate object to respond to a NSURLRequest generated after the user provides their Spark credentials. See LoginViewController.m for an example.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if([SparkAPI hybridAuthenticate:request
success:^(SparkAPI *sparkAPI) {
[self processAuthentication:sparkAPI parameters:nil];
}
failure:^(NSString* openIdMode, NSString* openIdError, NSError *httpError) {
NSString* message = nil;
if(openIdMode)
message = [self handleOpenIdError:openIdMode openIdError:openIdError];
[UIHelper handleFailure:message error:httpError];
}])
return NO;
else
return YES;
}
Once an authenticated SparkAPI
object is instantiated, api methods corresponding to the four HTTP methods are available as well as general api
method. Similar to the authentication methods, all utilize callback blocks that receive asynchronous responses from the Spark API on success or failure.
On success, the results JSON array is parsed from the Spark response object and provided as an argument to the success block.
On failure, sparkErrorCode
and sparkErrorMessage
are parsed from the returned JSON and provided as arguments to the failure block.
Session renewal is handled automatically by the SparkAPI
object when a session token expire error code is returned by the API.
- (void) get:(NSString*)apiCommand
parameters:(NSDictionary*)parameters
success:(void(^)(NSArray *resultsJSON))success
failure:(void(^)(NSInteger sparkErrorCode,
NSString* sparkErrorMessage,
NSError *httpError))failure;
- (void) post:(NSString*)apiCommand
parameters:(NSDictionary*)parameters
success:(void(^)(NSArray *resultsJSON))success
failure:(void(^)(NSInteger sparkErrorCode,
NSString* sparkErrorMessage,
NSError *httpError))failure;
- (void) put:(NSString*)apiCommand
parameters:(NSDictionary*)parameters
success:(void(^)(NSArray *resultsJSON))success
failure:(void(^)(NSInteger sparkErrorCode,
NSString* sparkErrorMessage,
NSError *httpError))failure;
- (void) delete:(NSString*)apiCommand
parameters:(NSDictionary*)parameters
success:(void(^)(NSArray *resultsJSON))success
failure:(void(^)(NSInteger sparkErrorCode,
NSString* sparkErrorMessage,
NSError *httpError))failure;
- (void) api:(NSString*)apiCommand
httpMethod:(NSString*)httpMethod
parameters:(NSDictionary*)parameters
success:(void(^)(NSArray *resultsJSON))success
failure:(void(^)(NSInteger sparkErrorCode,
NSString* sparkErrorMessage,
NSError *httpError))failure;
Below is an example API call to the /my/account
Spark API endpoint from the example app. On success, the table view interface is updated. On failure, an alert view is presented to the user.
[sparkAPI get:@"/my/account"
parameters:nil
success:^(NSArray *resultsJSON) {
if(resultsJSON && [resultsJSON count] > 0)
{
self.myAccountJSON = [resultsJSON objectAtIndex:0];
[self.tableView reloadData];
[self.activityView stopAnimating];
}
}
failure:^(NSInteger sparkErrorCode,
NSString* sparkErrorMessage,
NSError *httpError) {
[self.activityView stopAnimating];
[UIHelper handleFailure:self code:sparkErrorCode message:sparkErrorMessage error:httpError];
}];
The SparkAPI
object contains basic log level metering to control output of log messages to the console. By default, the logLevel
property is set to SPARK_LOG_LEVEL_INFO
to output each API call to the console. To output only errors to the console, call [SparkAPI setLogLevel:SPARK_LOG_LEVEL_ERROR]
.
The example app provides a great starting point for building your own Spark-powered iOS app. At a minimum, the core authentication features encapsulated by LoginViewController
can be repurposed.
In your AppDelegate
didFinishLaunchingWithOptions
method, you will need code similar to below that reads any saved tokens and bypasses Login if the session is valid to show your home ViewController
. If the session is not valid, the LoginViewController
is presented.
PDKeychainBindings *keychain = [PDKeychainBindings sharedKeychainBindings];
NSString* accessToken = [keychain objectForKey:SPARK_ACCESS_TOKEN];
NSString* refreshToken = [keychain objectForKey:SPARK_REFRESH_TOKEN];
NSString* openIdSparkid = [keychain objectForKey:SPARK_OPENID];
UIViewController *vc = nil;
if((accessToken && refreshToken) || openIdSparkid)
{
self.sparkAPI = [[SparkAPI alloc] initWithAccessToken:accessToken
refreshToken:refreshToken
openId:openIdSparkid];
vc = [UIHelper getHomeViewController];
}
else
{
vc = [[LoginViewController alloc] initWithNibName:([UIHelper iPhone] ? @"LoginViewController" : @"LoginViewController-iPad")
bundle:nil];
vc.title = @"Login";
}
UIViewController *rootVC = nil;
if([UIHelper iPhone])
rootVC = [UIHelper getNavigationController:vc];
else
rootVC = [vc isKindOfClass:[LoginViewController class]] ? vc : [UIHelper getSplitViewController];
In LoginViewController
, the processAuthentication
method should also be modified to save any session state (securely to Keychain or to Core Data) as well as redirect the user to the top ViewController
.
AppDelegate *appDelegate = ((AppDelegate*)[[UIApplication sharedApplication] delegate]);
appDelegate.sparkAPI = sparkAPI;
PDKeychainBindings *keychain = [PDKeychainBindings sharedKeychainBindings];
if(sparkAPI.oauthAccessToken)
[keychain setObject:sparkAPI.oauthAccessToken forKey:SPARK_ACCESS_TOKEN];
if(sparkAPI.oauthRefreshToken)
[keychain setObject:sparkAPI.oauthRefreshToken forKey:SPARK_REFRESH_TOKEN];
if([UIHelper iPhone])
[self.navigationController setViewControllers:[NSArray arrayWithObject:[UIHelper getHomeViewController]]
animated:YES];
else
{
AppDelegate* appDelegate = [UIHelper getAppDelegate];
appDelegate.window.rootViewController = [UIHelper getSplitViewController];
}
Tested OSs: iOS 6, iOS 5
Tested XCode versions: 4.5
Tested Devices: iPad 3, iPad 2, iPad mini, iPad 1, iPhone 5, iPhone 4