Skip to content

Getting Started - iOS

Welcome to the introductory guide for developing Android applications that interact with flinkey boxes using the Tapkey Mobile SDK. This document provides step-by-step instructions to get you started with integrating flinkey functionalities in your Android apps.

For practical implementation, we provide a Sample Application on GitHub that demonstrates how to use the Tapkey Mobile SDK within an iOS app. You can access and clone the repository here:

Additionally, extensive documentation on the Tapkey Mobile SDK is available at:

We recommend thoroughly exploring both the sample application and the Tapkey documentation to gain a comprehensive understanding of the capabilities and integration techniques.

Prerequisites

  • flinkey box with firmware version 30 or newer
  • flinkey API access

1. Library dependencies

There are three libraries to be added as dependencies to your Xcode project: The Tapkey Mobile SDK the WITTE Mobile Library and the AppAuth for iOS.

Library dependencies diagram

The Tapkey Mobile SDK enables mobile apps to interact with flinkey boxes on both Android and iOS platforms. It manages digital keys by retrieving them from the Tapkey Trust Service and storing them locally for offline use.

The WITTE Mobile Library supplements the Tapkey Mobile SDK with additional functionalities specific to flinkey box operations. It includes features for converting flinkey box IDs to physical lock IDs and vice versa, and interpreting the feedback from the flinkey box to determine its open or closed state. The library aids in the integration of Tapkey Mobile SDK within mobile applications, enhancing the management and operation of flinkey boxes.

AppAuth for iOS is a client SDK that facilitates communication with OAuth 2.0 and OpenID Connect providers. It is designed to support Android applications by providing a secure and efficient way to implement user authentication and authorization.

1.1. Tapkey Mobile SDK

The Tapkey Mobile SDK is distributed through Tapkey's own pod repository. Before you can reference the Tapkey Mobile SDK the corresponding pod repository needs to be added to the list of repositories in the Podfile.

source 'https://github.com/tapkey/TapkeyCocoaPods'

Add the Tapkey Mobile Library reference to your Podfile.

pod 'TapkeyMobileLib', '<latest_version>'

1.2 WITTE Mobile Library

The WITTE Mobile library is distributed via CocoaPods. Add the witte-mobile-library reference to your Podfile.

pod 'witte-mobile-library', '3.0.0'

1.3 AppAuth for Android

The AppAuth library is available via CocoaPods as well. Add the appauth reference to your Podfile.

pod 'AppAuth'

2. Authentication

The Tapkey Mobile SDK needs to communicate with the Tapkey Trust Service to synchronize digital keys with the mobile device for authenticated users. It is the responsibility of the mobile app to provide an access token to the Tapkey Mobile SDK upon login and subsequently on token refresh requests by the Tapkey Mobile SDK. The access token is retrieved by obtaining an IdToken first which is then exchanged for an access token.

Library dependencies diagram

2.1 IdToken

It is assumed that the mobile app has its own backend including user management for authentication and authorization. Since the flinkey API is designed for server to server communication only the mobile app cannot query the flinkey API directly. To obtain an IdToken the mobile app needs to query its own backend in a secure way. The app backend needs to handle this request by interacting with the flinkey API to get an IdToken for a specific user. Please refer to the section App SDK of the flinkey API v3 documentation.

2.2 Token Exchange

The IdToken can be exchanged for an access token utilizing functions provided by the AppAuth library. The following source code snippets show the basic usage. Please note that there is complete implementation of the token exchange available as part of the sample application.

The method OIDAuthorizationService.discoverConfiguration asynchronously fetches the configuration from the authorization server’s well-known configuration endpoint. The result of this operation together with the IdToken and constants provided above are used to build a OIDTokenRequest. The OIDTokenRequest object is then used to query the access token using the OIDAuthorizationService.

let idToken: String = "<IdToken>"

var urlComponents: URLComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "login.tapkey.com"

OIDAuthorizationService.discoverConfiguration(forIssuer: urlComponents.url!.absoluteURL, completion: { configuration, error in
    guard let config = configuration else {
        // handle error
    }

    let tokenRequest: OIDTokenRequest = OIDTokenRequest(
      configuration: config,
      grantType: "http://tapkey.net/oauth/token_exchange",
      authorizationCode: nil,
      redirectURL: nil,
      clientID: "wma-native-mobile-app",
      clientSecret: nil,
      scopes: ["register:mobiles", "read:user", "handle:keys"],
      refreshToken: nil,
      codeVerifier: nil,
      additionalParameters: [
          "provider": "wma.oauth",
          "subject_token_type": "jwt",
          "subject_token": idToken!,
          "audience": "tapkey_api",
          "requested_token_type": "access_token"
      ]
    )

    OIDAuthorizationService.perform(tokenRequest) { response, error in
        if (nil != error) {
            // handle error
        } else if let tokenResponse = response {
            if let accessToken = tokenResponse.accessToken {
                // use accessToken
            } else {
                // handle error
            }
        } else {
            // handle error
        }
    }
})

