Skip to content

Commit 90468b2

Browse files
committed
Update connection flow for jetpack setup with application password
1 parent dcc2f24 commit 90468b2

File tree

3 files changed

+166
-61
lines changed

3 files changed

+166
-61
lines changed

WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupView.swift

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@ import protocol WooFoundation.Analytics
77
final class JetpackSetupHostingController: UIHostingController<JetpackSetupView> {
88
private let viewModel: JetpackSetupViewModel
99
private let authentication: Authentication
10-
private let connectionWebViewCredentials: Credentials?
10+
private let wpcomCredentials: Credentials?
1111

1212
init(siteURL: String,
1313
connectionOnly: Bool,
14-
connectionWebViewCredentials: Credentials? = nil,
14+
wpcomCredentials: Credentials? = nil,
1515
stores: StoresManager = ServiceLocator.stores,
1616
authentication: Authentication = ServiceLocator.authenticationManager,
1717
analytics: Analytics = ServiceLocator.analytics,
1818
onStoreNavigation: @escaping (String?) -> Void) {
1919
self.viewModel = JetpackSetupViewModel(siteURL: siteURL,
2020
connectionOnly: connectionOnly,
21+
wpcomCredentials: wpcomCredentials,
2122
stores: stores,
2223
analytics: analytics,
2324
onStoreNavigation: onStoreNavigation)
2425
self.authentication = authentication
25-
self.connectionWebViewCredentials = connectionWebViewCredentials
26+
self.wpcomCredentials = wpcomCredentials
2627
super.init(rootView: JetpackSetupView(viewModel: viewModel))
2728

2829
rootView.webViewPresentationHandler = { [weak self] in
@@ -92,7 +93,7 @@ final class JetpackSetupHostingController: UIHostingController<JetpackSetupView>
9293
guard let self else { return }
9394
self.viewModel.jetpackConnectionInterrupted = true
9495
})
95-
let webView = AuthenticatedWebViewController(viewModel: webViewModel, extraCredentials: connectionWebViewCredentials)
96+
let webView = AuthenticatedWebViewController(viewModel: webViewModel, extraCredentials: wpcomCredentials)
9697
webView.navigationItem.leftBarButtonItem = UIBarButtonItem(title: Localization.cancel,
9798
style: .plain,
9899
target: self,
@@ -327,10 +328,3 @@ private extension JetpackSetupView {
327328
static let interruptedConnectionActionHandlerDelayTime: Double = 0.3
328329
}
329330
}
330-
331-
struct JetpackSetupView_Previews: PreviewProvider {
332-
static var previews: some View {
333-
JetpackSetupView(viewModel: JetpackSetupViewModel(siteURL: "https://test.com", connectionOnly: true))
334-
JetpackSetupView(viewModel: JetpackSetupViewModel(siteURL: "https://test.com", connectionOnly: false))
335-
}
336-
}

WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupViewModel.swift

Lines changed: 160 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import UIKit
33
import Yosemite
44
import enum Alamofire.AFError
55
import enum Networking.NetworkError
6+
import class Networking.AlamofireNetwork
67
import protocol WooFoundation.Analytics
78

89
/// View model for `JetpackSetupView`.
@@ -14,6 +15,7 @@ final class JetpackSetupViewModel: ObservableObject {
1415

1516
private let stores: StoresManager
1617
private let storeNavigationHandler: (_ connectedEmail: String?) -> Void
18+
private let wpcomCredentials: Credentials?
1719

1820
@Published private(set) var setupSteps: [JetpackInstallStep]
1921

@@ -101,12 +103,14 @@ final class JetpackSetupViewModel: ObservableObject {
101103

102104
init(siteURL: String,
103105
connectionOnly: Bool,
106+
wpcomCredentials: Credentials?,
104107
stores: StoresManager = ServiceLocator.stores,
105108
analytics: Analytics = ServiceLocator.analytics,
106109
delayBeforeRetry: Double = Constants.delayBeforeRetry,
107110
onStoreNavigation: @escaping (String?) -> Void = { _ in}) {
108111
self.siteURL = siteURL
109112
self.connectionOnly = connectionOnly
113+
self.wpcomCredentials = wpcomCredentials
110114
self.stores = stores
111115
self.analytics = analytics
112116
self.setupSteps = connectionOnly ? [.connection, .done] : JetpackInstallStep.allCases
@@ -138,14 +142,14 @@ final class JetpackSetupViewModel: ObservableObject {
138142

139143
func startSetup() {
140144
if connectionOnly {
141-
fetchJetpackConnectionURL()
145+
checkJetpackConnection(afterConnection: false)
142146
} else {
143147
retrieveJetpackPluginDetails()
144148
}
145149
}
146150

147151
func didAuthorizeJetpackConnection() {
148-
checkJetpackConnection()
152+
checkJetpackConnection(afterConnection: true)
149153
}
150154

151155
func didEncounterErrorDuringConnection(code: Int?) {
@@ -217,7 +221,7 @@ private extension JetpackSetupViewModel {
217221
if plugin.status == .inactive {
218222
self.activateJetpack()
219223
} else {
220-
self.fetchJetpackConnectionURL()
224+
self.checkJetpackConnection(afterConnection: false)
221225
}
222226
case .failure(let error):
223227
DDLogError("⛔️ Error retrieving Jetpack: \(error)")
@@ -280,9 +284,10 @@ private extension JetpackSetupViewModel {
280284
stores.dispatch(action)
281285
}
282286

287+
/// Jetpack connection flow using web view.
288+
/// Used only for sites with Jetpack plugin versions lower than 14.4.
289+
///
283290
func fetchJetpackConnectionURL() {
284-
currentSetupStep = .connection
285-
trackSetupAfterLogin()
286291
let action = JetpackConnectionAction.fetchJetpackConnectionURL { [weak self] result in
287292
guard let self else { return }
288293
switch result {
@@ -308,50 +313,6 @@ private extension JetpackSetupViewModel {
308313
stores.dispatch(action)
309314
}
310315

311-
func checkJetpackConnection(retryCount: Int = 0) {
312-
guard retryCount <= Constants.maxRetryCount else {
313-
setupFailed = true
314-
if let setupError {
315-
analytics.track(.loginJetpackSetupErrorCheckingJetpackConnection, withError: setupError)
316-
}
317-
return
318-
}
319-
currentConnectionStep = .inProgress
320-
let action = JetpackConnectionAction.fetchJetpackConnectionData { [weak self] result in
321-
guard let self else { return }
322-
switch result {
323-
case .success(let connectionData):
324-
guard let connectedEmail = connectionData.currentUser.wpcomUser?.email else {
325-
DDLogWarn("⚠️ Cannot find connected WPcom user")
326-
let missingWpcomUserError = NSError(domain: Constants.errorDomain,
327-
code: Constants.errorCodeNoWPComUser,
328-
userInfo: [Constants.errorUserInfoReason: Constants.errorUserInfoNoWPComUser])
329-
self.setupError = missingWpcomUserError
330-
self.trackSetupDuringLogin(.loginJetpackSetupCannotFindWPCOMUser, failure: missingWpcomUserError)
331-
// Retry fetching user in case Jetpack sync takes some time.
332-
DispatchQueue.main.asyncAfter(deadline: .now() + delayBeforeRetry) { [weak self] in
333-
self?.checkJetpackConnection(retryCount: retryCount + 1)
334-
}
335-
return
336-
}
337-
338-
self.jetpackConnectedEmail = connectedEmail
339-
self.currentConnectionStep = .authorized
340-
self.currentSetupStep = .done
341-
342-
self.trackSetupDuringLogin(.loginJetpackSetupAllStepsMarkedDone)
343-
self.trackSetupAfterLogin()
344-
case .failure(let error):
345-
DDLogError("⛔️ Error checking Jetpack connection: \(error)")
346-
self.setupError = error
347-
DispatchQueue.main.asyncAfter(deadline: .now() + delayBeforeRetry) { [weak self] in
348-
self?.checkJetpackConnection(retryCount: retryCount + 1)
349-
}
350-
}
351-
}
352-
stores.dispatch(action)
353-
}
354-
355316
func updateErrorMessage() {
356317
guard let setupErrorCode else {
357318
setupErrorDetail = .init(setupErrorMessage: Localization.genericErrorMessage,
@@ -377,6 +338,156 @@ private extension JetpackSetupViewModel {
377338
}
378339
}
379340

341+
// MARK: Handle connection steps
342+
// Ref: pe5sF9-401-p2
343+
private extension JetpackSetupViewModel {
344+
func checkJetpackConnection(afterConnection: Bool, retryCount: Int = 0) {
345+
currentSetupStep = .connection
346+
trackSetupAfterLogin()
347+
guard retryCount <= Constants.maxRetryCount else {
348+
return didFailJetpackConnection()
349+
}
350+
currentConnectionStep = .inProgress
351+
let action = JetpackConnectionAction.fetchJetpackConnectionData { [weak self] result in
352+
guard let self else { return }
353+
switch result {
354+
case .success(let connectionData):
355+
if afterConnection {
356+
checkConnectedUser(data: connectionData, retryCount: retryCount)
357+
} else {
358+
handleJetpackConnectionData(connectionData)
359+
}
360+
case .failure(let error):
361+
DDLogError("⛔️ Error checking Jetpack connection: \(error)")
362+
self.setupError = error
363+
DispatchQueue.main.asyncAfter(deadline: .now() + delayBeforeRetry) { [weak self] in
364+
self?.checkJetpackConnection(afterConnection: afterConnection, retryCount: retryCount + 1)
365+
}
366+
}
367+
}
368+
stores.dispatch(action)
369+
}
370+
371+
func checkConnectedUser(data: JetpackConnectionData, retryCount: Int = 0) {
372+
let connectedEmail = data.currentUser.wpcomUser?.email
373+
if let connectedEmail {
374+
return didCompleteJetpackConnection(connectedEmail: connectedEmail)
375+
}
376+
377+
DDLogWarn("⚠️ Cannot find connected WPcom user")
378+
let missingWpcomUserError = NSError(domain: Constants.errorDomain,
379+
code: Constants.errorCodeNoWPComUser,
380+
userInfo: [Constants.errorUserInfoReason: Constants.errorUserInfoNoWPComUser])
381+
setupError = missingWpcomUserError
382+
trackSetupDuringLogin(.loginJetpackSetupCannotFindWPCOMUser, failure: missingWpcomUserError)
383+
// Retry fetching user in case Jetpack sync takes some time.
384+
DispatchQueue.main.asyncAfter(deadline: .now() + delayBeforeRetry) { [weak self] in
385+
self?.checkJetpackConnection(afterConnection: true, retryCount: retryCount + 1)
386+
}
387+
}
388+
389+
func handleJetpackConnectionData(_ data: JetpackConnectionData) {
390+
if let connectedEmail = data.currentUser.wpcomUser?.email {
391+
return didCompleteJetpackConnection(connectedEmail: connectedEmail)
392+
}
393+
394+
if let isRegistered = data.isRegistered {
395+
return handleSiteRegisterResult(isRegistered: isRegistered, blogID: data.blogID)
396+
}
397+
398+
/// Check site info if `isRegistered` is unavailable.
399+
stores.dispatch(WordPressSiteAction.fetchSiteInfo(siteURL: siteURL, completion: { [weak self] result in
400+
guard let self else { return }
401+
switch result {
402+
case .success(let site):
403+
if site.isJetpackThePluginInstalled {
404+
/// `isRegistered` is unavailable due to outdated Jetpack. Proceed with web flow.
405+
fetchJetpackConnectionURL()
406+
} else {
407+
/// For Jetpack-connected sites, `isRegistered` is not returned. Check for `connectionOwner` instead.
408+
handleSiteRegisterResult(isRegistered: data.connectionOwner != nil, blogID: data.blogID)
409+
}
410+
case .failure(let error):
411+
DDLogWarn("⛔️ Cannot fetch site info")
412+
setupError = error
413+
didFailJetpackConnection()
414+
}
415+
}))
416+
}
417+
418+
func handleSiteRegisterResult(isRegistered: Bool, blogID: Int64?) {
419+
if let blogID, isRegistered {
420+
provisionSiteConnection(blogID: blogID)
421+
} else {
422+
registerSiteConnection()
423+
}
424+
}
425+
426+
func registerSiteConnection() {
427+
stores.dispatch(JetpackConnectionAction.registerSite(completion: { [weak self] result in
428+
guard let self else { return }
429+
switch result {
430+
case .success(let blogID):
431+
provisionSiteConnection(blogID: blogID)
432+
case .failure(let error):
433+
setupError = error
434+
didFailJetpackConnection()
435+
}
436+
}))
437+
}
438+
439+
func provisionSiteConnection(blogID: Int64) {
440+
stores.dispatch(JetpackConnectionAction.provisionConnection(completion: { [weak self] result in
441+
guard let self else { return }
442+
switch result {
443+
case .success(let response):
444+
finalizeSiteConnection(blogID: blogID, provisionResponse: response)
445+
case .failure(let error):
446+
setupError = error
447+
didFailJetpackConnection()
448+
}
449+
}))
450+
}
451+
452+
func finalizeSiteConnection(blogID: Int64, provisionResponse: JetpackConnectionProvisionResponse) {
453+
guard let wpcomCredentials else {
454+
return // TODO: what now?
455+
}
456+
let network = AlamofireNetwork(credentials: wpcomCredentials)
457+
stores.dispatch(JetpackConnectionAction.finalizeConnection(
458+
siteID: blogID,
459+
siteURL: siteURL,
460+
provisionResponse: provisionResponse,
461+
network: network
462+
) { [weak self] result in
463+
guard let self else { return }
464+
switch result {
465+
case .success:
466+
checkJetpackConnection(afterConnection: true)
467+
case .failure(let error):
468+
setupError = error
469+
didFailJetpackConnection()
470+
}
471+
})
472+
}
473+
474+
func didCompleteJetpackConnection(connectedEmail: String) {
475+
jetpackConnectedEmail = connectedEmail
476+
currentConnectionStep = .authorized
477+
currentSetupStep = .done
478+
479+
trackSetupDuringLogin(.loginJetpackSetupAllStepsMarkedDone)
480+
trackSetupAfterLogin()
481+
}
482+
483+
func didFailJetpackConnection() {
484+
setupFailed = true
485+
if let setupError {
486+
analytics.track(.loginJetpackSetupErrorCheckingJetpackConnection, withError: setupError)
487+
}
488+
}
489+
}
490+
380491
// MARK: Subtypes
381492
//
382493
extension JetpackSetupViewModel {

WooCommerce/Classes/ViewRelated/JetpackSetup/JetpackSetupCoordinator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ private extension JetpackSetupCoordinator {
222222
}
223223
let setupUI = JetpackSetupHostingController(siteURL: site.url,
224224
connectionOnly: requiresConnectionOnly,
225-
connectionWebViewCredentials: credentials,
225+
wpcomCredentials: credentials,
226226
onStoreNavigation: { [weak self] _ in
227227
DDLogInfo("🎉 Jetpack setup completes!")
228228
self?.rootViewController.topmostPresentedViewController.dismiss(animated: true, completion: {

0 commit comments

Comments
 (0)