Skip to content

Getting started! - iOS

Prerequisites

Xcode v11.2.1

Swift 5.1

flinkey API

  • Customer ID
  • SDK key
  • Subscription key

flinkey Box

flinkey user ID

Step-by-Step Guide

This documents guides you though the basic integration steps of the Tapkey Mobile SDK. At the end of this guide you should be able to trigger (open/close) your flinkey Box. In addition there are more advanced concepts like the management of digital keys which will be addressed in another .

1. Library dependencies

There are two libraries that need to be added as dependencies to your Android project: The Tapkey Mobile SDK and the WITTE Mobile Library. The Tapkey Mobile SDK comprises everything needed in the scope of a mobile application to operate the flinkey Box. The WITTE Mobile Library offers a set of utitlity and convenience extensions used in context with the Tapkey Mobile SDK.

1.1. Tapkey Mobile SDK

The Tapkey Mobile SDK is distributed through Tapkey's own CocoaPods reposity server. Before you can reference the Tapkey Mobile SDK the corresponding source needs to be added to the list of Podfile.

source 'https://github.com/tapkey/TapkeyCocoaPods'
source 'https://cdn.cocoapods.org/'

Add the TapkeyMobileLib library reference to your Podfile.

platform :ios, '9.0'
use_frameworks!

target 'your-app' do
  pod 'AppAuth'
  pod 'TapkeyMobileLib', '2.5.10.0'
end

1.2. WITTE Mobile Library

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

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/tapkey/TapkeyCocoaPods'

platform :ios, '9.0'
use_frameworks!

target 'your-app' do
  pod 'AppAuth'
  pod 'TapkeyMobileLib', '2.5.10.0'
  pod 'witte-mobile-library', '1.0.0'
end

2. Configuration

Create a configuration instance using your WITTE customer ID, SDK key and subscription key. This instance will be used in source code examples of following steps.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    private var _witteConfiguration: WDConfiguration!

    func application(_ application: UIApplication, 
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    _witteConfiguration = WDConfiguration(
        customerId: <your customer id>,
        sdkKey: <your sdk key>,
        subscriptionKey: <your subscription key>)
    }
}

3. TapkeyServiceFactory

The TapkeyServiceFactory is the central object of the Tapkey Mobile SDK. It creates and provides access to additional objects needed for authentication, management of digital keys, communication with the flinkey Box and others.

import TapkeyMobileLib
import witte_mobile_library

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    private var tapkeyServiceFactory: TapkeyServiceFactory!

    func application(_ application: UIApplication, 
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

            // . . .

            // Tapkey configuration
            let config = TKMEnvironmentConfigBuilder()
                .setbleServiceUuid(WD_BLE_SERIVCE_UUID)
                .setTenantId(WD_TENANT_ID)
                .build()

            // instantiate the Tapkey service factory builder
            _tapkeyServiceFactory = TKMServiceFactoryBuilder()
                .setConfig(config)
                .setTokenRefreshHandler(WitteTokenRefreshHandler(tokenProvider: _witteTokenProvider))
                .build()
    }
}

4. Access Token

The access token is retrieved from the Tapkey backend by exchanging an idToken which needs to be retrieved from the WITTE backend for a specific user. 1. Retrieve idToken from the Witte backen 2. Retrieve access token from the Tapkey backend by exchanging the idToken

let promiseSource = TKMPromiseSource<String>()

TKMAsync.executeAsync({() -> String? in
        // retrieve WITTE idToken
        let request = WDIdTokenRequest()
        let idToken = request.execute(with: self._witteConfiguration, andUserId: self._witteUserId)
        return idToken
    }).continueOnUi{(idToken) -> String in
        // exchange the WITTE idToken for a Tapkey access token
        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 {
                print("Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown")")
                promiseSource.setResult(nil)
                return
            }

            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 let tokenResponse = response {
                    if let accessToken = tokenResponse.accessToken {
                        promiseSource.setResult(accessToken)
                    } else {
                        print("error retrieving access_token")
                        promiseSource.setResult(nil)
                    }
                } else {
                    print("error retrieving access_token")
                    promiseSource.setResult(nil)
                }
            }
        })

        return "unused"
    }.catchOnUi{(error) -> String in
        print(error)
        promiseSource.setResult(nil)
        return "unused"
    }.conclude()

return promiseSource.promise

5. Login

Once you have retrieved an access token you are ready to authenticate with the Tapkey Mobile SDK. Authentication is done using the UserManager instance which is available though the TapkeyServiceFactory.