Please remember to replace the placeholder with the actual IdToken you have received from your service before usage.

3. Working with the Tapkey Mobile SDK

3.1 TKMServiceFactory

The TKMServiceFactory serves as the pivotal component of the Tapkey Mobile SDK. Primarily, it simplifies the creation and accessibility of various objects instrumental in executing an array of responsibilities, which include user authentication, digital key management, and facilitating communication with the flinkey box. The example below demonstrates how to create an instance of TKMServiceFactory within the application function in your AppDelegate class. Instantiate TKMEnvironmentConfig and configure it using TKMBleAdvertisingFormatBuilder. The setTenantId() method sets the tenant ID. Instantiate TKMBleAdvertisingFormat and further configure it using TapkeyBleAdvertisingFormatBuilder. At this stage, you're including formats v1 and v2. Lastly, create the TKMServiceFactory instance.

Here's how you can achieve these steps:

import UIKit
import TapkeyMobileLib
import witte_mobile_library

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    public var tapkeyServiceFactory: TKMServiceFactory!

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Create Tapkey configuration
        let config = TKMEnvironmentConfigBuilder()
            .setTenantId("wma")
            .build()

        let bleAdvertisingFormat = TKMBleAdvertisingFormatBuilder()
            .addV1Format(serviceUuid: "6e65742e-7470-6ba0-0000-060601810057")
            .addV2Format(domainId: 0x5754)
            .build()

        // Create Tapkey service factory
        tapkeyServiceFactory = TKMServiceFactoryBuilder()
                .setConfig(config)
                .setBluetoothAdvertisingFormat(bleAdvertisingFormat)
                .setTokenRefreshHandler(TokenRefreshHandler())                
                .build()

        // Further logic...
    }
}

The function .setTokenRefreshHandler() is used to set a token refresh handler to the TKMServiceFactory instance. The token refresh handler is responsible for refreshing the user's authorization token when it expires, thereby ensuring uninterrupted communication with the Tapkey services. This method of the TKMServiceFactoryBuilder accepts an instance of TKMTokenRefreshHandler as an argument. TKMTokenRefreshHandler is an interface where you need to implement two methods:

  1. refreshAuthenticationAsync(): This method is invoked when Tapkey Service Factory requires a new access token. It's given the user's tapkeyUserId and a CancellationToken. This method should return a promise object that completes with the new access token or fails if the access token could not be obtained. It should handle any asynchronous operation required to get the new access token (e.g., a request to your backend), but also handle the cancellation request signalized by the CancellationToken.
  2. onRefreshFailed(): This method is invoked whenever refreshing of the access token fails. Tapkey invokes this method with the tapkeyUserId as argument. If refreshing the access token fails, this method is called, and you can define how the system behaves (retry, notify the user, etc.) within this method. It allows you to handle any necessary clean-up or error logging procedures.
import Foundation
import TapkeyMobileLib

class TokenRefreshHandler: NSObject, TKMTokenRefreshHandler {
    func refreshAuthenticationAsync(userId: String, cancellationToken: TKMCancellationToken) -> TKMPromise<String> {
      // Implement the logic to retrieve the access token here
    }

    func onRefreshFailed(userId: String) {
      // Implement error handling logic here
    }
}

3.2 Login

After obtaining an access token, you're all set to authenticate with the Tapkey Mobile SDK. For authentication, we will use the TKMUserManager instance, which is accessible through the TKMServiceFactory.

The following example demonstrates how you can authenticate a user with the Tapkey Mobile SDK:

// authenticate a user with the Tapkey Mobile SDK
let tapkeyUserManager: TKMUserManager = tapkeyServiceFactory.userManager
tapkeyUserManager
    .logInAsync(accessToken: accessToken!, cancellationToken: TKMCancellationTokens.None)
    .continueOnUi { (userId: String?) -> Void in
        // login successful
    }
    .catchOnUi { (error: TKMAsyncError?) -> Void in
        // login failed
        return null;
    }

