Mobile Abstraction Layer
Treezor provides a React abstraction layer that lives above the Mobile SDK.
This wrapper allows you to implement SCA features more easily than via the SDK directly.
Alert – The abstraction layer is provided "as is"
Contrary to the SDK, this abstraction layer is provided "as is" without any warranty.
To use it you have to:
- Use React Native version 0.68 to 0.70 (this can be checked using
npx react-native doctor
) - Import
react-native-treezor-sca
using credentials provided by your Account Manager. - Import
bridge-reactNative-{version}
which has been provided to you encrypted using GPG.
Using npm install -S {package}
or yarn add {package}
.
iOS SDK Configuration
Prerequisites – Requires Apple Developer Account
You need a valid Apple Developer Account to achieve a valid configuration, as well as an up-to-date membership in Apple Developer Program (see Apple Membership).
You need the following Apple Capabilities to work with the SDK:
- Push Notifications – Required for SCA Wallet activation (silent push) and for authentication request (classic push)
- App Groups – Required to decrypt an authentication request and present decrypted content to iOS Push System.
If you're unfamiliar with how to configure these capabilities, you can either refer to the 2.3 section of the SDK documentation or go through the configuration steps listed below.
Configuration steps
In your Apple Developer Portal, you'll need to:
- Create your identifier (if it's not already the case)
- Configure App Groups
- Configure Apple Push Notifications
To create your identifier, go to Identifiers and select or create your application identifier (for a creation, check your identifier has an "Explicit" Bundle ID).
To configure App Groups:
- Select Identifiers in the left menu of the Developer, then select App Groups from the upper right dropdown
- Click on the "+" button and follow the instructions from the creation assistant
- Open your Application Identifier and click "Configure" from the "App Groups" feature. Then, select your previously created App Groups Identifier.
To Configure Push Notifications:
- Select Keys in the left menu of the Developer Portal.
- Click on the "+" button and:
- Provide a "Key name"
- Check "Apple Push Notifications service (APNs)"
- Click on "Continue", then "Register"
- Click on the "Download" button to retrieve the generated key and the "APNS Key ID" value. Also keep the "Team ID" value.
- Open your Application Identifier and click "Configure" from the "Push Notification" feature.
You can also:
- Follow the Apple's documentation to add an Apple Push Notification Production SSL certificate.
- Send Push information to the SDK
AntelopRelease.plist
Using XCode, create AntelopRelease.plist
file in ios -> AppName -> AntelopRelease.plist
and add it to your TargetAppName.
Your file should look like this:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>fr.antelop.alertLoggingEnabled</key>
<string>The Antelop SDK is logging.</string>
<key>fr.antelop.applicationGroupIdentifier</key>
<string>group.com.treezor.scaintegration</string>
<key>fr.antelop.initialConnectionTimeout</key>
<integer>60</integer>
<key>fr.antelop.application_id</key>
<integer>{yourApplicationId}</integer>
<key>fr.antelop.issuer_id</key>
<string>{yourIssuerId}</string>
<key>fr.antelop.teamIdentifier</key>
<string>{yourTeamIdentifier}</string>
</dict>
</plist>
Configure XCode
If you are using the last architecture of React Native which uses Obj-c++, you will need to add the following flag: Target->BuildSettings->Other C++ flags: -fmodules
and -fcxx-modules
If you get the "C++ modules are disabled" error, please set the following settings to true. Target->BuildSettings->Enable Modules(C and Objective-C)
Configure AppDelegate
In AppDelegate.mm
, add @import react_native_antelop;
at the top of the document.
c
@import react_native_antelop;
Override the AppDelegate.didFinishLaunchingWithOptions and use this code.
c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// [...] Your application code
// Add these lines at end of the method
[AntelopBridgeAppDelegate.shared didFinishLaunchingWithOptionsWithApplication:application launchOptions:launchOptions];
return YES;
}
Note – Do not replace your complete didFinishLaunchingWithOptions
method
Just add the provided code at the end of the method.
Override the AppDelegate.applicationDidBecomeActive and use this code :
c
- (void)applicationDidBecomeActive:(UIApplication *)application {
[AntelopBridgeAppDelegate.shared applicationDidBecomeActiveWithApplication:application];
}
Override the AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken and use this code:
c
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// You can retreive the deviceToken for you own Server side push
// Link to Antelop Bridge
[AntelopBridgeAppDelegate.shared didRegisterForRemoteNotificationsWithDeviceTokenWithApplication:application data:deviceToken];
}
Override the AppDelegate.didReceiveRemoteNotification and use this code:
c
-(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void
(^)(UIBackgroundFetchResult))completionHandler
{
typedef void (^FetchCompletionHandler)(UIBackgroundFetchResult);
typedef void (^ClientNotificationHandler)(NSDictionary * _Nonnull, FetchCompletionHandler);
ClientNotificationHandler clientNotificationHandler = ^void(NSDictionary * _Nonnull userInfo, FetchCompletionHandler handler) {
// To be implemented if the default behaviour is not desired
};
[AntelopBridgeAppDelegate.shared didReceiveRemoteNotificationWithUserInfo:userInfo completionHandler:completionHandler clientNotificationHandler:clientNotificationHandler];
}
Override the AppDelegate.performFetchWithCompletionHandler and use this code:
c
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[AntelopBridgeAppDelegate.shared performFetchWithCompletionHandlerWithApplication:application completion:completionHandler];
}
Push notifications
Adding the Notification Service is required to decrypt the encrypted content of the Push Notifications.
To add the necessary files for iOS, run the command yarn installNotificationIOS
Note – installNotificationIOS
script depends on npm xcode
package
Therefore, you should make sure that your package.json
specifies this package (yarn add xcode
or npm i xcode
).
This will add a new target "PushExtension" to your project. Verify the settings below for the newly added target:
- In XCode navigate to section "General"
- Verify your Bundle identifier, Version and Build
- Navigate to section "Signing & Capabilities"
- Add "App Groups" by clicking "+ Capability" and check the group identifier embed in your provisioning profile
- Verify your Bundle identifier
- Navigate to section "Build Settings":
- Search for "SWIFT_VERSION" in the filter and ensure that it is valued to "SWIFT 5"
In order to share data with the main app, the Extension must have access to the App Group declared in the AntelopRelease.plist
at fr.antelop.applicationGroupIdentifier
.
In your Podfile, add these lines to the target of your application:
c
pod 'react-native-antelop', :path => '../node_modules/react-native-antelop'
pod 'AntelopSDK', :path => '../node_modules/react-native-antelop/ios/Frameworks/'
pod 'SecureCModule', :path => '../node_modules/react-native-antelop/ios/Frameworks/'
In your Podfile, add these lines at the end of the file:
c
target 'PushExtension' do
config = use_native_modules!
use_react_native!(:path => config["reactNativePath"])
inherit! :search_paths
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
end
end
flipper_post_install(installer)
end
end
Make sure to add the AntelopRelease.plist
to the Extension Target: File Inspector -> Target Membership, check the box for the Extension Target if necessary.
Android SDK Configuration
Metadata
Add the following metadata to android/app/src/main/AndroidManifest.xml
xml
<meta-data
android:name="fr.antelop.application_id"
android:value="\4713640103500149457" />
<meta-data
android:name="fr.antelop.issuer_id"
android:value="treezor" />
Permissions
Add the following permissions to android/app/src/main/AndroidManifest.xml
xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
Libraries
Add the following libraries to android/build.gradle
json
allprojects {
// ...
repositories {
// ...
flatDir {
dirs project(':react-native-antelop').file('libs')
dirs project(':react-native-antelop').file('google-libs')
}
}
// ...
}
Add the following to android/app/build.gradle
(if not already present)
json
dependencies {
// ...
implementation 'androidx.work:work-runtime-ktx:2.7.0'
}
Reading – More SDK configuration information
If you need more information regarding initial configuration of the SDK on iOS or Android, contact Treezor to request access to the SDK full documentation.
Usage
Declare a custom prompt YourCustomPrompt.tsx
using:
js
import {
StrongCustomerAuthenticationProvider,
CustomPromptProps,
CustomPromptAction,
CustomPromptError,
} from 'react-native-treezor-sca';
const YourCustomPrompt = ({
onPasscodeEntered,
onClose,
action,
error,
}: CustomPromptProps) => {
const title: Record<CustomPromptAction, string> = {
auth: 'Enter your passcode',
check: 'Enter your current passcode',
setup: isConfirmation ? 'Repeat your passcode' : 'Set your passcode',
update: isConfirmation
? 'Repeat your new passcode'
: 'Enter your new passcode',
unlock: 'Enter your passcode to unlock your account',
activateBiometric: 'Enter your passcode to activate biometric',
deactivateBiometric: 'Enter your passcode to deactivate biometric',
};
const scaErrors: Record<CustomPromptError, string> = {
wrongPasscode: remainingPasscodeAttempts
? `Invalid passcode.\n You have ${remainingPasscodeAttempts} attempt(s) left`
: 'Invalid passcode.\n Please try again ',
wrongBiometrics:
"Many incorrect attempts to verify biometrics; use passcode until it's reactivated again",
};
return (
<Modal>
<Text>{title[action]}</Text>
{error && <Text>{errors[error]}</Text>}
{/* Rest of your prompt design & logic */}
</Modal>
);
};
When a passcode error in the prompt occurs, the set remainingPasscodeAttempts
value depends on the context.
Passcode error context | Value | Description |
---|---|---|
SCA | numerical | The number of remaining attempts before the authentication passcode is blocked. Applies to both per-session and per-operation SCA. |
SCA Wallet action | undefined | Specifically for biometry activation/deactivation or unlocking of the SCA Wallet. If there are too many erroneous attempts, the SCA Wallet will be blocked. |
Wrap your application in App.tsx
using the following.
js
import { StrongCustomerAuthenticationProvider } from 'react-native-treezor-sca';
return (
<StrongCustomerAuthenticationProvider Prompt={YourCustomPrompt}>
{/* Your application */}
</StrongCustomerAuthenticationProvider>
);
Then initialize the abstraction layer using:
js
import { useStrongCustomerAuthentication } from 'react-native-treezor-sca';
const {
isConnected,
biometricStatus,
destroy,
connect,
disconnect,
provision,
sign,
changePasscode,
activateBiometric,
deactivateBiometric,
} = useStrongCustomerAuthentication({
rootedDeviceForbidden: false,
onError: (code, details) => console.error(code, details),
onProvisioningRequired: async () => {
const scaWallet = await MyApiService.createScaWallet({ userId });
provision(scaWallet.activationCode);
},
onChangeCredentialsSuccess: () => console.info('Passcode has been updated'),
onCheckCredentialsSuccess: () => console.info('Passcode has been checked'),
onActivateBiometricSuccess: () =>
console.info('Biometric Authentication has been activated'),
onDeactivateBiometricSuccess: () =>
console.info('Biometric Authentication has been deactivated'),
});
Connect to an SCA Wallet
js
// by calling the connect() method :
// if no wallet exists, onProvisioningRequired() will be called
// if a wallet is already provisionned, a simple connexion to it is established
connect();
You should only connect to an SCA Wallet once the End User has authenticated on your app.
Generate an SCA Proof (without user action)
js
// Generate a JWS to make session calls
const signature = await sign();
const decoded = jwtDecode(signature);
console.info(decoded);
Generate an SCA Proof (with user action)
Generate an SCA proof with a user passcode or user biometrics request.
js
// Generate a JWS to make session calls
const signature = await sign({ type: 'authenticate' });
const decoded = jwtDecode(signature);
console.info(decoded);
Customize prompt biometric
js
// Customizing biometric prompt with android title, android subtitle or ios subtitle
const signature = await sign({
type: 'authenticate',
biometryMessage: {
androidTitle: 'Android title to custom',
androidSubTitle: 'Android subtitle to custom',
iosSubTitle: "iOS subtitle to custom
},
});
Sign parameters interface
js
// Sign params interface
const sign = async (options: ISignOptions = { type: 'none' })
interface ISignOptions {
type: ISignType; // authentication type
data?: unknown; // data to be displayed as part of the authentication or null
biometryMessage?: IBiometryMessage; // custom biometric prompt message
}
interface IBiometryMessage {
title?: string;
subTitle?: string;
}
Sign act parameters interface
js
// Sign act params interface
const signAct = (
operation: OperationSignature, // url and body of the sign act
biometryMessage?: IBiometryMessage // you can also customize biometric prompt message for signAct also
)
export type OperationSignature = {
url: string;
body: unknown;
};
Activate biometric
js
// Activate biometric
activateBiometric();
Deactivate biometric
js
// Deactivate biometric
deactivateBiometric();
Generate a per-operation SCA Proof
js
// Generate a JWS to update a Card's limits, with a user passcode request
const signature = await signAct({
url: '{baseUrl}/v1/cards/{cardId}/Limits/',
body: {
limitAtmMonth: 1000,
limitPaymentMonth: 800,
},
});
const decoded = jwtDecode(signature);
console.info(decoded);
You can check out the format of the payload to sign.
Change the passcode
js
// change passcode requested by end user
changePasscode();
Disconnect from an SCA Wallet
js
// disconnect from an SCA Wallet
disconnect();
A disconnect should occur when your app is put to the background, or when the End User disconnects from your app.
Delete an SCA Wallet
js
// irreversibly destroy the SCA Wallet from mobile device
destroy();
Listeners
Subscribing for "WalletEventListener" and "PushAuthenticationRequestListener" enables the application to capture events from remote notifications, thereby ensuring correct wallet and authentication statuses. For subscription, a set of callback functions for each event have to be provided.
Find below two examples of listening to notifications, with display or actions triggered afterwards.
Example 1 – Listen and display log or toaster outside the application
js
import {AppRegistry} from 'react-native';
import {Toast} from 'react-native-toast-message/lib/src/Toast';
import {
WalletEventListener,
PushAuthenticationRequestListener,
} from 'react-native-antelop';
import {name as appName} from './app.json';
import App from './src/App';
const walletEventListenerProtocole = {
onWalletLoaded: () => {
Toast.show({
type: 'info',
text1: 'Wallet has been loaded',
});
console.log('Event', '@WalletEventListener/onWalletLoaded');
},
onWalletLocked: reason => {
Toast.show({
type: 'info',
text1: 'Wallet has been locked',
});
console.log('Event', '@WalletEventListener/onWalletLocked', reason);
},
onWalletUnlocked: () => {
Toast.show({
type: 'info',
text1: 'Wallet has been unlocked',
});
console.log('Event', '@WalletEventListener/onWalletUnlocked');
},
onWalletDeleted: () => {
Toast.show({
type: 'info',
text1: 'Wallet has been deleted',
});
console.log('Event', '@WalletEventListener/onWalletDeleted');
},
onLogout: () => {
console.log('Event', '@WalletEventListener/onLogout');
},
onSettingsUpdated: () => {
console.log('Event', '@WalletEventListener/onSettingsUpdated');
},
onCountersUpdated: () => {
console.log('Event', '@WalletEventListener/onCountersUpdated');
},
onCustomerCredentialsReset: () => {
console.log('Event', '@WalletEventListener/onCustomerCredentialsReset');
},
onLostEligibility: () => {
console.log('Event', '@WalletEventListener/onLostEligibility');
},
onCardsUpdated: () => {
console.log('Event', '@WalletEventListener/onCardsUpdated');
},
};
const pushAuthProtocole = {
onRequestReceived(pushAuthenticationRequestId) {
console.log(
'PushAuthenticationRequestFetcher',
'onRequestReceived',
pushAuthenticationRequestId,
);
},
onRequestCancelled(requestId, reason) {
console.log(
'PushAuthenticationRequestFetcher',
'onRequestCancelled',
requestId,
reason,
);
},
};
PushAuthenticationRequestListener().setBackgroundListener(pushAuthProtocole);
PushAuthenticationRequestListener().setForegroundListener(pushAuthProtocole);
WalletEventListener().setBackgroundListener(walletEventListenerProtocole);
WalletEventListener().setForegroundListener(walletEventListenerProtocole);
AppRegistry.registerComponent(appName, () => App);
Example 2 – Listen and trigger appropriate actions inside the application
In this example, we listen for notification events relating to the SCA Wallet status (e.g., onWalletLocked
, onCredentialsReset
, and onWalletDelete
) to dispatch an event from the index.js
(other notifications are handled as the same way as the first example).
Inside the application logic (MobileActions.tsx
), on capturing the event, we display a modal with explanation and a CTA button to disconnect the SCA Wallet.
index.js
js
import {AppRegistry, NativeEventEmitter, NativeModules} from 'react-native';
import {Toast} from 'react-native-toast-message/lib/src/Toast';
import {
WalletEventListener,
PushAuthenticationRequestListener,
} from 'react-native-antelop';
import {name as appName} from './app.json';
import App from './src/App';
// For ios, the EmitterModule must be declared beforehand as describled in: https://reactnative.dev/docs/native-modules-ios
// As example, Add RCTEmitterModule.h and RCTEmitterModule.m inside the ios folder to to register the emitter module
const {EmitterModule} = NativeModules;
const eventEmitter = new NativeEventEmitter(EmitterModule);
const walletEventListenerProtocole = {
onWalletLoaded: () => {
Toast.show({
type: 'info',
text1: 'Wallet has been loaded',
});
console.log('Event', '@WalletEventListener/onWalletLoaded');
},
onWalletLocked: reason => {
console.log('Event', '@WalletEventListener/onWalletLocked', reason);
// dispatch an event in case of WalletLocked
eventEmitter.emit('onWalletUpdated', {type: 'Wallet locked', reason});
},
onWalletUnlocked: () => {
Toast.show({
type: 'info',
text1: 'Wallet has been unlocked',
});
console.log('Event', '@WalletEventListener/onWalletUnlocked');
},
onWalletDeleted: () => {
console.log('Event', '@WalletEventListener/onWalletDeleted');
// dispatch an event in case of WalletDeleted
eventEmitter.emit('onWalletUpdated', {type: 'Wallet deleted'});
},
onLogout: () => {
console.log('Event', '@WalletEventListener/onLogout');
},
onSettingsUpdated: () => {
console.log('Event', '@WalletEventListener/onSettingsUpdated');
},
onCountersUpdated: () => {
console.log('Event', '@WalletEventListener/onCountersUpdated');
},
onCustomerCredentialsReset: () => {
console.log('Event', '@WalletEventListener/onCustomerCredentialsReset');
// dispatch an event in case of CustomerCredentialsReset
eventEmitter.emit('onWalletUpdated', {type: 'Credentials reset'});
},
onLostEligibility: () => {
console.log('Event', '@WalletEventListener/onLostEligibility');
},
onCardsUpdated: () => {
console.log('Event', '@WalletEventListener/onCardsUpdated');
},
};
const pushAuthProtocole = {
onRequestReceived(pushAuthenticationRequestId) {
console.log(
'PushAuthenticationRequestFetcher',
'onRequestReceived',
pushAuthenticationRequestId,
);
},
onRequestCancelled(requestId, reason) {
console.log(
'PushAuthenticationRequestFetcher',
'onRequestCancelled',
requestId,
reason,
);
},
};
PushAuthenticationRequestListener().setBackgroundListener(pushAuthProtocole);
PushAuthenticationRequestListener().setForegroundListener(pushAuthProtocole);
WalletEventListener().setBackgroundListener(walletEventListenerProtocole);
WalletEventListener().setForegroundListener(walletEventListenerProtocole);
AppRegistry.registerComponent(appName, () => App);
MobileActions.tsx
js
...
const [walletStatus, setWalletStatus] = useState<string | undefined>();
...
// Listening to onWalletUpdated to display the modal on received
// Remove listener on unmount
useEffect(() => {
eventEmitter.addListener('onWalletUpdated', data => {
console.log(data);
isConnected && setWalletStatus(data?.type);
});
return () => {
eventEmitter.removeAllListeners('onWalletUpdated');
};
}, [isConnected]);
...
// Display the modal with explanation and CTA button to disconnect the sca wallet
<>
{!!walletStatus && (
<ModalView
title={walletStatus}
description={
'Your wallet has been updated. Please disconnect then re-connect again to the SCA wallet'
}
onCloseModal={() => setWalletStatus(undefined)}
onSubmit={() => {
setWalletStatus(undefined);
disconnect();
}}
/>
)}
</>
Errors
Treezor abstraction layer returns error objects in the following format:
json
{
"code": 449,
"error": "Canceled"
}
Here is the list of errors you may encounter:
HTTP Status code | SDK error | Description |
---|---|---|
401 | Invalid Credentials | Occurs when the passcode of the end user are invalid or provided in an invalid format. |
404 | Wallet Not Found | Occurs when the SCA Wallet doesn't exist (deleted, wrong Id). |
405 | Passcode Authentication Impossible | Occurs when the number of authentication attempts with a passcode is reached. To unblock this situation, you may activate the biometry or reset the passcode (via the app, dashboard, or your own back office with a PIN reset). |
406 | Device Not Eligible | Occurs when the device is not supported (due to the OS version for instance). |
407 | Authentication Error | Occurs when there is an issue with the strong customer authentication (timeout, duplicate, etc.). |
416 | Wallet Not Activated | Occurs when the SCA Wallet is not active. |
423 | Wallet Locked | Occurs when the SCA Wallet is locked. You may unlock the SCA Wallet per your end user's request in the Dashboard or in your own back office. |
428 | Activation Required | Occurs when the end user is active on their device and an action is made from the back office or Dashboard (unlock or reset PIN). The end user must log out and log back in order to reactivate their account or reinitialize their passwords. |
451 | Other Error | Contact Treezor for more information. |
498 | Invalid Activation Code | Occurs when the activation code is invalid, already used, expired or locked during the SCA Wallet initialization or provisioning. |
499 | Canceled | Occurs when the end user cancels the SCA. |
500 | Internal Error | Occurs in case of an unexpected server error. Contact Treezor if the error persists. |
511 | Network Not Available | Occurs when there is a network issue. |