In my last post I talked about the new direction for the SDK. While I am still trying to get that done, BUGS have been filed and changes have been made. So today I am going to talk about one of the more involved changes that will affect how the SDK is used.

 

For most Jive instances everything works as before. But there are 2 situations where things need to change.

 

The first situation is url redirection. There are cases where you need to manually update the URL used to connect to the instance. This is most commonly found in internal networks where IT has provided a short name that resolves to the full URL. Both are accessible but only the full URL should be used in @mentions and other links.

As part of the instance validation the server sends the URL that should be used. If this URL is different than the entered URL you need to test to see if it can be reached.

 

Which leads to the second situation. In this case the server wants you to use a different URL but it is not reachable. In this case the URLs returned by the server should be updated to match the URL entered. This scenario is handled by the SDK once you determine that the redirect cannot be done. This most commonly happens with large instances that need a load balancer. The servers can be configured to include an instance identifier. For the average user this identifier needs to be removed, and the SDK handles this.

 

When I created the Example project I skipped the version check because I hard coded the instance URL. To demonstrate what is needed now I have added a view for the user to enter the instance URL. I then validate it and transition to the original login view.

Here is the validation code:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    // Assume the default instance.
    NSString *instanceString = @"https://community.jivesoftware.com/";
    // Check to see if the user entered a different url.
    if (self.communityURL.text.length > 0) {
        instanceString = self.communityURL.text;
        if (![instanceString hasPrefix:@"http"]) {
            instanceString = [@"http://" stringByAppendingString:instanceString];
            // But what if it should be https:// ?
        }
        // The SDK assumes the URL has a / at the end. So make sure it does.
        if (![instanceString hasSuffix:@"/"]) {
            instanceString = [instanceString stringByAppendingString:@"/"];
        }
    }

Here I do simple formatting of the entered value. Make sure it has the correct prefix and suffix. You should validate both http and https prefixes, preferring the https version if they both work.

    NSURL *instanceURL = [NSURL URLWithString:instanceString];
    __block JVJiveFactory *factory = nil;
    [self.activityIndicator startAnimating];
    [self.communityURL resignFirstResponder];
    self.communityURL.enabled = NO;
    // Is it a valid instance?
    factory = [[JVJiveFactory alloc] initWithInstanceURL:instanceURL
                                                complete:^(JivePlatformVersion *version) {
                                                    [self checkForRedirect:version
                                                                   fromURL:instanceURL
                                                                   factory:factory];
                                                } error:^(NSError *error) {
                                                    [self.activityIndicator stopAnimating];
                                                    self.communityURL.enabled = YES;
                                                    [self.communityURL becomeFirstResponder];
                                                }];  
    return NO;
}

Then I create the jive factory class with blocks to handle the version check results. The simple interface elements are to disable user interaction while performing the check. If the check fails, by returning an error, I reenable interaction. You may want to do more to better inform the user of what happened. Alternatively you could dynamically update the GO button on the keyboard as the user types. If the check succeeds we proceed to the first redirect check.

- (id)initWithInstanceURL:(NSURL *)instanceURL
                 complete:(JivePlatformVersionBlock)completeBlock
                    error:(JiveErrorBlock)errorBlock {
    self = [super init];
    if (self) {
        self.jive = [[Jive alloc] initWithJiveInstance:instanceURL
                                 authorizationDelegate:self];
        [self.jive versionForInstance:instanceURL
                           onComplete:completeBlock
                              onError:errorBlock];
    }

    return self;
}

As part of creating the jive factory class I create a Jive object and start the version check.

 

- (void)checkForRedirect:(JivePlatformVersion *)version
                 fromURL:(NSURL *)targetURL
                 factory:(JVJiveFactory *)initialFactory {
    // Not all instances report their url in the version.
    if (!version.instanceURL || [version.instanceURL isEqual:targetURL]) {
        [self advanceToLogin:initialFactory];
    } else {
        // Attempt to redirect to the server's instance url.
        __block JVJiveFactory *factory = nil;
        factory = [[JVJiveFactory alloc] initWithInstanceURL:version.instanceURL
                                                    complete:^(JivePlatformVersion *redirectVersion) {
                                                        // Direct access granted.
                                                        self.communityURL.text = redirectVersion.instanceURL.absoluteString;
                                                        [self advanceToLogin:factory];
                                                    }
                                                       error:^(NSError *error) {
                                                           // The server lied, bad server. Use the original url.
                                                           [self advanceToLogin:initialFactory];
                                                       }];
    }
}

