iOS Components v3.8.0
Adds traceability and more granular outcomes across authentication, initialization, and biometric login, plus new tokenizationTokenize Replace a card's primary account number (PAN) with a unique digital token that stands in for the real card during a transaction. When a cardholder adds a card to Apple Pay or Google Pay via push provisioning, the wallet provider stores a device-specific token rather than the underlying PAN, so the real card number isn't exposed on the device or shared with merchants. and detokenization helpers for our secure UI components.
Added
- Updated the return types of existing methods to also include a
traceabilityvalue, which helps debug potential issues. - Added an asynchronous
setUserToken(token:), returning anAssociateTokenResult. This interface provides more granular outcomes of the operation. - Added
startBiometricLogin(viewController:), an asynchronous method returningBiometricsLoginResult. - Added
updateFCMToken(_:), an asynchronous method returningUpdateFCMTokenResult. - Added dictionary and array extensions for
secureInputsMatch. - Added a dictionary extension for
tokenise, returningGroupTokenisationResult. - Added new
tokenisemethods to our secure text fields, returningTokenisationResult. - Added
detokenise(_:)toSecureLabeland its subclasses. - Added an asynchronous
initialize(environment:uiKey:), returning anInitializationResult. This interface provides more granular outcomes of the operation, including the authentication type configured for your project andtraceability. - Added
traceabilitytoBiometricsEnrollmentResult,BiometricsChallengeResult, and everyKYCEventcase, helping identify the logs for a given operation. - Added
AuthenticationType, describing whether a project is configured for passcode or password authentication. - Added Spanish and Portuguese localizations.
Changed
- Made
identityonWeavrSecureLoginDataoptional, to support "Single User Multiple Identities" login scenarios where the token isn't yet associated with an identity. - Improved the UX of the enrollment flow.
- Renamed
UXComponetGroupProtocolandUXComponetGrouptoUXComponentGroupProtocolandUXComponentGrouprespectively, fixing a typo in their names.
Deprecated
- Deprecated
setUserToken(token:, completion:)in favor of the asynchronoussetUserToken(token:), returning anAssociateTokenResult. - Deprecated
startBiometricPSALogin(completion:),startBiometricPSALogin(completion:onForgotPasscode:), andstartBiometricLogin(completion:)in favor of the asynchronousstartBiometricLogin(viewController:), returning aBiometricsLoginResult. - Deprecated
UXComponetGroupProtocolandUXComponetGroupin favor ofUXComponentGroupProtocolandUXComponentGroup(typo fix). - Deprecated
updateDeviceToken(fcmToken:)in favor ofupdateFCMToken(_:), as it providestraceabilityand a clearer outcome representation. - Deprecated
UXComponentGroupandUXComponentGroupProtocolin favor of the array and dictionary extensions forsecureInputsMatchandtokenise. - Deprecated the
createTokenmethods ofUXComponentGroupandUXComponentGroupProtocol. - Deprecated the
setTokeniseTextmethods ofSecureLabeland its subclasses in favor ofdetokenise(_:). - Deprecated
initialize(env:uiKey:completion:)andinitialize(environment:uiKey:completion:)in favor of the asynchronousinitialize(environment:uiKey:), returning anInitializationResult. - Deprecated
UXComponentsStatusin favor ofInitializationResult.
Fix
- Ensuring that
clearComponentsCacheclears only data related to the SDK (previously cleared UserDefaults using the app domain).
Migration guide
Adopting these APIs is optional: every deprecated method keeps working and internally calls its replacement, so you can migrate call site by call site.
Initializing the SDK
initialize(environment:uiKey:) is now asynchronous and returns an InitializationResult, replacing the completion-based overloads and UXComponentsStatus.
// Before
UXComponents.initialize(environment: environment, uiKey: uiKey) { result in
switch result {
case .success(let status):
if status.userNeedsRelogin {
// Prompt the user to log in again.
}
case .failure(let error):
// Handle the error.
}
}
// After
let result = await UXComponents.initialize(environment: environment, uiKey: uiKey)
switch result {
case .initialized(let isBiometricsEnabled, let authenticationType, let userNeedsRelogin, let traceability):
if userNeedsRelogin {
// Prompt the user to log in again.
}
case .serverError(let code, let message, let traceability):
// Handle a server-side error. Can retry the initialisation.
case .networkFailure(let cause, let traceability):
// Handle a network error. Can retry the initialisation.
// These errors shouldn't commonly occur, they mostly relate
// to failures to store data (device runs out), or other unlikely
// scenarios like iOS failing to create a keychain.
//
// Feel free to retry, but most likely you'll need to point the user
// to your support team, providing the traceability id, so we can
// assist debugging the root cause.
case .setupError(let cause, let error, let traceability):
// Handle a local setup error, e.g. a keychain failure.
case .failure(let cause, let traceability):
// Handle any other error.
}
authenticationType tells you whether the project is configured for .passcode or .password authentication.
Associating a user token
setUserToken(token:) is now asynchronous and returns an AssociateTokenResult.
// Before
UXComponents.setUserToken(token: token) { result in
switch result {
case .success:
break
case .failure(let error):
// Handle the error.
}
}
// After
let result = await UXComponents.setUserToken(token: token)
switch result {
case .success(let traceability):
break
case .serverError(let code, let message, let traceability):
// The server rejected the token, e.g. it's invalid or has expired.
// Fetch a fresh token from your backend and call setUserToken(token:) again.
case .networkFailure(let cause, let traceability):
// The device couldn't reach the server. Retry once connectivity is restored.
case .failure(let cause, let traceability):
// An unexpected error occurred. Retry, and share `traceability` with Weavr's
// support team if it keeps happening.
}
Biometric login
startBiometricLogin(viewController:) replaces startBiometricPSALogin and the completion-based startBiometricLogin, and returns a BiometricsLoginResult.
// Before
UXComponents.psa.startBiometricLogin(completion: { result in
switch result {
case .success(let loginData):
// Handle the successful login.
case .failure(let error):
// Handle the error.
}
}, onForgotPasscode: {
// Handle the user tapping "Forgot passcode".
})
// After
let result = await UXComponents.psa.startBiometricLogin(viewController: viewController)
switch result {
case .success(let loginData, let traceability):
// Handle the successful login.
case .userEnteredInvalidFallbackPassword(let code, let message, let traceability):
// The user's fallback password (used when biometrics fail or are unavailable) was
// wrong. Surface `message` inline and let them retry; `code` can reflect a lockout
// after too many attempts, in which case fall back to standard password login.
case .userDeclinedChallenge(let traceability):
// The user cancelled the OS biometric prompt. This isn't an error - dismiss the flow
// quietly and let them use standard login instead.
case .cryptoError(let error, let message, let status, let traceability):
// The device's local key material is broken or missing, e.g. after a device restore.
// The user needs to re-enroll biometrics; prompt the enrollment flow again.
case .serverError(let code, let message, let traceability):
// The server rejected the request. Show a generic error and let the user retry.
case .networkFailure(let cause, let traceability):
// The device couldn't reach the server. Retry once connectivity is restored.
case .failure(let cause, let traceability):
// An unexpected error occurred. Retry, and share `traceability` with Weavr's
// support team if it keeps happening.
}
The "Forgot passcode" flow is presented as part of the viewController-driven flow, so onForgotPasscode no longer needs a separate callback.
Updating the FCM token
updateFCMToken(_:) replaces updateDeviceToken(fcmToken:) and returns an UpdateFCMTokenResult.
// Before
let result = await UXComponents.psa.updateDeviceToken(fcmToken: token)
switch result {
case .success(let readyForEnrollment):
break
case .failure(let error):
// Handle the error.
}
// After
let result = await UXComponents.psa.updateFCMToken(token)
switch result {
case .readyForEnrollment(let traceability):
// The device can now receive push challenges. Safe to show the biometrics
// enrollment CTA.
case .notReadyForEnrollment(let traceability):
// This is returned when the programme does not support Biometrics or it's not
// properly set-up. Share the traceability with us to help diagnose the root cause.
case .serverError(let code, let message, let traceability):
// The server rejected the request. Retry, e.g. on the next app launch or before enrolling the user.
case .networkError(let cause, let traceability):
// The device couldn't reach the server. Retry once connectivity is restored.
case .failure(let cause, let traceability):
// An unexpected error occurred. Retry, and share `traceability` with Weavr's
// support team if it keeps happening.
}
Matching and tokenizing groups of secure fields
UXComponentGroup and UXComponentGroupProtocol are deprecated in favor of array and dictionary extensions on SecureTextField.
// Before
let group = UXComponents.createGroup(components: [passwordField, passwordFieldTwo])
let matches = group.match()
group.createToken(fieldKeys: ["password", "passwordTwo"]) { result in
switch result {
case .success(let tokens):
break
case .failure(let error):
// Handle the error.
}
}
// After
let matches = [passwordField, passwordFieldTwo].secureInputsMatch
// Also works with dictionaries:
// let matches = [
// "password": passwordField,
// "passwordTwo": passwordFieldTwo,
// ].secureInputsMatch
// tokenise() is available directly on a single SecureTextField, returning a TokenisationResult:
let singleResult = await passwordField.tokenise()
switch singleResult {
case .success(let token, let traceability):
break
case .invalidInput(let traceability):
// The field value failed local validation before any request was sent, e.g. an
// empty field. Show an inline validation error instead of retrying.
case .serverError(let code, let message, let traceability):
// The server rejected the request. Show a generic error and let the user retry.
case .networkFailure(let cause, let traceability):
// The device couldn't reach the server. Retry once connectivity is restored.
case .failure(let cause, let traceability):
// An unexpected error occurred. Retry, and share `traceability` with Weavr's
// support team if it keeps happening.
}
// It's also available on dictionaries of SecureTextField, returning a GroupTokenisationResult:
let groupResult = await [
"password": passwordField,
"passwordTwo": passwordFieldTwo,
].tokenise()
switch groupResult {
case .success(let tokenisedValues, let traceability):
// tokenised values is a dictionary of string -> string, where the key
// is the same used in your dictionary, and the value is the tokenised
// value for the specific field.
break
case .invalidInput(let traceability):
// The field values failed local validation before any request was sent, e.g. an
// empty field. Show an inline validation error instead of retrying.
case .serverError(let code, let message, let traceability):
// The server rejected the request. Show a generic error and let the user retry.
case .networkFailure(let cause, let traceability):
// The device couldn't reach the server. Retry once connectivity is restored.
case .failure(let cause, let traceability):
// An unexpected error occurred. Retry, and share `traceability` with Weavr's
// support team if it keeps happening.
}
If you're only fixing the typo in UXComponetGroup/UXComponetGroupProtocol, rename them to UXComponentGroup/UXComponentGroupProtocol - both are aliases for the same deprecated type.
Detokenizing labels
detokenise(_:) replaces setTokeniseText(toDetokenise:callback:) on SecureLabel and its subclasses, and returns a DetokenisationResult.
// Before
secureLabel.setTokeniseText(toDetokenise: token) { result in
switch result {
case .success:
break
case .failure(let error):
// Handle the error.
}
}
// After
let result = await secureLabel.detokenise(token)
switch result {
case .success(let traceability):
break
case .serverError(let code, let message, let traceability):
// The server rejected the token, e.g. it's no longer valid or has expired.
// Consider re-tokenising the underlying value and detokenising again.
case .networkFailure(let cause, let traceability):
// The device couldn't reach the server. Retry once connectivity is restored.
case .failure(let cause, let traceability):
// An unexpected error occurred. Retry, and share `traceability` with Weavr's
// support team if it keeps happening.
}