self._tapkeyUserManager!.logInAsync(accessToken: accesToken!, cancellationToken: TKMCancellationTokens.None)
    .continueOnUi{ (userId: String?) -> Void in
        // login successful
    }
    .catchOnUi{(error) -> Void in
        // login failed
    }
    .conclude();

6. Install a token refresh handler for re-authentication

Once a user is authenticated with the Tapkey Mobile SDK the Tapkey Mobile SDK will need to reauthenticate from time to time. Therefore we need a custom identity provider that retrieves an idToken and hands this token over to the Tapkey Mobile SDK as requested.

import Foundation
import TapkeyMobileLib

class WitteTokenRefreshHandler: NSObject, TKMTokenRefreshHandler {
    let _tokenProvider: WitteTokenProvider

    init(tokenProvider: WitteTokenProvider) {
        _tokenProvider = tokenProvider
    }

    func refreshAuthenticationAsync(userId: String, cancellationToken: TKMCancellationToken) -> TKMPromise<String> {
        return self._tokenProvider.accessToken()
    }

    func onRefreshFailed(userId: String) {
    }
}
import TapkeyMobileLib
import witte_mobile_library

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    private var _tapkeyServiceFactory: TapkeyServiceFactory!

    func application(_ application: UIApplication, 
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

            // . . .

            // register the WitteTokenRefreshHandler
            _tapkeyServiceFactory = TKMServiceFactoryBuilder()
                .setConfig(config)
                .setTokenRefreshHandler(WitteTokenRefreshHandler(tokenProvider: _witteTokenProvider))
                .build()

            // . . .
    }
}

7. Scanning

A flinkey Box is an BLE device. It is not neccessary to pair the flinkey Box with the mobile device but the Tapkey Mobile SDK needs to scan and eventually find flinkey Boxes nearby before any connection can be made in order to operate the flinkey Box.

var _scanInProgress: Bool = false;

// . . .

private func startScanning() {
    let bluetoothState = _tapkeyConfigManager?.getBluetoothState()
    if (bluetoothState == NetTpkyMcModelBluetoothState.bluetooth_ENABLED()) {
        if(nil == _tapkeyStartForegroundScanRegistration) {
            _tapkeyStartForegroundScanRegistration = _tapkeyBleLockScanner?.startForegroundScan()
        }
    } else {
        // bluetooth is not enabled (anymore),
        // we stop the scanning procedure in case it is already running
        stopScanning()
    }
}

private func stopScanning() {
    if (nil != _tapkeyStartForegroundScanRegistration) {
            _tapkeyStartForegroundScanRegistration?.close()
            _tapkeyStartForegroundScanRegistration = nil
        }
}  

8. Triggering

Triggering a flinkey Box means to toggle its state from opened to closed or vice versa. There are no methods to query the current state of the box (opened or closed) or command the box to open or close. Instead there is the triggerLockAsync method which toggles the state of the flinkey Box and delivers a NetTpkyMcModelCommandResult object as result of the method call. The NetTpkyMcModelCommandResult object can be evaluated to determine the current state of the flinkey Box.

let bluetoothAddress = _tapkeyBleLockScanner!.getLock(physicalLockId: physicalLockId)?.bluetoothAddress
if(nil != bluetoothAddress) {
    _tapkeyBleLockCommunicator!
        .executeCommandAsync(
            bluetoothAddress: bluetoothAddress!,
            physicalLockId: physicalLockId,
            commandFunc: { tlcpConnection in self._tapkeyCommandExecutionFacade!.triggerLockAsync(tlcpConnection, cancellationToken: TKMCancellationTokens.None)},
            cancellationToken: TKMCancellationTokens.None)
        .continueOnUi{ (commandResult: TKMCommandResult?) -> Bool? in
            if commandResult?.code == TKMCommandResult.TKMCommandResultCode.ok {
                let responseData = commandResult?.responseData
                if(nil != responseData) {
                    let bytes = responseData! as! IOSByteArray
                    let data = bytes.toNSData()
                    let boxFeedback = WDBoxFeedback(responseData: data!)

                    if(WDBoxState.unlocked == boxFeedback.boxState) {
                        print("Box has been opened")
                    }
                    else if(WDBoxState.locked == boxFeedback.boxState) {
                        print("Box has been closed")
                    }
                    else if(WDBoxState.drawerOpen == boxFeedback.boxState) {
                        print("The drawer of the Box is open")
                    }

                    return true
                }
                else {
                    return false
                }
            }

            return false
        }
        .catchOnUi{ (error: TKMAsyncError) -> Bool? in
            print("triggerLock failed")
            return false
        }
        .conclude()
}

9. Finish

At this point you should be able to actually trigger your flinkey Box in order to open and close it.