-
Notifications
You must be signed in to change notification settings - Fork 976
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Connect] Support opening authenticated web views (#4144)
## Summary Adds support for the `openAuthenticatedWebView` JS messenger. When the web view sends this message to the mobile SDK, we open an `ASWebAuthenticatedSession` to the given URL and then call `window.returnedFromAuthenticatedWebView` with the return URL. Also adds id to `returnedFromAuthenticatedWebView` payload so that web layer can associate the callback with the original open request. ## Motivation https://jira.corp.stripe.com/browse/MXMOBILE-2578 ## Testing | Scenario | Unit test coverage | Manually tested | | -------------------------------- | ------------------ | ---------------- | | User cancel action | ✓ | ✓ modal + navbar | | Error state: multiple sessions | ✓ | ✓ | | Error state: session can't start | ✓ | ✓ background app | | Error state: no window | ✓ | | | Error state: misc error | ✓ | | | Successfully redirect | ✓ | ✓ | Because this callback is not yet implemented on web, manual testing was performed by directly invoking the handler in the Safari debug console (sound on): https://github.com/user-attachments/assets/f8a0b764-6d4d-4d68-8d95-fd325dae3fee
- Loading branch information
1 parent
cafce5a
commit 2d1e81f
Showing
10 changed files
with
386 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
...onnect/StripeConnect/Source/Internal/AuthenticatedWebView/AuthenticatedWebViewError.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// | ||
// AuthenticatedWebViewError.swift | ||
// StripeConnect | ||
// | ||
// Created by Mel Ludowise on 10/15/24. | ||
// | ||
|
||
enum AuthenticatedWebViewError: Int, Error { | ||
|
||
// NOTE: These integer values should remain stable as they are used as | ||
// error codes in error logging | ||
|
||
/// ASWebAuthenticationSession could not be started | ||
/// - Note: This can occur if the app is backgrounded when attempting to present the web view | ||
case cannotStartSession = 0 | ||
|
||
/// There's no window on which to present | ||
case notInViewHierarchy = 1 | ||
|
||
/// An ASWebAuthenticationSession is currently already being presented | ||
case alreadyPresenting = 2 | ||
} |
83 changes: 83 additions & 0 deletions
83
...nect/StripeConnect/Source/Internal/AuthenticatedWebView/AuthenticatedWebViewManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// | ||
// AuthenticatedWebViewManager.swift | ||
// StripeConnect | ||
// | ||
// Created by Mel Ludowise on 10/15/24. | ||
// | ||
|
||
import AuthenticationServices | ||
|
||
/// Manages authenticated web views for a single component | ||
class AuthenticatedWebViewManager: NSObject { | ||
typealias SessionFactory = ( | ||
_ url: URL, | ||
_ callbackURLScheme: String?, | ||
_ completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler | ||
) -> ASWebAuthenticationSession | ||
|
||
/// Used to dependency inject in tests and wrap `ASWebAuthenticationSession.init` | ||
private let sessionFactory: SessionFactory | ||
|
||
/// The currently presented auth session, if there is one | ||
weak var authSession: ASWebAuthenticationSession? | ||
|
||
init(sessionFactory: @escaping SessionFactory = ASWebAuthenticationSession.init) { | ||
self.sessionFactory = sessionFactory | ||
} | ||
|
||
/// Returns the redirect URL or nil if the user cancelled the flow | ||
@MainActor | ||
func present(with url: URL, from view: UIView) async throws -> URL? { | ||
guard authSession == nil else { | ||
throw AuthenticatedWebViewError.alreadyPresenting | ||
} | ||
guard let window = view.window else { | ||
throw AuthenticatedWebViewError.notInViewHierarchy | ||
} | ||
|
||
let presentationContextProvider = AuthenticatedWebViewPresentationContextProvider(window: window) | ||
|
||
let returnUrl: URL? = try await withCheckedThrowingContinuation { continuation in | ||
let authSession = sessionFactory(url, StripeConnectConstants.authenticatedWebViewReturnUrlScheme) { returnUrl, error in | ||
|
||
if let authenticationSessionError = error as? ASWebAuthenticationSessionError, | ||
authenticationSessionError.code == .canceledLogin { | ||
// The user either selected "Cancel" in the initial modal | ||
// prompting them to "Sign In" or they hit the "Cancel" | ||
// button in presented browser view | ||
continuation.resume(returning: nil) | ||
return | ||
} else if let error { | ||
continuation.resume(throwing: error) | ||
return | ||
} | ||
|
||
continuation.resume(returning: returnUrl) | ||
} | ||
authSession.presentationContextProvider = presentationContextProvider | ||
self.authSession = authSession | ||
|
||
guard authSession.canStart, | ||
authSession.start() else { | ||
continuation.resume(throwing: AuthenticatedWebViewError.cannotStartSession) | ||
return | ||
} | ||
} | ||
|
||
return returnUrl | ||
} | ||
} | ||
|
||
// MARK: - ASWebAuthenticationPresentationContextProviding | ||
|
||
private class AuthenticatedWebViewPresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding { | ||
let window: UIWindow | ||
|
||
init(window: UIWindow) { | ||
self.window = window | ||
} | ||
|
||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { | ||
return window | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.