Skip to main content

iOS Components v3.8.0

· 8 min read

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 traceability value, which helps debug potential issues.
  • Added an asynchronous setUserToken(token:), returning an AssociateTokenResult. This interface provides more granular outcomes of the operation.
  • Added startBiometricLogin(viewController:), an asynchronous method returning BiometricsLoginResult.
  • Added updateFCMToken(_:), an asynchronous method returning UpdateFCMTokenResult.
  • Added dictionary and array extensions for secureInputsMatch.
  • Added a dictionary extension for tokenise, returning GroupTokenisationResult.
  • Added new tokenise methods to our secure text fields, returning TokenisationResult.
  • Added detokenise(_:) to SecureLabel and its subclasses.
  • Added an asynchronous initialize(environment:uiKey:), returning an InitializationResult. This interface provides more granular outcomes of the operation, including the authentication type configured for your project and traceability.
  • Added traceability to BiometricsEnrollmentResult, BiometricsChallengeResult, and every KYCEvent case, 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 identity on WeavrSecureLoginData optional, 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 UXComponetGroupProtocol and UXComponetGroup to UXComponentGroupProtocol and UXComponentGroup respectively, fixing a typo in their names.

Deprecated

  • Deprecated setUserToken(token:, completion:) in favor of the asynchronous setUserToken(token:), returning an AssociateTokenResult.
  • Deprecated startBiometricPSALogin(completion:), startBiometricPSALogin(completion:onForgotPasscode:), and startBiometricLogin(completion:) in favor of the asynchronous startBiometricLogin(viewController:), returning a BiometricsLoginResult.
  • Deprecated UXComponetGroupProtocol and UXComponetGroup in favor of UXComponentGroupProtocol and UXComponentGroup (typo fix).
  • Deprecated updateDeviceToken(fcmToken:) in favor of updateFCMToken(_:), as it provides traceability and a clearer outcome representation.
  • Deprecated UXComponentGroup and UXComponentGroupProtocol in favor of the array and dictionary extensions for secureInputsMatch and tokenise.
  • Deprecated the createToken methods of UXComponentGroup and UXComponentGroupProtocol.
  • Deprecated the setTokeniseText methods of SecureLabel and its subclasses in favor of detokenise(_:).
  • Deprecated initialize(env:uiKey:completion:) and initialize(environment:uiKey:completion:) in favor of the asynchronous initialize(environment:uiKey:), returning an InitializationResult.
  • Deprecated UXComponentsStatus in favor of InitializationResult.

Fix

  • Ensuring that clearComponentsCache clears 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.
}