diff --git a/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginView.swift b/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginView.swift index be4d93ca551..288ab2f3c2a 100644 --- a/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginView.swift +++ b/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginView.swift @@ -134,6 +134,7 @@ struct WPComEmailLoginView_Previews: PreviewProvider { static var previews: some View { WPComEmailLoginView(viewModel: .init(siteURL: "https://example.com", requiresConnectionOnly: true, + allowAccountCreation: false, onPasswordUIRequest: { _ in }, onMagicLinkUIRequest: { _ in }, onError: { _ in })) diff --git a/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginViewModel.swift b/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginViewModel.swift index 744486c9189..7ad394ac81b 100644 --- a/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginViewModel.swift +++ b/WooCommerce/Classes/Authentication/WPComLogin/WPComEmailLoginViewModel.swift @@ -2,6 +2,8 @@ import Combine import UIKit import WordPressAuthenticator import protocol WooFoundation.Analytics +import enum WordPressKit.WordPressAPIError +import struct WordPressKit.WordPressComRestApiEndpointError /// A protocol used to mock `WordPressComAccountService` for unit tests. protocol WordPressComAccountServiceProtocol { @@ -21,6 +23,7 @@ final class WPComEmailLoginViewModel: ObservableObject { let termsAttributedString: NSAttributedString + private let allowAccountCreation: Bool private let accountService: WordPressComAccountServiceProtocol private let analytics: Analytics private let onPasswordUIRequest: (String) -> Void @@ -31,12 +34,14 @@ final class WPComEmailLoginViewModel: ObservableObject { init(siteURL: String, requiresConnectionOnly: Bool, + allowAccountCreation: Bool, debounceDuration: Double = Constants.fieldDebounceDuration, accountService: WordPressComAccountServiceProtocol = WordPressComAccountService(), analytics: Analytics = ServiceLocator.analytics, onPasswordUIRequest: @escaping (String) -> Void, onMagicLinkUIRequest: @escaping (String) -> Void, onError: @escaping (String) -> Void) { + self.allowAccountCreation = allowAccountCreation self.analytics = analytics self.accountService = accountService self.onPasswordUIRequest = onPasswordUIRequest @@ -78,8 +83,16 @@ final class WPComEmailLoginViewModel: ObservableObject { } await startAuthentication(email: email, isPasswordlessAccount: passwordless) } catch { - analytics.track(event: .JetpackSetup.loginFlow(step: .emailAddress, failure: error)) - onError(error.localizedDescription) + if allowAccountCreation, + let apiError = error as? WordPressAPIError, + case let .endpointError(endpointError) = apiError, + endpointError.apiErrorCode == Constants.unknownUserErrorCode { + // The user does not exist yet, trigger magic link flow for account creation + await requestAuthenticationLink(email: email, forAccountCreation: true) + } else { + analytics.track(event: .JetpackSetup.loginFlow(step: .emailAddress, failure: error)) + onError(error.localizedDescription) + } } } @@ -93,12 +106,12 @@ final class WPComEmailLoginViewModel: ObservableObject { } @MainActor - func requestAuthenticationLink(email: String) async { + func requestAuthenticationLink(email: String, forAccountCreation: Bool = false) async { do { try await withCheckedThrowingContinuation { continuation in accountService.requestAuthenticationLink(for: email, jetpackLogin: false, - createAccountIfNotFound: false, + createAccountIfNotFound: forAccountCreation, success: { continuation.resume() }, failure: { error in @@ -119,6 +132,7 @@ extension WPComEmailLoginViewModel { static let jetpackTermsURL = "https://jetpack.com/redirect/?source=wpcom-tos&site=" static let jetpackShareDetailsURL = "https://jetpack.com/redirect/?source=jetpack-support-what-data-does-jetpack-sync&site=" static let wpcomErrorCodeKey = "WordPressComRestApiErrorCodeKey" + static let unknownUserErrorCode = "unknown_user" } enum Localization { diff --git a/WooCommerce/Classes/ViewRelated/JetpackSetup/JetpackSetupCoordinator.swift b/WooCommerce/Classes/ViewRelated/JetpackSetup/JetpackSetupCoordinator.swift index 6a39bf71323..eecb4449bcc 100644 --- a/WooCommerce/Classes/ViewRelated/JetpackSetup/JetpackSetupCoordinator.swift +++ b/WooCommerce/Classes/ViewRelated/JetpackSetup/JetpackSetupCoordinator.swift @@ -1,4 +1,5 @@ import UIKit +import Experiments import Yosemite import enum Networking.NetworkError import class Networking.AlamofireNetwork @@ -16,6 +17,7 @@ final class JetpackSetupCoordinator { private var jetpackConnectedEmail: String? private let stores: StoresManager private let analytics: Analytics + private let featureFlagService: FeatureFlagService private let dotcomAuthScheme: String private var loginNavigationController: LoginNavigationController? @@ -24,6 +26,7 @@ final class JetpackSetupCoordinator { private lazy var emailLoginViewModel: WPComEmailLoginViewModel = { .init(siteURL: site.url, requiresConnectionOnly: requiresConnectionOnly, + allowAccountCreation: featureFlagService.isFeatureFlagEnabled(.jetpackSetupWPComAccountCreation), onPasswordUIRequest: showPasswordUI(email:), onMagicLinkUIRequest: showMagicLinkUI(email:), onError: { [weak self] message in @@ -40,13 +43,15 @@ final class JetpackSetupCoordinator { dotcomAuthScheme: String = ApiCredentials.dotcomAuthScheme, rootViewController: UIViewController, stores: StoresManager = ServiceLocator.stores, - analytics: Analytics = ServiceLocator.analytics) { + analytics: Analytics = ServiceLocator.analytics, + featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) { self.site = site self.dotcomAuthScheme = dotcomAuthScheme self.requiresConnectionOnly = false // to be updated later after fetching Jetpack status self.rootViewController = rootViewController self.stores = stores self.analytics = analytics + self.featureFlagService = featureFlagService /// the authenticator needs to be initialized with configs /// to be used for requesting authentication link and handle login later.