Skip to content

Commit

Permalink
Code alignments
Browse files Browse the repository at this point in the history
  • Loading branch information
shilgapira committed Nov 7, 2024
1 parent 47dd605 commit 50a3191
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 160 deletions.
23 changes: 10 additions & 13 deletions src/flows/Flow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,25 @@ public enum DescopeFlowState: String {
@MainActor
public class DescopeFlow {
/// TODO
public static var current: DescopeFlow?
public static weak var current: DescopeFlow?

/// The URL where the flow is hosted.
public let url: String
public let url: URL

public var descope: DescopeSDK = Descope.sdk

/// TODO
public var oauthNativeProvider: OAuthProvider?
public var oauthProvider: OAuthProvider?

public var requestTimeoutInterval: TimeInterval?
public var magicLinkRedirect: URL?

/// Creates a new ``DescopeFlow`` object that encapsulates a single flow run.
///
/// - Parameter url: The URL where the flow is hosted.
public init(url: String) {
self.url = url
}
public var requestTimeoutInterval: TimeInterval?

/// Creates a new ``DescopeFlow`` object that encapsulates a single flow run.
///
/// - Parameter url: The URL where the flow is hosted.
public init(url: URL) {
self.url = url.absoluteString
self.url = url
}

/// Resumes a running flow that's waiting for Magic Link authentication.
Expand All @@ -67,12 +64,12 @@ public class DescopeFlow {
/// }
/// }
public func resume(with url: URL) {
resume?(url)
resume?(self, url)
}

// Internal

typealias ResumeClosure = @MainActor (URL) -> ()
typealias ResumeClosure = @MainActor (DescopeFlow, URL) -> ()

/// The running flow periodically checks this property to for any redirect URL from calls
/// to the ``handleURL(_:)`` function.
Expand Down
46 changes: 24 additions & 22 deletions src/flows/FlowBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ protocol FlowBridgeDelegate: AnyObject {

enum FlowBridgeRequest {
case oauthNative(clientId: String, stateId: String, nonce: String, implicit: Bool)
case oauthWeb(startURL: URL, finishURL: URL?)
case webAuth(startURL: URL, finishURL: URL?)
}

enum FlowBridgeResponse {
case oauthNative(stateId: String, authorizationCode: String?, identityToken: String?, user: String?)
case oauthWeb(exchangeCode: String)
case webAuth(exchangeCode: String)
case failure(String)
}

@MainActor
Expand Down Expand Up @@ -62,8 +63,8 @@ class FlowBridge: NSObject {
webView?.callJavaScript(function: "send", params: response.stringValue)
}

func setOAuthProvider(_ name: String) {
webView?.callJavaScript(function: "oauth", params: name)
func set(oauthProvider: String?, magicLinkRedirect: String?) {
webView?.callJavaScript(function: "set", params: oauthProvider ?? "", magicLinkRedirect ?? "")
}
}

Expand Down Expand Up @@ -176,13 +177,13 @@ private extension FlowBridgeRequest {
guard let start = payload["start"] as? [String: Any] else { return nil }
guard let clientId = start["clientId"] as? String, let stateId = start["stateId"] as? String, let nonce = start["nonce"] as? String, let implicit = start["implicit"] as? Bool else { return nil }
self = .oauthNative(clientId: clientId, stateId: stateId, nonce: nonce, implicit: implicit)
case "oauthWeb":
case "oauthWeb", "sso":
guard let startString = payload["startUrl"] as? String, let startURL = URL(string: startString) else { return nil }
var finishURL: URL?
if let str = payload["finishUrl"] as? String, !str.isEmpty, let url = URL(string: str) {
finishURL = url
}
self = .oauthWeb(startURL: startURL, finishURL: finishURL)
self = .webAuth(startURL: startURL, finishURL: finishURL)
default:
return nil
}
Expand All @@ -207,11 +208,15 @@ private extension FlowBridgeResponse {
return [
"nativeOAuth": nativeOAuth,
]
case let .oauthWeb(exchangeCode):
case let .webAuth(exchangeCode):
return [
"exchangeCode": exchangeCode,
"idpInitiated": true,
]
case let .failure(failure):
return [
"failure": failure,
]
}
}

Expand Down Expand Up @@ -253,16 +258,11 @@ function \(namespace)_find() {
// Called by bridge when the WebView finished loading
function \(namespace)_wait() {
document.body.style.background = 'transparent'
const styles = `
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
}
:root {
color-scheme: light dark;
}
`
const stylesheet = document.createElement("style")
Expand All @@ -281,18 +281,19 @@ function \(namespace)_wait() {
// Attaches event listeners once the Descope web-component is ready
function \(namespace)_prepare(component) {
const parent = component.parentElement?.parentElement
if (parent) {
parent.style.boxShadow = 'unset'
}
component.nativeOptions = {
platform: 'ios',
oauthRedirect: 'oauth://redirect',
bridgeVersion: 1,
oauthRedirect: '\(WebAuth.redirectURL)',
ssoRedirect: '\(WebAuth.redirectURL)',
}
component.addEventListener('ready', () => {
window.webkit.messageHandlers.\(FlowBridgeMessage.ready.rawValue).postMessage('')
if (!component.bridgeVersion) {
window.webkit.messageHandlers.\(FlowBridgeMessage.failure.rawValue).postMessage('The flow is using an unsupported web component version')
} else {
window.webkit.messageHandlers.\(FlowBridgeMessage.ready.rawValue).postMessage('')
}
})
component.addEventListener('bridge', (event) => {
Expand All @@ -308,11 +309,12 @@ function \(namespace)_prepare(component) {
})
}
// Updates the name of the native OAuth provider
function \(namespace)_oauth(name) {
// Updates the values of native options
function \(namespace)_set(oauthProvider, magicLinkRedirect) {
let component = \(namespace)_find()
if (component) {
component.nativeOptions.oauthProvider = name
component.nativeOptions.oauthProvider = oauthProvider
component.nativeOptions.magicLinkRedirect = magicLinkRedirect
}
}
Expand Down
111 changes: 55 additions & 56 deletions src/flows/FlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,81 +11,77 @@ public protocol DescopeFlowCoordinatorDelegate: AnyObject {
func coordinatorDidFailLoading(_ coordinator: DescopeFlowCoordinator, error: DescopeError)
func coordinatorDidFinishLoading(_ coordinator: DescopeFlowCoordinator)
func coordinatorDidBecomeReady(_ coordinator: DescopeFlowCoordinator)
func coordinatorDidInterceptNavigation(_ coordinator: DescopeFlowCoordinator, to url: URL, external: Bool)
func coordinatorDidFailAuthentication(_ coordinator: DescopeFlowCoordinator, error: DescopeError)
func coordinatorDidFinishAuthentication(_ coordinator: DescopeFlowCoordinator, response: AuthenticationResponse)
}

@MainActor
public class DescopeFlowCoordinator {
private let descope: DescopeSDK
private let logger: DescopeLogger?
private let bridge: FlowBridge

private var logger: DescopeLogger?

public weak var delegate: DescopeFlowCoordinatorDelegate?

public private(set) var state: DescopeFlowState = .initial {
didSet {
if state == .failed || state == .finished, DescopeFlow.current === flow {
DescopeFlow.current = nil
}
delegate?.coordinatorDidUpdateState(self, to: state, from: oldValue)
}
}

public var webView: WKWebView? {
private var flow: DescopeFlow? {
didSet {
bridge.webView = webView
logger = flow?.descope.config.logger
bridge.logger = logger
}
}

public convenience init() {
self.init(sdk: Descope.sdk)
}

public convenience init(using descope: DescopeSDK) {
self.init(sdk: descope)
public var webView: WKWebView? {
didSet {
bridge.webView = webView
}
}

private init(sdk: DescopeSDK) {
descope = sdk
logger = sdk.config.logger
public init() {
bridge = FlowBridge()
bridge.logger = logger
bridge.delegate = self
}

public func prepare(configuration: WKWebViewConfiguration) {
bridge.prepare(configuration: configuration)
}

// Flow

public func start(flow: DescopeFlow) {
self.flow = flow
DescopeFlow.current = flow

logger(.info, "Starting flow authentication", flow)
state = .started

// dispatch this error asynchronously to prevent zalgo and let the calling function return
guard let url = URL(string: flow.url) else {
DispatchQueue.main.async { [self] in
state = .failed
delegate?.coordinatorDidFailLoading(self, error: DescopeError.flowFailed.with(message: "Invalid flow URL"))
}
return
}

let loadURL = { @MainActor [weak self, weak flow] url in
var request = URLRequest(url: url)
if let timeout = flow?.requestTimeoutInterval {
request.timeoutInterval = timeout
}
self?.webView?.load(request)
}
flow.resume = makeFlowResumeClosure(for: self)

flow.resume = loadURL
load(url: flow.url, for: flow)
}

loadURL(url)
fileprivate func load(url: URL, for flow: DescopeFlow) {
var request = URLRequest(url: url)
if let timeout = flow.requestTimeoutInterval {
request.timeoutInterval = timeout
}
webView?.load(request)
}

// Authentication

private func handleAuthentication(_ data: Data) {
logger(.info, "Finishing flow authentication")
Task {
logger(.info, "Finishing flow authentication")
guard let authResponse = await parseAuthentication(data) else { return }
state = .finished
delegate?.coordinatorDidFinishAuthentication(self, response: authResponse)
Expand All @@ -97,8 +93,9 @@ public class DescopeFlowCoordinator {
var jwtResponse = try JSONDecoder().decode(DescopeClient.JWTResponse.self, from: data)
try jwtResponse.setValues(from: data)
let cookies = await webView?.configuration.websiteDataStore.httpCookieStore.allCookies() ?? []
jwtResponse.sessionJwt = try jwtResponse.sessionJwt ?? findTokenCookie(named: DescopeClient.sessionCookieName, in: cookies, projectId: descope.config.projectId)
jwtResponse.refreshJwt = try jwtResponse.refreshJwt ?? findTokenCookie(named: DescopeClient.refreshCookieName, in: cookies, projectId: descope.config.projectId)
let projectId = flow?.descope.config.projectId ?? ""
jwtResponse.sessionJwt = try jwtResponse.sessionJwt ?? findTokenCookie(named: DescopeClient.sessionCookieName, in: cookies, projectId: projectId)
jwtResponse.refreshJwt = try jwtResponse.refreshJwt ?? findTokenCookie(named: DescopeClient.refreshCookieName, in: cookies, projectId: projectId)
return try jwtResponse.convert()
} catch let error as DescopeError {
logger(.error, "Unexpected error converting authentication response", error)
Expand All @@ -108,15 +105,15 @@ public class DescopeFlowCoordinator {
} catch {
logger(.error, "Unexpected error parsing authentication response", error)
state = .failed
delegate?.coordinatorDidFailAuthentication(self, error: .flowFailed.with(cause: error))
delegate?.coordinatorDidFailAuthentication(self, error: DescopeError.flowFailed.with(cause: error))
return nil
}
}

// OAuth Native

private func handleOAuthNative(clientId: String, stateId: String, nonce: String, implicit: Bool) {
logger(.info, "Requesting authorization for Sign in with Apple", clientId)
logger(.info, "Requesting authentication using Sign in with Apple", clientId)
Task {
await performOAuthNative(stateId: stateId, nonce: nonce, implicit: implicit)
}
Expand All @@ -128,34 +125,30 @@ public class DescopeFlowCoordinator {
let response = FlowBridgeResponse.oauthNative(stateId: stateId, authorizationCode: authorizationCode, identityToken: identityToken, user: user)
bridge.send(response: response)
} catch .oauthNativeCancelled {
// TODO
bridge.send(response: .failure("OAuthNativeCancelled"))
} catch {
logger(.error, "Failed authenticating using Sign in with Apple", error)
state = .failed
delegate?.coordinatorDidFailAuthentication(self, error: error)
bridge.send(response: .failure("OAuthNativeFailed"))
}
}

// OAuth Web
// OAuth / SSO

private func handleOAuthWeb(startURL: URL, finishURL: URL?) {
logger(.info, "Requesting authorization for OAuth web", startURL)
private func handleWebAuth(startURL: URL, finishURL: URL?) {
logger(.info, "Requesting web authentication", startURL)
Task {
await performOAuthWeb(startURL: startURL, finishURL: finishURL)
await performWebAuth(startURL: startURL, finishURL: finishURL)
}
}

private func performOAuthWeb(startURL: URL, finishURL: URL?) async {
private func performWebAuth(startURL: URL, finishURL: URL?) async {
do {
let exchangeCode = try await OAuth.performWebAuthentication(url: startURL, accessSharedUserData: true, logger: logger)
let response = FlowBridgeResponse.oauthWeb(exchangeCode: exchangeCode)
let response = FlowBridgeResponse.webAuth(exchangeCode: exchangeCode)
bridge.send(response: response)
} catch .oauthWebCancelled {
// TODO
} catch .webAuthCancelled {
bridge.send(response: .failure("WebAuthCancelled"))
} catch {
logger(.error, "Failed authenticating using OAuth web", error)
state = .failed
delegate?.coordinatorDidFailAuthentication(self, error: error)
bridge.send(response: .failure("WebAuthFailed"))
}
}
}
Expand All @@ -175,21 +168,21 @@ extension DescopeFlowCoordinator: FlowBridgeDelegate {
}

func bridgeDidBecomeReady(_ bridge: FlowBridge) {
bridge.set(oauthProvider: flow?.oauthProvider?.name, magicLinkRedirect: flow?.magicLinkRedirect?.absoluteString)
state = .ready
delegate?.coordinatorDidBecomeReady(self)
}

func bridgeDidInterceptNavigation(_ bridge: FlowBridge, to url: URL, external: Bool) {
// TODO
UIApplication.shared.open(url)
delegate?.coordinatorDidInterceptNavigation(self, to: url, external: external)
}

func bridgeDidReceiveRequest(_ bridge: FlowBridge, request: FlowBridgeRequest) {
switch request {
case let .oauthNative(clientId, stateId, nonce, implicit):
handleOAuthNative(clientId: clientId, stateId: stateId, nonce: nonce, implicit: implicit)
case let .oauthWeb(startURL, finishURL):
handleOAuthWeb(startURL: startURL, finishURL: finishURL)
case let .webAuth(startURL, finishURL):
handleWebAuth(startURL: startURL, finishURL: finishURL)
}
}

Expand Down Expand Up @@ -226,3 +219,9 @@ private func findTokenCookie(named name: String, in cookies: [HTTPCookie], proje

return token.jwt
}

private func makeFlowResumeClosure(for coordinator: DescopeFlowCoordinator) -> DescopeFlow.ResumeClosure {
return { @MainActor [weak coordinator] flow, url in
coordinator?.load(url: url, for: flow)
}
}
Loading

0 comments on commit 50a3191

Please sign in to comment.