From 83bfe9c8da6b4c24f182d9d8cf09a6d76a0494a6 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Mon, 21 Oct 2024 14:14:05 +0100 Subject: [PATCH 01/10] move check for network failure error into the right scope. --- .../OrchestratedBiometricKycViewModel.swift | 2 +- .../OrchestratedDocumentVerificationViewModel.swift | 2 +- Sources/SmileID/Classes/Helpers/LocalStorage.swift | 13 +------------ Sources/SmileID/Classes/Networking/APIError.swift | 11 +++++++++++ .../Classes/SelfieCapture/SelfieViewModel.swift | 2 +- .../View/OrchestratedSelfieCaptureScreen.swift | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift index 20f1a9bf..aea82df2 100644 --- a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift +++ b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift @@ -170,7 +170,7 @@ internal class OrchestratedBiometricKycViewModel: ObservableObject { self.error = error return } - if SmileID.allowOfflineMode, LocalStorage.isNetworkFailure(error: error) { + if SmileID.allowOfflineMode, SmileIDError.isNetworkFailure(error: error) { didSubmitBiometricJob = true DispatchQueue.main.async { self.errorMessageRes = "Offline.Message" diff --git a/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift b/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift index 161bc591..e5746788 100644 --- a/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift +++ b/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift @@ -263,7 +263,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse self.onError(error: error) return } - if SmileID.allowOfflineMode, LocalStorage.isNetworkFailure(error: error) { + if SmileID.allowOfflineMode, SmileIDError.isNetworkFailure(error: error) { didSubmitJob = true DispatchQueue.main.async { self.errorMessageRes = "Offline.Message" diff --git a/Sources/SmileID/Classes/Helpers/LocalStorage.swift b/Sources/SmileID/Classes/Helpers/LocalStorage.swift index ad8b4d9d..4c77bb50 100644 --- a/Sources/SmileID/Classes/Helpers/LocalStorage.swift +++ b/Sources/SmileID/Classes/Helpers/LocalStorage.swift @@ -276,24 +276,13 @@ public class LocalStorage { error: SmileIDError ) throws -> Bool { var didMove = false - if !(SmileID.allowOfflineMode && isNetworkFailure(error: error)) { + if !(SmileID.allowOfflineMode && SmileIDError.isNetworkFailure(error: error)) { try LocalStorage.moveToSubmittedJobs(jobId: jobId) didMove = true } return didMove } - static func isNetworkFailure( - error: SmileIDError - ) -> Bool { - switch error { - case .httpError: - true - default: - false - } - } - public static func toZip(uploadRequest: UploadRequest) throws -> Data { var destinationFolder: String? // Extract directory paths from all images and check for consistency diff --git a/Sources/SmileID/Classes/Networking/APIError.swift b/Sources/SmileID/Classes/Networking/APIError.swift index 90d0ae1a..33fb6f8a 100644 --- a/Sources/SmileID/Classes/Networking/APIError.swift +++ b/Sources/SmileID/Classes/Networking/APIError.swift @@ -12,6 +12,17 @@ public enum SmileIDError: Error { case invalidJobId case fileNotFound(String) case invalidRequestBody + + static func isNetworkFailure( + error: SmileIDError + ) -> Bool { + switch error { + case .httpError: + true + default: + false + } + } } extension SmileIDError: LocalizedError { diff --git a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift index 1b84deda..3ac0e554 100644 --- a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift +++ b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift @@ -438,7 +438,7 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate { self.error = error return } - if SmileID.allowOfflineMode, LocalStorage.isNetworkFailure(error: error) { + if SmileID.allowOfflineMode, SmileIDError.isNetworkFailure(error: error) { DispatchQueue.main.async { self.errorMessageRes = "Offline.Message" self.processingState = .success diff --git a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift index 48c7e933..824e7cca 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift @@ -9,11 +9,11 @@ public struct OrchestratedSelfieCaptureScreen: View { public let showInstructions: Bool public let onResult: SmartSelfieResultDelegate @ObservedObject var viewModel: SelfieViewModel - + @State private var localMetadata = LocalMetadata() @State private var acknowledgedInstructions = false private var originalBrightness = UIScreen.main.brightness - + public init( userId: String, jobId: String, @@ -40,7 +40,7 @@ public struct OrchestratedSelfieCaptureScreen: View { localMetadata: LocalMetadata() ) } - + public var body: some View { if showInstructions, !acknowledgedInstructions { SmartSelfieInstructionsScreen(showAttribution: showAttribution) { From 90f9b9b87b4d4e189f3d86816b6aa34dabb9df1a Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Tue, 22 Oct 2024 15:00:34 +0100 Subject: [PATCH 02/10] remove error message from success subitit --- Sources/SmileID/Classes/Networking/APIError.swift | 11 ++++------- .../View/OrchestratedSelfieCaptureScreen.swift | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/SmileID/Classes/Networking/APIError.swift b/Sources/SmileID/Classes/Networking/APIError.swift index 33fb6f8a..88960a6e 100644 --- a/Sources/SmileID/Classes/Networking/APIError.swift +++ b/Sources/SmileID/Classes/Networking/APIError.swift @@ -12,16 +12,13 @@ public enum SmileIDError: Error { case invalidJobId case fileNotFound(String) case invalidRequestBody - + static func isNetworkFailure( error: SmileIDError ) -> Bool { - switch error { - case .httpError: - true - default: - false - } + guard case let .request(urlError) = error else { return false } + let value = urlError.code == .notConnectedToInternet + return value } } diff --git a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift index 824e7cca..fd608cb4 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift @@ -60,7 +60,7 @@ public struct OrchestratedSelfieCaptureScreen: View { for: "Confirmation.SelfieCaptureComplete" ), successSubtitle: SmileIDResourcesHelper.localizedString( - for: $viewModel.errorMessageRes.wrappedValue ?? "Confirmation.SuccessBody" + for: "Confirmation.SuccessBody" ), successIcon: SmileIDResourcesHelper.CheckBold, errorTitle: SmileIDResourcesHelper.localizedString( From 094da1290c2af25ff583bf0082c4f76094cb552c Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Wed, 23 Oct 2024 20:23:06 +0100 Subject: [PATCH 03/10] break down submit job function into several components. --- .../OrchestratedBiometricKycViewModel.swift | 304 ++++++++++-------- .../Classes/Networking/SmileIDService.swift | 2 +- .../SelfieCapture/SelfieViewModel.swift | 3 +- 3 files changed, 178 insertions(+), 131 deletions(-) diff --git a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift index aea82df2..7ad25941 100644 --- a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift +++ b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift @@ -8,30 +8,30 @@ internal enum BiometricKycStep { internal class OrchestratedBiometricKycViewModel: ObservableObject { // MARK: - Input Properties - + private let userId: String private let jobId: String private let allowNewEnroll: Bool private var extraPartnerParams: [String: String] private let localMetadata = LocalMetadata() private var idInfo: IdInfo - + // MARK: - Other Properties - + internal var selfieFile: URL? internal var livenessFiles: [URL]? private var error: Error? private var didSubmitBiometricJob: Bool = false - + // MARK: - UI Properties - + /// we use `errorMessageRes` to map to the actual code to the stringRes to allow localization, /// and use `errorMessage` to show the actual platform error message that we show if /// `errorMessageRes` is not set by the partner @Published var errorMessageRes: String? @Published var errorMessage: String? @Published @MainActor private(set) var step: BiometricKycStep = .selfie - + init( userId: String, jobId: String, @@ -45,19 +45,20 @@ internal class OrchestratedBiometricKycViewModel: ObservableObject { self.idInfo = idInfo self.extraPartnerParams = extraPartnerParams } - + func onRetry() { if selfieFile != nil { submitJob() } else { - DispatchQueue.main.async { self.step = .selfie } + updateStep(.selfie) } } - + func onFinished(delegate: BiometricKycResultDelegate) { if let selfieFile = selfieFile, - let livenessFiles = livenessFiles, - let selfiePath = getRelativePath(from: selfieFile) { + let livenessFiles = livenessFiles, + let selfiePath = getRelativePath(from: selfieFile) + { delegate.didSucceed( selfieImage: selfiePath, livenessImages: livenessFiles.compactMap { getRelativePath(from: $0) }, @@ -69,130 +70,177 @@ internal class OrchestratedBiometricKycViewModel: ObservableObject { delegate.didError(error: SmileIDError.unknown("onFinish with no result or error")) } } - + func submitJob() { - DispatchQueue.main.async { self.step = .processing(.inProgress) } + updateStep(.processing(.inProgress)) Task { do { - selfieFile = try LocalStorage.getFileByType( - jobId: jobId, - fileType: FileType.selfie - ) - - livenessFiles = try LocalStorage.getFilesByType( - jobId: jobId, - fileType: FileType.liveness - ) - - guard let selfieFile else { - // Set step to .selfieCapture so that the Retry button goes back to this step - DispatchQueue.main.async { self.step = .selfie } - error = SmileIDError.unknown("Error capturing selfie") - return - } - - var allFiles = [URL]() - let infoJson = try LocalStorage.createInfoJsonFile( - jobId: jobId, - idInfo: idInfo.copy(entered: true), - selfie: selfieFile, - livenessImages: livenessFiles - ) - allFiles.append(contentsOf: [selfieFile, infoJson]) - if let livenessFiles { - allFiles.append(contentsOf: livenessFiles) - } - let zipData = try LocalStorage.zipFiles(at: allFiles) - let authRequest = AuthenticationRequest( - jobType: .biometricKyc, - enrollment: false, - jobId: jobId, - userId: userId, - country: idInfo.country, - idType: idInfo.idType - ) - if SmileID.allowOfflineMode { - try LocalStorage.saveOfflineJob( - jobId: jobId, - userId: userId, - jobType: .biometricKyc, - enrollment: false, - allowNewEnroll: allowNewEnroll, - localMetadata: localMetadata, - partnerParams: extraPartnerParams - ) - } - let authResponse = try await SmileID.api.authenticate(request: authRequest) - let prepUploadRequest = PrepUploadRequest( - partnerParams: authResponse.partnerParams.copy(extras: extraPartnerParams), - allowNewEnroll: String(allowNewEnroll), // TODO: - Fix when Michael changes this to boolean - metadata: localMetadata.metadata.items, - timestamp: authResponse.timestamp, - signature: authResponse.signature - ) - let prepUploadResponse: PrepUploadResponse - do { - prepUploadResponse = try await SmileID.api.prepUpload( - request: prepUploadRequest - ) - } catch let error as SmileIDError { - switch error { - case .api("2215", _): - prepUploadResponse = try await SmileID.api.prepUpload( - request: prepUploadRequest.copy(retry: "true") - ) - default: - throw error - } - } - let _ = try await SmileID.api.upload( - zip: zipData, - to: prepUploadResponse.uploadUrl - ) - didSubmitBiometricJob = true - do { - try LocalStorage.moveToSubmittedJobs(jobId: self.jobId) - } catch { - print("Error moving job to submitted directory: \(error)") - self.error = error - DispatchQueue.main.async { self.step = .processing(.error) } - return - } - DispatchQueue.main.async { self.step = .processing(.success) } + try await handleJobSubmission() + updateStep(.processing(.success)) } catch let error as SmileIDError { - do { - _ = try LocalStorage.handleOfflineJobFailure( - jobId: self.jobId, - error: error - ) - } catch { - print("Error moving job to submitted directory: \(error)") - self.error = error - return - } - if SmileID.allowOfflineMode, SmileIDError.isNetworkFailure(error: error) { - didSubmitBiometricJob = true - DispatchQueue.main.async { - self.errorMessageRes = "Offline.Message" - self.step = .processing(.success) - } - } else { - didSubmitBiometricJob = false - print("Error submitting job: \(error)") - let (errorMessageRes, errorMessage) = toErrorMessage(error: error) - self.error = error - DispatchQueue.main.async { - self.errorMessageRes = errorMessageRes - self.errorMessage = errorMessage - self.step = .processing(.error) - } - } + handleSubmissionFailure(error) } catch { didSubmitBiometricJob = false print("Error submitting job: \(error)") self.error = error - DispatchQueue.main.async { self.step = .processing(.error) } + updateStep(.processing(.error)) + } + } + } + + private func handleJobSubmission() async throws { + try fetchRequiredFiles() + + let zipData = try createZipData() + + let authResponse = try await authenticate() + + let preUploadResponse = try await prepareForUpload(authResponse: authResponse) + + try await uploadFiles(zipData: zipData, uploadUrl: preUploadResponse.uploadUrl) + didSubmitBiometricJob = true + + try moveJobToSubmittedDirectory() + } + + private func fetchRequiredFiles() throws { + selfieFile = try LocalStorage.getFileByType( + jobId: jobId, + fileType: FileType.selfie + ) + + livenessFiles = try LocalStorage.getFilesByType( + jobId: jobId, + fileType: FileType.liveness + ) + + guard let selfieFile else { + // Set step to .selfieCapture so that the Retry button goes back to this step + updateStep(.selfie) + error = SmileIDError.unknown("Error capturing selfie") + return + } + } + + private func createZipData() throws -> Data { + var allFiles = [URL]() + let infoJson = try LocalStorage.createInfoJsonFile( + jobId: jobId, + idInfo: idInfo.copy(entered: true), + selfie: selfieFile, + livenessImages: livenessFiles + ) + if let selfieFile { + allFiles.append(contentsOf: [selfieFile, infoJson]) + } + if let livenessFiles { + allFiles.append(contentsOf: livenessFiles) + } + return try LocalStorage.zipFiles(at: allFiles) + } + + private func authenticate() async throws -> AuthenticationResponse { + let authRequest = AuthenticationRequest( + jobType: .biometricKyc, + enrollment: false, + jobId: jobId, + userId: userId, + country: idInfo.country, + idType: idInfo.idType + ) + + if SmileID.allowOfflineMode { + try saveOfflineJobIfAllowed() + } + + return try await SmileID.api.authenticate(request: authRequest) + } + + private func saveOfflineJobIfAllowed() throws { + try LocalStorage.saveOfflineJob( + jobId: jobId, + userId: userId, + jobType: .biometricKyc, + enrollment: false, + allowNewEnroll: allowNewEnroll, + localMetadata: localMetadata, + partnerParams: extraPartnerParams + ) + } + + private func prepareForUpload(authResponse: AuthenticationResponse) async throws -> PrepUploadResponse { + let prepUploadRequest = PrepUploadRequest( + partnerParams: authResponse.partnerParams.copy(extras: extraPartnerParams), + allowNewEnroll: String(allowNewEnroll), // TODO: - Fix when Michael changes this to boolean + metadata: localMetadata.metadata.items, + timestamp: authResponse.timestamp, + signature: authResponse.signature + ) + do { + return try await SmileID.api.prepUpload( + request: prepUploadRequest + ) + } catch let error as SmileIDError { + guard case let .api(errorCode, _) = error, + errorCode == "2215" + else { + throw error } + return try await SmileID.api.prepUpload( + request: prepUploadRequest.copy(retry: "true")) + } + } + + private func uploadFiles(zipData: Data, uploadUrl: String) async throws { + try await SmileID.api.upload( + zip: zipData, + to: uploadUrl + ) + } + + private func moveJobToSubmittedDirectory() throws { + try LocalStorage.moveToSubmittedJobs(jobId: self.jobId) + } + + private func updateStep(_ newStep: BiometricKycStep) { + DispatchQueue.main.async { + self.step = newStep + } + } + + private func updateErrorMessages( + errorMessage: String? = nil, + errorMessageRes: String? = nil + ) { + DispatchQueue.main.async { + self.errorMessage = errorMessage + self.errorMessageRes = errorMessageRes + } + } + + private func handleSubmissionFailure(_ smileIDError: SmileIDError) { + do { + _ = try LocalStorage.handleOfflineJobFailure( + jobId: self.jobId, + error: smileIDError + ) + } catch { + print("Error moving job to submitted directory: \(error)") + self.error = smileIDError + return + } + + if SmileID.allowOfflineMode, SmileIDError.isNetworkFailure(error: smileIDError) { + didSubmitBiometricJob = true + updateErrorMessages(errorMessageRes: "Offline.Message") + updateStep(.processing(.success)) + } else { + didSubmitBiometricJob = false + print("Error submitting job: \(smileIDError)") + let (errorMessageRes, errorMessage) = toErrorMessage(error: smileIDError) + self.error = smileIDError + updateErrorMessages(errorMessage: errorMessage, errorMessageRes: errorMessageRes) + updateStep(.processing(.error)) } } } @@ -205,9 +253,9 @@ extension OrchestratedBiometricKycViewModel: SmartSelfieResultDelegate { ) { submitJob() } - + func didError(error _: Error) { error = SmileIDError.unknown("Error capturing selfie") - DispatchQueue.main.async { self.step = .processing(.error) } + updateStep(.processing(.error)) } } diff --git a/Sources/SmileID/Classes/Networking/SmileIDService.swift b/Sources/SmileID/Classes/Networking/SmileIDService.swift index 4fa0a532..ec917677 100644 --- a/Sources/SmileID/Classes/Networking/SmileIDService.swift +++ b/Sources/SmileID/Classes/Networking/SmileIDService.swift @@ -10,7 +10,7 @@ public protocol SmileIDServiceable { func prepUpload(request: PrepUploadRequest) async throws -> PrepUploadResponse /// Uploads files to S3. The URL should be the one returned by `prepUpload`. - func upload(zip: Data, to url: String) async throws -> Data + @discardableResult func upload(zip: Data, to url: String) async throws -> Data /// Perform a synchronous SmartSelfie Enrollment. The response will include the final result of /// the enrollment. diff --git a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift index 3ac0e554..94b601fd 100644 --- a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift +++ b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift @@ -463,8 +463,7 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate { if let selfieImage = selfieImage, let selfiePath = getRelativePath(from: selfieImage), livenessImages.count == numLivenessImages, - !livenessImages.contains(where: { getRelativePath(from: $0) == nil }) - { + !livenessImages.contains(where: { getRelativePath(from: $0) == nil }) { let livenessImagesPaths = livenessImages.compactMap { getRelativePath(from: $0) } callback.didSucceed( From 86c081e4e34a658a3fc09b78fd5449a3594f1acd Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Wed, 23 Oct 2024 22:44:32 +0100 Subject: [PATCH 04/10] fix the issue with the check when handling offline job failure. don't set success title with error message. --- .../BiometricKYC/OrchestratedBiometricKycScreen.swift | 2 +- .../BiometricKYC/OrchestratedBiometricKycViewModel.swift | 5 ++--- Sources/SmileID/Classes/Helpers/LocalStorage.swift | 6 ++---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycScreen.swift b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycScreen.swift index 33f3ab87..b557f6ac 100644 --- a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycScreen.swift +++ b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycScreen.swift @@ -68,7 +68,7 @@ struct OrchestratedBiometricKycScreen: View { for: "BiometricKYC.Success.Title" ), successSubtitle: SmileIDResourcesHelper.localizedString( - for: $viewModel.errorMessageRes.wrappedValue ?? "BiometricKYC.Success.Subtitle" + for: "BiometricKYC.Success.Subtitle" ), successIcon: SmileIDResourcesHelper.CheckBold, errorTitle: SmileIDResourcesHelper.localizedString(for: "BiometricKYC.Error.Title"), diff --git a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift index 7ad25941..d9c25a5e 100644 --- a/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift +++ b/Sources/SmileID/Classes/BiometricKYC/OrchestratedBiometricKycViewModel.swift @@ -57,8 +57,7 @@ internal class OrchestratedBiometricKycViewModel: ObservableObject { func onFinished(delegate: BiometricKycResultDelegate) { if let selfieFile = selfieFile, let livenessFiles = livenessFiles, - let selfiePath = getRelativePath(from: selfieFile) - { + let selfiePath = getRelativePath(from: selfieFile) { delegate.didSucceed( selfieImage: selfiePath, livenessImages: livenessFiles.compactMap { getRelativePath(from: $0) }, @@ -114,7 +113,7 @@ internal class OrchestratedBiometricKycViewModel: ObservableObject { fileType: FileType.liveness ) - guard let selfieFile else { + guard selfieFile != nil else { // Set step to .selfieCapture so that the Retry button goes back to this step updateStep(.selfie) error = SmileIDError.unknown("Error capturing selfie") diff --git a/Sources/SmileID/Classes/Helpers/LocalStorage.swift b/Sources/SmileID/Classes/Helpers/LocalStorage.swift index 4c77bb50..5f5b7a1e 100644 --- a/Sources/SmileID/Classes/Helpers/LocalStorage.swift +++ b/Sources/SmileID/Classes/Helpers/LocalStorage.swift @@ -276,7 +276,7 @@ public class LocalStorage { error: SmileIDError ) throws -> Bool { var didMove = false - if !(SmileID.allowOfflineMode && SmileIDError.isNetworkFailure(error: error)) { + if !SmileID.allowOfflineMode && !SmileIDError.isNetworkFailure(error: error) { try LocalStorage.moveToSubmittedJobs(jobId: jobId) didMove = true } @@ -302,9 +302,6 @@ public class LocalStorage { throw SmileIDError.fileNotFound("Job not found") } - // Get the URL for the JSON file - let jsonUrl = try LocalStorage.getInfoJsonFile(jobId: finalDestinationFolder) - // Create full URLs for all images let imageUrls = uploadRequest.images.map { imageInfo in URL(fileURLWithPath: finalDestinationFolder).appendingPathComponent(imageInfo.fileName) @@ -313,6 +310,7 @@ public class LocalStorage { var allUrls = imageUrls do { + // Get the URL for the JSON file let jsonUrl = try LocalStorage.getInfoJsonFile(jobId: finalDestinationFolder) allUrls.append(jsonUrl) } catch { From 1d4790cccee157dabe38f66d3ba4fe3083160a05 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Wed, 23 Oct 2024 22:53:52 +0100 Subject: [PATCH 05/10] update changelog. --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 317b082d..8d985f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ # Release Notes + +## [Unreleased] + +### Changed +* Break down `submitJob()` function for BiometricKYC for easier readability and debugging. +* Remove setting process screen sucess state subtitle with `errorMessageRes`. + +### Fixed +* Improve how we handle offline job failure scenario. + ## 10.2.14 ### Changed From e65c9dabdfde398fa76467726844e75c758354f0 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Wed, 23 Oct 2024 22:55:04 +0100 Subject: [PATCH 06/10] update changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d985f70..df893de5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## [Unreleased] ### Changed -* Break down `submitJob()` function for BiometricKYC for easier readability and debugging. +* Split up `submitJob()` functionalities for BiometricKYC for easier readability and debugging. * Remove setting process screen sucess state subtitle with `errorMessageRes`. ### Fixed From 65ccca09721ce49812d4d61445924846bf60d4e7 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Wed, 23 Oct 2024 23:05:09 +0100 Subject: [PATCH 07/10] code formatting. --- ...stratedDocumentVerificationViewModel.swift | 108 +++++++++--------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift b/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift index e5746788..d1eae290 100644 --- a/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift +++ b/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift @@ -18,7 +18,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse internal let captureBothSides: Bool internal let jobType: JobType internal let extraPartnerParams: [String: String] - + // Other properties internal var documentFrontFile: Data? internal var documentBackFile: Data? @@ -29,7 +29,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse internal var didSubmitJob: Bool = false internal var error: Error? var localMetadata: LocalMetadata - + // UI properties @Published var acknowledgedInstructions = false /// we use `errorMessageRes` to map to the actual code to the stringRes to allow localization, @@ -38,7 +38,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse @Published var errorMessageRes: String? @Published var errorMessage: String? @Published var step = DocumentCaptureFlow.frontDocumentCapture - + internal init( userId: String, jobId: String, @@ -62,7 +62,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse self.extraPartnerParams = extraPartnerParams self.localMetadata = localMetadata } - + func onFrontDocumentImageConfirmed(data: Data) { documentFrontFile = data if captureBothSides { @@ -75,18 +75,18 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse } } } - + func onBackDocumentImageConfirmed(data: Data) { documentBackFile = data DispatchQueue.main.async { self.step = .selfieCapture } } - + func acknowledgeInstructions() { acknowledgedInstructions = true } - + func onError(error: Error) { self.error = error stepToRetry = step @@ -94,7 +94,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse self.step = .processing(.error) } } - + func onDocumentBackSkip() { if selfieFile == nil { DispatchQueue.main.async { @@ -104,11 +104,11 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse submitJob() } } - + func onFinished(delegate _: T) { fatalError("Must override onFinished") } - + func submitJob() { Task { do { @@ -118,28 +118,28 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse onError(error: SmileIDError.unknown("Error getting document front file")) return } - + selfieFile = try LocalStorage.getFileByType( jobId: jobId, fileType: FileType.selfie ) - + livenessFiles = try LocalStorage.getFilesByType( jobId: jobId, fileType: FileType.liveness ) - + guard let selfieFile else { // Set step to .selfieCapture so that the Retry button goes back to this step step = .selfieCapture onError(error: SmileIDError.unknown("Error getting selfie file")) return } - + DispatchQueue.main.async { self.step = .processing(.inProgress) } - + var allFiles = [URL]() let frontDocumentUrl = try LocalStorage.createDocumentFile( jobId: jobId, @@ -197,7 +197,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse let authResponse = try await SmileID.api.authenticate(request: authRequest) let prepUploadRequest = PrepUploadRequest( partnerParams: authResponse.partnerParams.copy(extras: self.extraPartnerParams), - allowNewEnroll: String(allowNewEnroll), // TODO: - Fix when Michael changes this to boolean + allowNewEnroll: String(allowNewEnroll), // TODO: - Fix when Michael changes this to boolean metadata: localMetadata.metadata.items, timestamp: authResponse.timestamp, signature: authResponse.signature @@ -209,12 +209,12 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse ) } catch let error as SmileIDError { switch error { - case .api("2215", _): - prepUploadResponse = try await SmileID.api.prepUpload( - request: prepUploadRequest.copy(retry: "true") - ) - default: - throw error + case .api("2215", _): + prepUploadResponse = try await SmileID.api.prepUpload( + request: prepUploadRequest.copy(retry: "true") + ) + default: + throw error } } let _ = try await SmileID.api.upload( @@ -224,16 +224,18 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse didSubmitJob = true do { try LocalStorage.moveToSubmittedJobs(jobId: self.jobId) - self.selfieFile = try LocalStorage.getFileByType( - jobId: jobId, - fileType: FileType.selfie, - submitted: true - ) ?? selfieFile - self.livenessFiles = try LocalStorage.getFilesByType( - jobId: jobId, - fileType: FileType.liveness, - submitted: true - ) ?? [] + self.selfieFile = + try LocalStorage.getFileByType( + jobId: jobId, + fileType: FileType.selfie, + submitted: true + ) ?? selfieFile + self.livenessFiles = + try LocalStorage.getFilesByType( + jobId: jobId, + fileType: FileType.liveness, + submitted: true + ) ?? [] } catch { print("Error moving job to submitted directory: \(error)") self.onError(error: error) @@ -247,16 +249,18 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse error: error ) if didMove { - self.selfieFile = try LocalStorage.getFileByType( - jobId: jobId, - fileType: FileType.selfie, - submitted: true - ) ?? selfieFile - self.livenessFiles = try LocalStorage.getFilesByType( - jobId: jobId, - fileType: FileType.liveness, - submitted: true - ) ?? [] + self.selfieFile = + try LocalStorage.getFileByType( + jobId: jobId, + fileType: FileType.selfie, + submitted: true + ) ?? selfieFile + self.livenessFiles = + try LocalStorage.getFilesByType( + jobId: jobId, + fileType: FileType.liveness, + submitted: true + ) ?? [] } } catch { print("Error moving job to submitted directory: \(error)") @@ -284,7 +288,7 @@ internal class IOrchestratedDocumentVerificationViewModel: Obse } } } - + /// If stepToRetry is ProcessingScreen, we're retrying a network issue, so we need to kick off /// the resubmission manually. Otherwise, we're retrying a capture error, so we just need to /// reset the UI state @@ -310,7 +314,7 @@ extension IOrchestratedDocumentVerificationViewModel: SmartSelfieResultDelegate ) { submitJob() } - + func didError(error: Error) { onError(error: SmileIDError.unknown("Error capturing selfie")) } @@ -322,9 +326,9 @@ internal class OrchestratedDocumentVerificationViewModel: { override func onFinished(delegate: DocumentVerificationResultDelegate) { if let savedFiles, - let selfiePath = getRelativePath(from: selfieFile), - let documentFrontPath = getRelativePath(from: savedFiles.documentFront), - let documentBackPath = getRelativePath(from: savedFiles.documentBack) + let selfiePath = getRelativePath(from: selfieFile), + let documentFrontPath = getRelativePath(from: savedFiles.documentFront), + let documentBackPath = getRelativePath(from: savedFiles.documentBack) { delegate.didSucceed( selfie: selfiePath, @@ -345,13 +349,15 @@ internal class OrchestratedDocumentVerificationViewModel: // swiftlint:disable opening_brace internal class OrchestratedEnhancedDocumentVerificationViewModel: // swiftlint:disable line_length - IOrchestratedDocumentVerificationViewModel + IOrchestratedDocumentVerificationViewModel< + EnhancedDocumentVerificationResultDelegate, EnhancedDocumentVerificationJobResult + > { override func onFinished(delegate: EnhancedDocumentVerificationResultDelegate) { if let savedFiles, - let selfiePath = getRelativePath(from: selfieFile), - let documentFrontPath = getRelativePath(from: savedFiles.documentFront), - let documentBackPath = getRelativePath(from: savedFiles.documentBack) + let selfiePath = getRelativePath(from: selfieFile), + let documentFrontPath = getRelativePath(from: savedFiles.documentFront), + let documentBackPath = getRelativePath(from: savedFiles.documentBack) { delegate.didSucceed( selfie: selfiePath, From 186affb3c9136c72e9ca709bc5e8bfd19d8876bb Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Thu, 24 Oct 2024 09:22:35 +0100 Subject: [PATCH 08/10] remove superfluous disable command. --- .../Model/OrchestratedDocumentVerificationViewModel.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift b/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift index d1eae290..b54ec201 100644 --- a/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift +++ b/Sources/SmileID/Classes/DocumentVerification/Model/OrchestratedDocumentVerificationViewModel.swift @@ -348,7 +348,6 @@ internal class OrchestratedDocumentVerificationViewModel: // swiftlint:disable opening_brace internal class OrchestratedEnhancedDocumentVerificationViewModel: - // swiftlint:disable line_length IOrchestratedDocumentVerificationViewModel< EnhancedDocumentVerificationResultDelegate, EnhancedDocumentVerificationJobResult > From af3e048ee255b58483c6a481ff86cce438dbe80a Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Thu, 24 Oct 2024 09:25:37 +0100 Subject: [PATCH 09/10] update changelog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df893de5..d950d9d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Changed * Split up `submitJob()` functionalities for BiometricKYC for easier readability and debugging. -* Remove setting process screen sucess state subtitle with `errorMessageRes`. +* Remove setting job processing screen sucess state subtitle with `errorMessageRes`. +* Modify how we check for network failure due to internet connection and move the `isNetworkFailure()` function into a more appropriate scope. ### Fixed * Improve how we handle offline job failure scenario. From 66533cf744863189f674b834ad864ad3e1df2d96 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Thu, 24 Oct 2024 10:44:18 +0100 Subject: [PATCH 10/10] update check for network failure. --- Sources/SmileID/Classes/Networking/APIError.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/SmileID/Classes/Networking/APIError.swift b/Sources/SmileID/Classes/Networking/APIError.swift index 88960a6e..fb8a21de 100644 --- a/Sources/SmileID/Classes/Networking/APIError.swift +++ b/Sources/SmileID/Classes/Networking/APIError.swift @@ -16,9 +16,8 @@ public enum SmileIDError: Error { static func isNetworkFailure( error: SmileIDError ) -> Bool { - guard case let .request(urlError) = error else { return false } - let value = urlError.code == .notConnectedToInternet - return value + guard case .request = error else { return false } + return true } }