Tanker vs. app code

In most cases, code written with the Tanker Client SDK will be added to pre-existing user flows of your application (e.g. session initialization or data processing). In code examples throughout this guide, we use a global app object, representing your application, which is supposed to handle the following user or app server interactions:

// Global object representing your application
const app = {
  // Authenticate the user with your application server
  async authenticateUser(userId, password): Promise<{ tankerIdentity: string }>,

  // Ask the application server for user public identities
  async getPublicIdentities(userIds): Promise<Array<publicTankerIdentity: string>>,

  // Wait for the user to input a string (verification code, passphrase, and so on.)
  async promptUser(message): Promise<string>,

  // Ask your application server to send a verification code via Tanker
  async sendVerificationCode(email): Promise<void>
};
@interface User : NSObject

@property NSString* identity;
@property NSString* provisionalIdentity;

@end

// Global object representing your application
@interface App : NSObject

  // Authenticate the user with your application server
  - (User*)authenticateUser:(NSString*)userID
                   password:(NSString*)password;

  // Ask the application server for user public identities
  // returns list of public identities
  - (NSArray<NSString*>*)getPublicIdentities:(NSArray<NSString*>*)userIds;

  // Ask your application server to send a verification code via Tanker
  - (void)sendVerificationCode:(NSString*)email;

  // Wait for the user to input a string (verification code, passphrase, and so on.)
  - (NSString*)promptUser:(NSString*)message;

@end
class User {
  public String tankerIdentity;
  public String provisionalIdentity; // may be null
}

// Global object representing your application
class App {
  // Authenticate the user with your application server
  public User authenticateUser(String userId, String password);

  // Ask the application server for user public identities
  public List<String> getPublicIdentities(List<String> userIds);

  // Wait for the user to input a string (verification code, passphrase, and so on.)
  public String promptUser(String message);

  // Ask your application server to send a verification code via Tanker
  public void sendVerificationCode(String email);
}

Asynchronous code

The Tanker API is asynchronous, meaning that all methods of the Tanker object return JavaScript promises. For any method() returning a result asynchronously, you can either await the call:

const result = await tanker.method(/*...*/);

Or chain it with then if you need to be compatible with ECMAScript 5:

tanker.method(/*...*/).then(function (result) {
  // continue work here
});

The Tanker API is asynchronous, meaning that all methods of the Tanker object return TankerFuture objects.

In order to make an asynchronous call, use then() on the promise with a callback:

TankerFuture future = tanker.start(...);
future.then((openFuture) -> {
    if (openFuture.getError()) {
        // handle error
    } else {
        // ...
    }
);

getError() is used for error handling. It should only be called in the then() callback. If there was no error, it will return null.

To get the value of the future, call get() inside the then() callback:

TankerFuture<String> future = ...;
future.then((resultFuture) -> {
    String result = resultFuture.get();
    /// ...
);

Note that if you call get() and there was an error, get() will throw the error.

Also note that if you want to use lambdas like shown above, you should add the following lines to your build.gradle file:

android {
  compileOptions {
      targetCompatibility 1.8
      sourceCompatibility 1.8
  }
}

The Tanker API is asynchronous, meaning that all methods of the Tanker object take a completionHandler callback as last argument.

The callback usually takes two nullable parameters

  1. The result value
  2. An NSError*.

This allows implementing error handling:


// Note: callback types are defined in TKRCompletionsHandler.h:
typedef void (^TKREncryptedDataHandler)(NSData* _Nullable encryptedData,
                                        NSError* _Nullable err);

// here completionHandler must match the TKREncryptedDataHandler type definition
[tanker encryptString:clearText
    completionHandler:^(NSData* encryptedData, NSError* err) {
      if (err == nil) {
        // Do something with encryptedData
      }
    }
];

However, dealing with completions handlers can get cumbersome, that's why we recommand wrapping the callback-based Tanker API into a promise-based API using an asynchronous framework.

Here's a example with PromiseKit:

// In PMKTanker.h

@import PromiseKit;

@interface PMKTanker : NSObject

@property TKRTanker *tanker;

- (PMKPromise<NSData*> *)encryptString:(NSString*)clearText;

@end
// In PMKTanker.m

@import Tanker;
#import "PMKTanker.h"

@implementation PMKTanker

- (PMKPromise<NSData*> *)encryptString:(NSString*)clearText {
  return [PMKPromise promiseWithAdapter:^(PMKAdapter adapter) {
    [self.tanker encryptString:clearText completionHandler:adapter];
  }];
}
@end

Then you can use .then() and .catch():


[pmTanker encryptString].then(
  ^(NSData* encryptedData) {
    // Handle encryptedData
  }).catch(
  ^(NSError* error) {
    // Handle error
  });


Next, we will use a Tanker identity to start a Tanker session.