In this example, the logInAsync function is used for the login operation. This method uses the access token (obtained earlier) and an instance of TKMCancellationTokens.None (if cancellation isn't desired) as its arguments. Success or failure of the operation can be handled within continueOnUi and catchOnUi blocks respectively.

3.3 Scanning

A flinkey box is a Bluetooth Low Energy (BLE) device. Although it's not mandatory to pair a flinkey box with your mobile device, the Tapkey Mobile SDK needs to locate nearby flinkey Boxes via a scan before it can establish any connection required to control the flinkey box.

Below is a code snippet demonstrating how to utilize the SDK:

private var tapkeyStartForegroundScanRegistration: TKMObserverRegistration?

// Acquire an instance of TKMBleLockScanner from the TKMServiceFactory.
let tapkeyBleLockScanner: TKMBleLockScanner = tapkeyServiceFactory.bleLockScanner

// Initiate the scanning process for flinkey Boxes.
if nil == tapkeyStartForegroundScanRegistration {
    tapkeyStartForegroundScanRegistration = tapkeyBleLockScanner.startForegroundScan()
}

// The scanning for flinkey Boxes can be stopped as follows.
if nil != tapkeyStartForegroundScanRegistration {
    tapkeyStartForegroundScanRegistration?.close()
    tapkeyStartForegroundScanRegistration = nil
}

The startForegroundScan() function from the TKMBleLockScanner instance commences the scanning for flinkey boxes. The close() function stops the ongoing scan.

The tapkeyStartForegroundScanRegistration is used to manage the scanning operation, closing it when it's not needed anymore, and ensuring there's no ongoing scan when starting a new one. It's strongly recommended to manage the scanning process properly to prevent unnecessary use of resources.

3.4 Triggering

The process of 'Triggering' a flinkey box refers to changing its status from open to closed, or the other way around. This can be achieved by using the executeStandardCommandAsync method provided in the Mobile SDK. When this method is invoked, it returns a TKMCommandResult object, which can be examined to identify the current status of the flinkey box.

Converting the flinkey box ID into a physical lock ID that is compatible with the Tapkey Mobile SDK is an essential step. This is accomplished using the WDBoxIdConverter, a part of the WITTE Mobile Library.

Below is the code snippet demonstrating the usage of executeStandardCommandAsync method to toggle a flinkey box's status:

let tapkeyBleLockScanner: TKMBleLockScanner = tapkeyServiceFactory.bleLockScanner
let tapkeyBleLockCommunicator: TKMBleLockCommunicator = tapkeyServiceFactory.bleLockCommunicator
let tapkeyCommandExecutionFacade: TKMCommandExecutionFacade = tapkeyServiceFactory.commandExecutionFacade

// The flinkey box ID is printed on the box's label
let boxId: String = "C3-B2-C3-D4";

// Convert box id to physical lock id
let converter = WDBoxIdConverter()
let physicalLockId = converter.toPhysicalLockId(withBoxId: boxId)

// Get the bluetooth address of the flinkey box
let bluetoothAddress = tapkeyBleLockScanner.getLock(physicalLockId: physicalLockId)?.peripheralId

tapkeyBleLockCommunicator!
    .executeCommandAsync(
        peripheralId: bluetoothAddress!,
        physicalLockId: physicalLockId,
        commandFunc: { tlcpConnection -> TKMPromise<TKMCommandResult> in
            let triggerLockCommand = TKMDefaultTriggerLockCommandBuilder().build()
            return commandExecutionFacade.executeStandardCommandAsync(
                tlcpConnection, command: triggerLockCommand, cancellationToken: timeout)
        },
        cancellationToken: timeout
    )
    .continueOnUi { (commandResult: TKMCommandResult?) -> Bool? in
        let commandResultCode: TKMCommandResult.TKMCommandResultCode =
        commandResult?.code ?? TKMCommandResult.TKMCommandResultCode.technicalError

        switch commandResultCode {
        case TKMCommandResult.TKMCommandResultCode.ok:
            let responseData = commandResult?.responseData
            if nil != responseData {
                let bytes = responseData! as! IOSByteArray
                let data = bytes.toNSData()

                if 10 == bytes.length() {
                    let boxFeedback = WDBoxFeedback(responseData: data!)
                    // Evaluate box feedback ...
                } else {
                    var bf: WDBoxFeedbackV3? = WDBoxFeedbackV3()
                    let success = WDBoxFeedbackV3Parser.parseData(data, boxFeedback: &bf)
                    // Evaluate box feedback ...
                }
            }
        }
    }
    .finallyOnUi {
      // Termination handling code
    }
    .catchOnUi { (error: TKMAsyncError) -> Bool? in
      // Exception handling code
    }
    .conclude()

The above code will execute the TKMDefaultTriggerLockCommand and then evaluate the TKMCommandResult to fetch the box's feedback. Based on the length of the response data, it either creates a WDBoxFeedback or WDBoxFeedbackV3 object. The feedback is then evaluated to understand the status of the box.