iOS SDK Integration

Introduction

The Fintech Bridge SDK is intended for embedding small, widget-like applications inside a larger app. The application is structured in the following parts:

Including embedded applications thus requires including the bridge library, configuring the SubaioManager and instantiating a SubaioView.

Prerequisites

Installation

The iOS SDK is versioned using semver 2.0.0 without any prefix. Thus, patch and minor versions should require no changes. We recommend always using the latest version if possible.

The SDK has a single external dependency, Trustkit, used to handle SSL pinning.

The SDK can be installed either using Carthage to directly build from the git repository, or using pre-built frameworks. It cannot be installed as a swift package, since this is unsupported by Trustkit.

Using Git and Carthage

With Carthage, the framework can be automatically built from a git repository. If possible, this is the recommended method.

The git repository is located at git@bitbucket.org:subhub/fintech-bridge.git. To avoid issues with managing user access for this repo, it is recommended that you create a mirror of this in your own git server solution using a single set of credentials given by Subaio, and give all relevant developers and CI servers access to the clone.

Add the repository to the Cartfile:

git "<mirror-url>" "<version>"

Install using carthage bootstrap in a terminal. This should install the SDK and its one dependency.

Go to “Project Settings” > “General” > “Frameworks, Libraries, and Embedded Content” in XCode, and press “+”. Select “Add Other” > “Add Files” and select Subaio.framework and Trustkit.framework from ./Carthage/Build/iOS.

Install pre-built frameworks

Download the zipped framework for the appropriate version of XCode. The files are named after the following pattern: ios/<SDK-version>/xcode-<XCode-version>/subaio.framework.zip. Extract Subaio.framework and copy it to the project directory.

Go to “Project Settings” > “General” > “Frameworks, Libraries, and Embedded Content” in XCode, and press “+”. Select “Add Other” > “Add Files” and select Subaio.framework.

Install Trustkit according to their guide.

Setup SubaioManager

The SubaioManager is a singleton class used to configure shared settings and manage authentication. Before initializing any views, the manager needs these configuration settings. The sections below outline the purpose and options of the different sections. To get started, the following should be run during app start:

SubaioManager.shared.language = "en"

SubaioManager.shared.configure(using: SubaioConfiguration(
    baseUrl: URL(string: "<app-url>")!,
    allowExternalUrls: true,
    allowShare: true,
    sslPinningConfiguration: SubaioSSLPinningConfiguration(sites: [
        .init(domain: "subaio.com", publicKeyHashes: [
            // *.integration.subaio.com
            "LpLtgPvW/ac308Qkrh5S86yjzUOON4LB8dEkx/zcwW4=",
            // *.qa.subaio.com
            "PPqSMfht94cpmovcJ+bBEupcmKvGxPReeNoWsJL0Gok=",
            // *.prod.subaio.com
            "PO7y+PL8YCAcEIZzhekzoFgkx92aIB6HfHzanC7RDNI=",
        ])
    ]),
    onTokenRequired: { (language, completionHandler) in
        guard /* user is logged in */
        else {
            completionHandler(.unauthenticated)
            return
        }
        /* fetch subaio token through backend */
        guard /* token was successfully fetched */
        else {
            completionHandler(.unavailable)
            return
        }
        completionHandler(.success(token: "..."))
    }
)) { [weak self] result in
    /* Views can now be created */
}

Authentication

The SubaioViews use JSON web tokens for authentication. All views use a shared token, administred by the SubaioManager, fetched through the onTokenRequired callback. The tokens generally expire after one hour, so the SubaioManager has internal logic to automatically refresh the token when necessary.

Getting a Subaio-token should be handled through your own backend, to ensure your own user security is observed.

The onTokenRequired callback must handle three cases:

Besides providing the callback, you should invoke SubaioManager.shared.refreshToken when the current user is unauthenticated or a new user is authenticated, to force an update of the token.

As a convenience, the current language is passed to onTokenRequired, since this should be passed along to the subaio backend during authentication. If the language is not set, a token is not requested. If the language is changed, a new token is automatically requested.

While the authentication flow through your backend is setup, you can use a long-lived hardcoded token provided by Subaio for the integration environment. Set onTokenRequired to

onTokenRequired: { (language, completionHandler) in
    completionHandler(.success(token: "<token>"))
}

SSL pinning

SSL pinning limits the domains available in the webviews and ensures you connect to the correct servers. SSL pinning is handled by Trustkit. A base64 hash of the certificate public key is used to identify it. The following certificate hashes are used:

Subdomain Hash Expiration Notes
*.integration.subaio.com LpLtgPvW/ac308Qkrh5S86yjzUOON4LB8dEkx/zcwW4= 2020-02-13 Unstable, automatically renewed periodically.
*.qa.subaio.com PPqSMfht94cpmovcJ+bBEupcmKvGxPReeNoWsJL0Gok= 2020-05-30 Stable, Subaio coordinates renewal well before expiration.
*.prod.subaio.com PO7y+PL8YCAcEIZzhekzoFgkx92aIB6HfHzanC7RDNI= 2020-05-30 Stable, Subaio coordinates renewal well before expiration. Should always be included, since error logging is hosted here.

