1- import UIKit
1+ import AuthenticationServices
22import CocoaLumberjack
3- import NSURL_IDN
43import GoogleSignIn
4+ import NSURL_IDN
5+ import UIKit
56import WordPressShared
67import WordPressUI
7- import AuthenticationServices
8+ import WordPressKit
89
910// MARK: - WordPressAuthenticator: Public API to deal with WordPress.com and WordPress.org authentication.
1011//
@@ -56,14 +57,9 @@ import AuthenticationServices
5657 ///
5758 @objc public static let WPSigninDidFinishNotification = " WPSigninDidFinishNotification "
5859
59- /// Internal Constants.
60+ /// The host name that identifies magic link URLs
6061 ///
61- private enum Constants {
62- static let authenticationInfoKey = " authenticationInfoKey "
63- static let username = " username "
64- static let emailMagicLinkSource = " emailMagicLinkSource "
65- static let magicLinkUrlPath = " magic-login "
66- }
62+ private static let magicLinkUrlHostname = " magic-login "
6763
6864 // MARK: - Initialization
6965
@@ -126,7 +122,7 @@ import AuthenticationServices
126122 /// Indicates if the received URL is a WordPress.com Authentication Callback.
127123 ///
128124 @objc public func isWordPressAuthUrl( _ url: URL ) -> Bool {
129- let expectedPrefix = configuration. wpcomScheme + " :// " + Constants . magicLinkUrlPath
125+ let expectedPrefix = configuration. wpcomScheme + " :// " + Self . magicLinkUrlHostname
130126 return url. absoluteString. hasPrefix ( expectedPrefix)
131127 }
132128
@@ -138,8 +134,8 @@ import AuthenticationServices
138134
139135 /// Attempts to process the specified URL as a WordPress Authentication Link. Returns *true* on success.
140136 ///
141- @objc public func handleWordPressAuthUrl( _ url: URL , rootViewController: UIViewController ) -> Bool {
142- return WordPressAuthenticator . openAuthenticationURL ( url, fromRootViewController: rootViewController)
137+ @objc public func handleWordPressAuthUrl( _ url: URL , rootViewController: UIViewController , automatedTesting : Bool = false ) -> Bool {
138+ return WordPressAuthenticator . openAuthenticationURL ( url, fromRootViewController: rootViewController, automatedTesting : automatedTesting )
143139 }
144140
145141
@@ -309,68 +305,91 @@ import AuthenticationServices
309305
310306 // MARK: - Authentication Link Helpers
311307
312-
313308 /// Present a signin view controller to handle an authentication link.
314309 ///
315310 /// - Parameters:
316311 /// - url: The authentication URL
317- /// - rootViewController: The view controller to act as the presenter for the signin view controller.
318- /// By convention this is the app's root vc .
312+ /// - rootViewController: The view controller to act as the presenter for the signin view controller. By convention this is the app's root vc.
313+ /// - automatedTesting: for calling this method for automated testing. It won't sync the account or load any other VCs .
319314 ///
320- @objc public class func openAuthenticationURL( _ url: URL , fromRootViewController rootViewController: UIViewController ) -> Bool {
321- guard let token = url. query? . dictionaryFromQueryString ( ) . string ( forKey: " token " ) else {
322- DDLogError ( " Signin Error: The authentication URL did not have the expected path. " )
315+ @objc public class func openAuthenticationURL(
316+ _ url: URL ,
317+ fromRootViewController rootViewController: UIViewController ,
318+ automatedTesting: Bool = false ) -> Bool {
319+
320+ guard let queryDictionary = url. query? . dictionaryFromQueryString ( ) else {
321+ DDLogError ( " Magic link error: we couldn't retrieve the query dictionary from the sign-in URL. " )
323322 return false
324323 }
325-
326- let loginFields = retrieveLoginInfoForTokenAuth ( )
327-
324+
325+ guard let authToken = queryDictionary. string ( forKey: " token " ) else {
326+ DDLogError ( " Magic link error: we couldn't retrieve the authentication token from the sign-in URL. " )
327+ return false
328+ }
329+
330+ guard let flowRawValue = queryDictionary. string ( forKey: " flow " ) else {
331+ DDLogError ( " Magic link error: we couldn't retrieve the flow from the sign-in URL. " )
332+ return false
333+ }
334+
335+ let loginFields = LoginFields ( )
336+
328337 if url. isJetpackConnect {
329338 loginFields. meta. jetpackLogin = true
330339 }
331-
332- let storyboard = Storyboard . emailMagicLink. instance
333- guard let loginController = storyboard. instantiateViewController ( withIdentifier: " LinkAuthView " ) as? NUXLinkAuthViewController else {
334- DDLogInfo ( " App opened with authentication link but couldn't create login screen. " )
340+
341+ // We could just use the flow, but since `MagicLinkFlow` is an ObjC enum, it always
342+ // allows a `default` value. By mapping the ObjC enum to a Swift enum we can avoid that afterwards.
343+ let flow : NUXLinkAuthViewController . Flow
344+
345+ switch MagicLinkFlow ( rawValue: flowRawValue) {
346+ case . signup:
347+ flow = . signup
348+ loginFields. meta. emailMagicLinkSource = . signup
349+ Self . track ( . signupMagicLinkOpened)
350+ case . login:
351+ flow = . login
352+ loginFields. meta. emailMagicLinkSource = . login
353+ Self . track ( . loginMagicLinkOpened)
354+ default :
355+ DDLogError ( " Magic link error: the flow should be either `signup` or `login`. We can't handle an unsupported flow. " )
335356 return false
336357 }
337- loginController. loginFields = loginFields
338- loginController. token = token
339- let controller = loginController
340-
341- if let linkSource = loginFields. meta. emailMagicLinkSource {
342- switch linkSource {
343- case . signup:
344- WordPressAuthenticator . track ( . signupMagicLinkOpened)
345- case . login:
346- WordPressAuthenticator . track ( . loginMagicLinkOpened)
358+
359+ if !automatedTesting {
360+ let storyboard = Storyboard . emailMagicLink. instance
361+ guard let loginVC = storyboard. instantiateViewController ( withIdentifier: " LinkAuthView " ) as? NUXLinkAuthViewController else {
362+ DDLogInfo ( " App opened with authentication link but couldn't create login screen. " )
363+ return false
347364 }
365+ loginVC. loginFields = loginFields
366+
367+ let navController = LoginNavigationController ( rootViewController: loginVC)
368+ navController. modalPresentationStyle = . fullScreen
369+
370+ // The way the magic link flow works some view controller might
371+ // still be presented when the app is resumed by tapping on the auth link.
372+ // We need to do a little work to present the SigninLinkAuth controller
373+ // from the right place.
374+ // - If the rootViewController is not presenting another vc then just
375+ // present the auth controller.
376+ // - If the rootViewController is presenting another NUX vc, dismiss the
377+ // NUX vc then present the auth controller.
378+ // - If the rootViewController is presenting *any* other vc, present the
379+ // auth controller from the presented vc.
380+ let presenter = rootViewController. topmostPresentedViewController
381+ if presenter. isKind ( of: NUXNavigationController . self) || presenter. isKind ( of: LoginNavigationController . self) ,
382+ let parent = presenter. presentingViewController {
383+ parent. dismiss ( animated: false , completion: {
384+ parent. present ( navController, animated: false , completion: nil )
385+ } )
386+ } else {
387+ presenter. present ( navController, animated: false , completion: nil )
388+ }
389+
390+ loginVC. syncAndContinue ( authToken: authToken, flow: flow, isJetpackConnect: url. isJetpackConnect)
348391 }
349-
350- let navController = LoginNavigationController ( rootViewController: controller)
351- navController. modalPresentationStyle = . fullScreen
352-
353- // The way the magic link flow works some view controller might
354- // still be presented when the app is resumed by tapping on the auth link.
355- // We need to do a little work to present the SigninLinkAuth controller
356- // from the right place.
357- // - If the rootViewController is not presenting another vc then just
358- // present the auth controller.
359- // - If the rootViewController is presenting another NUX vc, dismiss the
360- // NUX vc then present the auth controller.
361- // - If the rootViewController is presenting *any* other vc, present the
362- // auth controller from the presented vc.
363- let presenter = rootViewController. topmostPresentedViewController
364- if presenter. isKind ( of: NUXNavigationController . self) || presenter. isKind ( of: LoginNavigationController . self) ,
365- let parent = presenter. presentingViewController {
366- parent. dismiss ( animated: false , completion: {
367- parent. present ( navController, animated: false , completion: nil )
368- } )
369- } else {
370- presenter. present ( navController, animated: false , completion: nil )
371- }
372-
373- deleteLoginInfoForTokenAuth ( )
392+
374393 return true
375394 }
376395
@@ -409,58 +428,6 @@ import AuthenticationServices
409428 return path
410429 }
411430
412-
413- // MARK: - Helpers for Saved Magic Link Info
414-
415- /// Saves certain login information in NSUserDefaults
416- ///
417- /// - Parameter loginFields: The loginFields instance from which to save.
418- ///
419- class func storeLoginInfoForTokenAuth( _ loginFields: LoginFields ) {
420- var dict : [ String : String ] = [
421- Constants . username: loginFields. username
422- ]
423-
424- if let linkSource = loginFields. meta. emailMagicLinkSource {
425- dict [ Constants . emailMagicLinkSource] = String ( linkSource. rawValue)
426- }
427-
428- UserDefaults . standard. set ( dict, forKey: Constants . authenticationInfoKey)
429- }
430-
431-
432- /// Retrieves stored login information if any.
433- ///
434- /// - Returns: A loginFields instance or nil.
435- ///
436- class func retrieveLoginInfoForTokenAuth( ) -> LoginFields {
437-
438- let loginFields = LoginFields ( )
439-
440- guard let dict = UserDefaults . standard. dictionary ( forKey: Constants . authenticationInfoKey) else {
441- return loginFields
442- }
443-
444- if let username = dict [ Constants . username] as? String {
445- loginFields. username = username
446- }
447-
448- if let linkSource = dict [ Constants . emailMagicLinkSource] as? String ,
449- let linkSourceRawValue = Int ( linkSource) {
450- loginFields. meta. emailMagicLinkSource = EmailMagicLinkSource ( rawValue: linkSourceRawValue)
451- }
452-
453- return loginFields
454- }
455-
456-
457- /// Removes stored login information from NSUserDefaults
458- ///
459- class func deleteLoginInfoForTokenAuth( ) {
460- UserDefaults . standard. removeObject ( forKey: Constants . authenticationInfoKey)
461- }
462-
463-
464431 // MARK: - Other Helpers
465432
466433
0 commit comments