PACE Cloud SDK
This framework combines multipe functionalities provided by PACE i.e. authorizing via PACE ID or requesting and displaying Apps. These functionalities are separated and structured into different Kits by namespaces, i.e. IDKit, AppKit.
Specifications
PACECloudSDK currently supports iOS 11 and above.
It has some external dependencies which you will need to inlcude as well:
Installation
Carthage
With Carthage, add the following line to your Cartfile and run carthage update --platform iOS
:
github "pace/cloud-sdk-ios" ~> 2.0
Cocoapods
With CocoaPods, add the following line to your Podfile
to use the latest available version:
pod 'PACECloudSDK'
Swift Package Manager (experimental)
With Swift Package Manager, add the following dependency to your Package.swift:
dependencies: [
.package(name: "PACECloudSDK", url: "https://github.com/pace/cloud-sdk-ios", .from(from: "2.0.0"))
]
Binary
Each release has an XCFramework
attached, which can be added to your application; see releases.
Setup
The PACECloudSDK
needs to be setup before any of its Kits
can be used. Therefore you must call PACECloudSDK.shared.setup(with: PACECloudSDK.Configuration)
. The best way to do this is inside
applicationDidFinishLaunching
in your AppDelegate
. It will automatically authorize your application with the provided api key.
PACECloudSDK.Configuration
only has clientId
and apiKey
as a mandatory property, all others are optional and can be passed as necessary.
Note: PACECloudSDK
is using the .production
environment as default. In case you are still doing tests, you probably want to change it to .sandbox
or .stage
.
Available parameters:
clientId: String
apiKey: String? // Default: nil
authenticationMode: AuthenticationMode // Default: .web
accessToken: String? // Default: nil
environment: Environment // Default: .production
configValues: [ConfigValue: Any]? // Default: nil
Migration
From 2.x.x to 3.x.x
In 3.0.0
we’ve introduced a universal setup method: PACECloudSDK.shared.setup(with: PACECloudSDK.Configuration)
and removed the setup for AppKit
and POIKitManager
.
The PACECloudSDK.Configuration
almost has the same signature as the previous AppKit.AppKitConfiguration
, only the theme
parameter has been removed, which is now defaulted to .automatic
. In case you want to set a specific theme, you can set it via AppKit
‘s theme
property: AppKit.shared.theme
.
IDKit
IDKit manages the OpenID (OID) authorization and the general session flow with its token handling via PACE ID.
Setup
This code example shows how to setup IDKit. The parameter cacheSession
defines if IDKit will persist the session.
let config = IDKit.OIDConfiguration(authorizationEndpoint: AUTH_ENDPOINT,
tokenEndpoint: TOKEN_ENDPOINT,
clientId: CLIENT_ID,
redirectUrl: REDIRECT_URL,
additionalParameters: [KEY: VALUE])
IDKit.setup(with: config, cacheSession: true, presentingViewController: YOUR_VIEWCONTROLLER)
Authorization
To start the authorization process and to retrieve your access token simply call:
IDKit.authorize { accessToken, error in
...
}
IDKit will automatically try to refresh the previous session if you passed cacheSession: true
during the setup.
For all devices on iOS 12 and below a native permission prompt for the internally requested ASWebAuthenticationSession
will be displayed to the user.
Token refresh
If you prefer to refresh the access token of your current session manually call:
IDKit.refreshToken { accessToken, error in
...
}
Session refreshToken
Resetting the current session works as follows:
IDKit.resetSession()
A new authorization will be required afterwards.
AppKit
Main Features
- Check for available Apps at the current location
- Retrieve an App as UIViewController or WKWebView
- Retrieve an App as Drawer/Slider
- Payment Authentication
Setup
Biometry is needed for 2FA during the payment process, thus make sure that NSFaceIDUsageDescription
is correctly set in your target properties.
Native login
You can use AppKit with your native login (given that your token has the necessary scopes) as well. In case of a native login,
it is crucial that you set the configuration during setup accordingly, i.e. setting the authenticationMode
to .native
,
and passing an initial accessToken
, if available.
There is a AppKitDelegate
method that you will need to implement, i.e. tokenInvalid(completion: ((String) -> Void))
,
which is triggered whenever your access token (or possible lack thereof) is invalid; possible reasons: it has expired, has missing scopes
or has been revoked. You are responsible for retrieving and passing a valid token to the completion
block.
In case that you can’t retrieve a new valid token, don’t call the completion
handler, otherwise you will most likely end up
in an endless loop. Make sure to clean up all the App related views as well.
Pseudocode of implementing the protocol method and passing the response to AppKit
:
func tokenInvalid(completion: ((String) -> Void)) {
retrieveNewToken { newToken in
completion(newToken)
}
}
Deep Linking
Some of our services (e.g. PayPal
) do not open the URL in the App web view, but in a SFSafariViewController
within the app, due to security reasons. After completion of the process the user is redirected back to the App web view via deep linking. In order to set the redirect URL correctly and to ensure that the client app intercepts the deep link, the following requirements must be met:
- Set the
clientId
in PACECloudSDK’s configuration during the setup, because it is needed for the redirect URL - Specify the
pace.$clientId
in the app target’s custom URL scheme (please refer to Apple’s doc on how to set up the custom URL scheme).
In your AppDelegate
’s application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool
you will have to catch the redirect
host and call AppKit.shared.handleRedirectURL
to handle the callback.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
switch url.host {
case "redirect":
AppKit.shared.handleRedirectURL(url)
return true
default:
return false
}
}
Theming
AppKit
offers both a .light
and .dark
theme. The theme is set to .automatic
by default, which then takes the system’s appearance setting. In case you want to enforce either .light
or .dark
theme, you can set it via the public theme
property: AppKit.shared.theme
.
AppKitDelegate
This protocol needs to be implemented in order to receive further information about your requests. It will also provide specific data that allows you to correctly display Apps.
didFail(with error: AppKit.AppError)
: Called everytime an error occured during requestsdidReceiveAppDrawers(_ appDrawers: [AppKit.AppDrawer], _ appDatas: [AppKit.AppData])
: Called if one or more AppDrawers have been fetched successfully
Requesting local Apps
You need to make sure your users allowed your application to use their location. AppKit requires the user’s current location but will not request the permissions.
To request the check for available local Apps call AppKit.shared.requestLocalApps()
. This function will start retrieving the user’s current position and return
all available Apps as AppDrawers by asynchronously invoking AppKitDelegate's
didReceiveAppDrawers(_ appDrawers: [AppKit.AppDrawer], _ appDatas: [AppKit.AppData])
once for each request. Possible errors during the request will also be passed via the AppKitDelegate
.
Because of the fact that AppDrawers are dependent on the user’s position, it’s necessary to call the mentioned method periodically to make sure that Apps
will stay up to date. If a request does not contain a currently presented App / AppDrawer it will be removed by AppKit automatically. This may happen if the user changes the position to where the previously fetched App is no longer available at.
Is POI in range?
To check if there is a App for the given POI ID at the current location, call AppKit.shared.isPoiInRange(id: String, completion: @escaping ((Bool) -> Void))
.
Note that this method is also triggering AppKitDelegate
’s didReceiveAppDrawers
callback.
AppKit.shared.isPoiInRange(id: poiId) { found in
NSLog("==== Found id in range: \(found)")
}
AppWebView / AppViewController
AppKit provides a default WKWebView or UIViewController that contains the requested App. There are several methods to obtain this WebView or ViewController. You may either pass a appUrl
or a appUrl
with some reference
(e.g. a gas station reference).
let webView = AppKit.shared.appWebView(appUrl: "App_URL")
let viewController = AppKit.shared.appViewController(appUrl: "App_URL")
let webView = AppKit.shared.appWebView(appUrl: "App_URL", reference: "REFERENCE")
let viewController = AppKit.shared.appViewController(appUrl: "App_URL", reference: "REFERENCE")
// Example reference
// The reference starts with a specific namespace identifier followed by the gas station id in this case
// It has to conform to the URN format
let reference = "prn:poi:gas-stations:1a3b5c7d-1a3b-12a4-abcd-8c106b8360d3"
AppDrawerContainer
Before being able to display AppDrawers you need an instance of AppDrawerContainer
. Call setupContainerView()
to setup the container. It will automatically resize itself based on the amount of available AppDrawers.
AppDrawer
The AppDrawer
is a view that functions as a preview for a local App. It has a collapsed and expanded state. Former shows an icon and expands by tapping. Latter additionally shows a title and subtitle and collapses by tapping the x-Button
. Tapping the AppDrawer bar will open the corresponding fullscreen
App. The drawers need to be added to a AppDrawerContaine
in order to work correctly by calling appDrawerContainer.inject(YOUR_DRAWERS)
.
A AppDrawer and the eventually opened App will automatically remove themselves if the App is no longer available at the user’s current position.
AppError
This enum will provide several error messages during App requests and overall processing to give you a better understanding on what went wrong.
Possible errors:
noLocationFound
: GPS is unavailable or inaccuratelocationNotAuthorized
: Missing location permissioncouldNotFetchApp
: AppKit was unable to retrieve the AppfailedRetrievingUrl
: The App url couldn’t be loadedfetchAlreadyRunning
: App fetch is currently runningpaymentError
: The payment couldn’t be processedbadRequest
: The request does not match the expected formatinvalidURNFormat
: The passed POI reference value does not conform to our URN formatcustomURLSchemeNotSet
: The App tried to open an URL inSFSafariViewController
, but deep linking has not been correctly configured