Each subdomain can have multiple hashes defined without problems, so near expiration a new hash is simply added alongside the others.

Language

Language is defined using a BCP 47 language tag, such as en or da-DK. The current language can be changed at any time, immediately to be reflected in all open views. The fallback language when an unknown language is english, but can be changed if necessary.

Since the language is used as part of the token request, a token isn’t requested before a language has been provided, which prohibits any views from loading.

Sharing and external URLs

To better allow fine-grained control of your application, these events can be disabled. Generally, they should be enabled unless otherwise agreed. They are used to open subscription overviews in subscription aggregators, such as iTunes or Google Play.

Custom configuration (optional)

If necessary, custom configuration values can be sent to the webview. This is useful if the app allows for in-app theme changes or if user information needs to be shared.

Note that custom configuration is only necessary for dynamic theming that can only be known at runtime. General theming should be decided as part of the design process.

customConfig is added to the SubaioConfiguration as an optional string-dictionary:

SubaioManager.shared.configure(using: SubaioConfiguration(
    ...
    customConfig: [
        "name1": "value1"
    ]
    ...
)

Create a SubaioView

SubaioView is a UIView-wrapper for a webview to handle authentication, configuration and events from the webview. To get started, the following UIViewController can be used:

final class SubaioViewController: UIViewController {
    private lazy var subaioView: SubaioView = {
        let subaioView = SubaioView(page: self.page)
        subaioView.delegate = self
        return subaioView
    }()

    var page: Page

    convenience init(page: Page) {
        self.init(page: page, nibName: nil, bundle: nil)
    }

    init(page: Page, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        self.page = page
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.addSubview(subaioView)

        // Handle layout
    }
}

// Event handler extension
extension SubaioViewController: SubaioViewDelegate {
    func subaioView(_ subaioView: SubaioView, navigatedTo page: Page) {
        DispatchQueue.main.async {
            let viewController = SubaioViewController(page: page)
            self.navigationController?.pushViewController(viewController, animated: true)
        }
    }

    func subaioViewDidSelectBack(_ subaioView: SubaioView) {
        DispatchQueue.main.async {
            self.navigationController?.popViewController(animated: true)
        }
    }

    func subaioView(_ subaioView: SubaioView, changedStatusTo status: ViewStatus) {
        // React to status if custom loading or error behaviour needs to be implemented
    }

    func subaioView(_ subaioView: SubaioView, updatedTitleTo title: String) {
        // React to title updates if a navigation-bar title is used
    }

    func subaioView(_ subaioView: SubaioView, didSendUnsupportedEventOfType type: String, payload: Any?) {
        // Potentially log invalid events sent
        // Also logged internally
        print("SUBAIO UNSUPPORTED EVENT: \(type)\nPayload: \(String(describing: payload))")
    }

    func subaioView(_ subaioView: SubaioView, didReceiveError error: SubaioError) {
        // React to the view failing. `noTemplate` and `viewTimedOut` means the view failed to load
        // and nothing is shown, on `internalError` the view will show error content.
        print("SUBAIO ERROR: \(error)")
    }

    func subaioView(_ subaioView: SubaioView, isAttemptingToShare items: [Any]) {
        // Handle sharing of messages.
        let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
        self.present(activityViewController, animated: true, completion: nil)
    }
}

To better facilitate native transitions between pages, the subaio app is separated in pages that can be placed in separate view-controllers and pushed separately to the nagivation stack.

Each instance of an embedded view can be set to show a specific Page, defined by a name and optionally a set of parameters, e.g. Page(name: "OVERVIEW", params: nil) or Page(name: "SUBSCRIPTION", params: ["serviceId": "..."]). The exact pages will depend on the application. For simple integrations where the views are simply pushed to the navigation stack, the host application only need knowledge of the initial overview page, available as Page.overview for convenience.

Views will push new pages using the navigatedTo event.

Notifications for the embedded app should generally be implemented to pass name and params provided by subaio on to the host application to easily handle navigation.

Layout

A SubaioView can be sized with constraints like any view. Generally, the SubaioView should simply take up the available space.

If, however, the SubaioView should be embedded among existing native content, for instance as a dashboard-widget, a special feature allows scaling the view to automatically match the content. This is triggered by adding the autosizeDefaultHeight-argument to the SubaioView contructor. The default height should match the size the widget will most likely end up as, and should be agreed upon based on the design. This resizing is achieved by adding a height-constraint to the SubaioView.

If a common navigation header is used for most view-controllers, it can be used here as well. The SubaioView should be constrained to take up the remaining space. A localized title for the header can be used by the updatedTitleTo event-handler.