diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift index ca9896d6cc..23b8cb68f6 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift @@ -27,6 +27,16 @@ final class ActionsHandler { self.step = step } + func currentAction() -> Action? { + guard let lastExecutedActionIndex = self.lastExecutedActionIndex else { return nil } + + if lastExecutedActionIndex < step.actions.count { + return step.actions[lastExecutedActionIndex] + } else { + return nil + } + } + func nextAction() -> Action? { guard let lastExecutedActionIndex = self.lastExecutedActionIndex else { // If last executed action index is nil. Means we didn't execute any action, so we return the first action. diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift index 5b64fd7634..ff1e23a60a 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift @@ -38,6 +38,7 @@ protocol DataBrokerOperation: CCFCommunicationDelegate { var continuation: CheckedContinuation? { get set } var extractedProfile: ExtractedProfile? { get set } var shouldRunNextStep: () -> Bool { get } + var retriesCountOnError: Int { get set } func run(inputValue: InputValue, webViewHandler: WebViewHandler?, @@ -46,6 +47,7 @@ protocol DataBrokerOperation: CCFCommunicationDelegate { showWebView: Bool) async throws -> ReturnValue func executeNextStep() async + func executeCurrentAction() async } extension DataBrokerOperation { @@ -103,6 +105,15 @@ extension DataBrokerOperation { } } + if action as? GetCaptchaInfoAction != nil { + // Captcha is a third-party resource that sometimes takes more time to load + // if we are not able to get the captcha information. We will try to run the action again + // instead of failing the whole thing. + // + // https://app.asana.com/0/1203581873609357/1205476538384291/f + retriesCountOnError = 3 + } + if let extractedProfile = self.extractedProfile { await webViewHandler?.execute(action: action, data: .extractedProfile(extractedProfile)) } else { @@ -191,7 +202,23 @@ extension DataBrokerOperation { } func onError(error: DataBrokerProtectionError) async { - await webViewHandler?.finish() - failed(with: error) + if retriesCountOnError > 0 { + await executeCurrentAction() + } else { + await webViewHandler?.finish() + failed(with: error) + } + } + + func executeCurrentAction() async { + let waitTimeUntilRunningTheActionAgain: TimeInterval = 3 + try? await Task.sleep(nanoseconds: UInt64(waitTimeUntilRunningTheActionAgain) * 1_000_000_000) + + if let currentAction = self.actionsHandler?.currentAction() { + retriesCountOnError -= 1 + await runNextAction(currentAction) + } else { + await onError(error: .unknown("No current action to execute")) + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift index 9965cd164b..1fbc344f73 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift @@ -38,6 +38,7 @@ final class OptOutOperation: DataBrokerOperation { var stageCalculator: DataBrokerProtectionStageDurationCalculator? private let operationAwaitTime: TimeInterval let shouldRunNextStep: () -> Bool + var retriesCountOnError: Int = 0 init(privacyConfig: PrivacyConfigurationManaging, prefs: ContentScopeProperties, @@ -97,6 +98,7 @@ final class OptOutOperation: DataBrokerOperation { } func executeNextStep() async { + retriesCountOnError = 0 // We reset the retries on error when it is successful os_log("OPTOUT Waiting %{public}f seconds...", log: .action, operationAwaitTime) try? await Task.sleep(nanoseconds: UInt64(operationAwaitTime) * 1_000_000_000) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift index 66fa4bb3ec..66293f1552 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift @@ -38,13 +38,14 @@ final class ScanOperation: DataBrokerOperation { var stageCalculator: DataBrokerProtectionStageDurationCalculator? private let operationAwaitTime: TimeInterval let shouldRunNextStep: () -> Bool + var retriesCountOnError: Int = 0 init(privacyConfig: PrivacyConfigurationManaging, prefs: ContentScopeProperties, query: BrokerProfileQueryData, emailService: EmailServiceProtocol = EmailService(), captchaService: CaptchaServiceProtocol = CaptchaService(), - operationAwaitTime: TimeInterval = 1, + operationAwaitTime: TimeInterval = 3, shouldRunNextStep: @escaping () -> Bool ) { self.privacyConfig = privacyConfig @@ -92,6 +93,7 @@ final class ScanOperation: DataBrokerOperation { } func executeNextStep() async { + retriesCountOnError = 0 // We reset the retries on error when it is successful os_log("SCAN Waiting %{public}f seconds...", log: .action, operationAwaitTime) try? await Task.sleep(nanoseconds: UInt64(operationAwaitTime) * 1_000_000_000)