There are 4 possibilities here: the server reported the same url that the user entered, the server didn't report a url, the server reported a different url that should be used instead of the user entered url, and finally the server requires the SDK to rewrite the urls.

In the first 2 cases we just go to the login screen normally. For the last 2 cases we need to check the server reported URL to distinguish them, by creating a second JVJiveFactory with the server reported URL. If this second check succeeds we use the server reported URL as if the user had entered it instead. Otherwise we continue with the user entered URL.

 

- (void)advanceToLogin:(JVJiveFactory *)factory {
    [self.activityIndicator stopAnimating];
    [JVJiveFactory setInstance:factory];
    [self performSegueWithIdentifier:@"Community" sender:self];
}

Here we update the UI and set the jive factory instance to the validated factory object.

 

The second redirect check happens after the user enters his credentials and tries to login. You need to change the completeBlock passed to the Jive me:onError: method.

In the example this is in the JVJiveFactory loginWithName:password:complete:error: method.

    [self.jive me:^(JivePerson *me) {
        JivePlatformVersion *platformVersion = self.jive.platformVersion;
    
        // url check.
        if (platformVersion.instanceURL) {
            // It's all good.
            if (completeBlock) {
                completeBlock(me);
            }
        } else {
            // NO!!! We have to make sure we have the right URL.
            [self doubleCheckInstanceURLForMe:me onComplete:completeBlock];
        }
    }

The new completeBlock checks to see if the version already includes the instance URL. If not it does another call to the server to get the instance URL.

 

- (void)doubleCheckInstanceURLForMe:(JivePerson *)me
                         onComplete:(JivePersonCompleteBlock)completeBlock {
    [self.jive propertyWithName:JivePropertyNames.instanceURL
                     onComplete:^(JiveProperty *property) {
                         NSString *instanceString = property.valueAsString;
                     
                         // The SDK assumes the URL has a / at the end. So make sure it does.
                         if (![instanceString hasSuffix:@"/"]) {
                             instanceString = [instanceString stringByAppendingString:@"/"];
                         }
                     
                         NSURL *instanceURL = [NSURL URLWithString:instanceString];
                     
                         // Yes! We have a server url.
                         if ([instanceString isEqualToString:self.jive.jiveInstanceURL.absoluteString]) {
                             // Everything matches up.
                             if (completeBlock) {
                                 completeBlock(me);
                             }
                         } else {
                             [self loginWithNewInstanceURL:instanceURL me:me completeBlock:completeBlock];
                         }
                     } onError:^(NSError *error) {
                         // No! We are stuck with what works.
                         if (completeBlock) {
                             completeBlock(me);
                         }
                     }];
}

If there is an error or the server URL matches the entered URL we continue accept the current login. If not then we try logging in again with the new URL.

 

- (void)loginWithNewInstanceURL:(NSURL *)instanceURL me:(JivePerson *)me completeBlock:(JivePersonCompleteBlock)completeBlock
{
    // Make sure we have the correct me object.
    Jive *oldJive = self.jive;

    self.jive = [[Jive alloc] initWithJiveInstance:instanceURL
                             authorizationDelegate:self];
    [self.jive me:completeBlock
          onError:^(NSError *error) {
              // Fall back to what works.
              self.jive = oldJive;
              if (completeBlock) {
                  completeBlock(me);
              }
          }];
}

We start by creating a new Jive instance and try to login with it, returning to the previous login attempt if this one fails.

 

That's a lot of code for something the SDK should just handle. Unfortunately because of the number of NSOperations involved and the different places they need to be, they can't simply be put into the SDK. Feel free to prove me wrong on this.