Skip to content

Instantly share code, notes, and snippets.

@cachafla
Last active February 14, 2018 23:55
Show Gist options
  • Save cachafla/30a9d40124c32934b310e8c66fa922c0 to your computer and use it in GitHub Desktop.
Save cachafla/30a9d40124c32934b310e8c66fa922c0 to your computer and use it in GitHub Desktop.
Updating iOS SDK implementation to support Google Fit

Given that some of our data providers have started banning the use of WebViews (on iOS and Android), we are encouraging our customers to update their SDK implementations to use an instance of their native browsers instead of creating browser view objects. In the example below we show how to integrate Connect by using a SFSafariViewController instead of a UIWebView.

Given that SFSafariViewController instances cannot be manipulated like UIWebViews, we need to make use of URL redirection when integrating Connect. This means that we have to opt for a simpler integration on the mobile side but customers need to update their server side callback URLs to support an HTTP GET method instead. In this case, Connect will be automatically redirecting the user to a customer hosted URL that should process the newly created token and redirect the user back to the customer app.

This is the list of steps customers should follow in order to update their SDK implementations to support SFSafariViewController and URL redirection:

Import HumanConnect class file into your project

This is a new class that handles generating a Connect URL for any user given their clientUserId and their publicToken. It doens't provide any other functionality at the moment (see attached files).

Make the following changes in the file where you want to launch Connect from

Require HumanConnect and SafariServices:

#import "HumanConnect.h"
@import SafariServices;

Ensure your view controller implements the SFSafariViewControllerDelegate protocol:

@interface ViewController : UIViewController <SFSafariViewControllerDelegate>

In your view controller's viewDidLoad method, register a new observer that will listen to external notifcations when a user has successfully finished a Connect interaction (i.e. after the token exchange is completed on your server):

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(completeConnection:) name:@"HumanAPIConnection" object:nil];

Inside the method where you launch Connect on your view controller instance:

// Register your server-side callback URL to finalize authentication on your server
NSString *callbackURL = @"http://127.0.0.1:8080/sdk-test";

// Generate a Connect URL with HumanConnect
HumanConnect *connect = [[HumanConnect alloc] initWithClientID:myClientID andCallbackURL:callbackURL];
connect.options = [NSDictionary dictionaryWithObjectsAndKeys:
                       @"en",@"language",
                       nil];

NSURL *connectUrl = [connect generateURLForNewUser:@"andres-test"];

// And present Connect to the user
self.connectWindow = [[SFSafariViewController alloc] initWithURL:connectUrl];
self.connectWindow.delegate = self;
    
[self presentViewController:self.connectWindow animated:YES completion:nil];

Now, when the user finalizes their Connect session, they will be redirected to the callback URL you registered above. This is where you will now have to make a change to your implementation. The next step in this process is to redirect your user back to your own app and allow Connect to be closed gracefully. This can be achieved by doing the following:

Redirect user to your app by issuing a redirect with the following URL format from your server:

// Redirect to a URL with the 'HumanAPIAuthDemo' protocol
"HumanAPIAuthDemo://co.my.company?" + queryString;

// Where queryString are any values you want to pass back to your app.

Ensure your app handles the application:openURL method on the delegate:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    if ([sourceApplication isEqualToString:@"com.apple.SafariViewService"]) {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"HumanAPIConnection" object:url];
    }
    
    return YES;
}

Given that you already setup an observer on your view controller class, completeNotification: handler should take care of gracefully closing the SFSafariViewController instance:

- (void)completeConnection:(NSNotification *)notification
{
    NSLog(@"User has completed authorization process! Popup is going to be automatically closed.");
    [self.connectWindow dismissViewControllerAnimated:YES completion:nil];
}

Attached you can find some sample code that implements the integration steps defined above.

#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
if ([sourceApplication isEqualToString:@"com.apple.SafariViewService"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"HumanAPIConnection" object:url];
}
return YES;
}
@end
#import <UIKit/UIKit.h>
@interface HumanConnect : NSObject
@property NSString *clientID;
@property NSString *callbackURL;
@property NSDictionary *options;
- (id)initWithClientID:(NSString *)cliendID andCallbackURL:(NSString *)callbackURL;
- (NSURL *)generateURLForNewUser:(NSString *)userId;
- (NSURL *)generateURLFor:(NSString *)userId andPublicToken:(NSString *)publicToken;
@end
#import "HumanConnect.h"
@implementation HumanConnect
NSString *ConnectURL = @"https://connect.humanapi.co";
- (id)initWithClientID:(NSString *)clientID andCallbackURL:(NSString *)callbackURL
{
self = [super init];
self.clientID = clientID;
self.callbackURL = callbackURL;
return self;
}
- (NSURL *)generateUrlWithOptions:(NSDictionary *)params
{
NSMutableDictionary *allParams = [[NSMutableDictionary alloc] init];
[allParams addEntriesFromDictionary:params];
[allParams addEntriesFromDictionary:self.options];
NSString *paramsString = [NSString stringWithFormat:@"/embed?finish_url=%@&close_url=%@",
self.callbackURL,
self.callbackURL];
for(id key in allParams) {
paramsString = [paramsString stringByAppendingString:[NSString stringWithFormat: @"&%@=%@",
key,
[allParams objectForKey:key]]];
}
NSURL *url = [NSURL URLWithString:[ConnectURL stringByAppendingString:paramsString]];
return url;
}
- (NSURL *)generateURLForNewUser:(NSString *)userId
{
return [self generateUrlWithOptions:[NSDictionary dictionaryWithObjectsAndKeys:
self.clientID, @"clientId", userId, @"clientUserId", nil]];
}
- (NSURL *)generateURLFor:(NSString *)userId andPublicToken:(NSString *)publicToken
{
return [self generateUrlWithOptions:[NSDictionary dictionaryWithObjectsAndKeys:
self.clientID, @"clientId", userId, @"clientUserId", publicToken, @"publicToken", nil]];
}
@end
#import <UIKit/UIKit.h>
#import "HumanConnect.h"
@import SafariServices;
@interface ViewController : UIViewController <HumanAPINotifications, SFSafariViewControllerDelegate>
@property SFSafariViewController *connectWindow;
@end
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(completeConnection:) name:@"HumanAPIConnection" object:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (IBAction)launchHumanConnect:(id)sender {
NSString *myClientID = @"my-client-id"; //From the Developer Portal
// URL to send sessionTokenObject (finalize authentication on your server, replace with your server URL)
NSString *callbackURL = @"http://127.0.0.1:8080/connect-finish";
// Custom Connect options if needed
HumanConnect *connect = [[HumanConnect alloc] initWithClientID:myClientID andCallbackURL:callbackURL];
connect.options = [NSDictionary dictionaryWithObjectsAndKeys:
@"en",@"language",
@"wellness",@"mode",
nil];
// The only step required is to generate a NSURL that you can feed to SFSafariViewController
NSURL *connectUrl;
// For demo purposes -- with local user accounts, use code below instead
connectUrl = [connect generateURLForNewUser:@"andres-test"];
// This URL should be accessible from an browser
NSLog(@"Connect URL for user: %@", connectUrl);
self.connectWindow = [[SFSafariViewController alloc] initWithURL:connectUrl];
self.connectWindow.delegate = self;
[self presentViewController:self.connectWindow animated:YES completion:nil];
}
- (void)completeConnection:(NSNotification *)notification
{
NSLog(@"User has completed authorization process! Popup is going to be automatically closed.");
[self.connectWindow dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - SFSafariViewControllerDelegate
- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller
{
NSLog(@"User closed Connect window");
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment