diff --git a/src/DescopeKit.swift b/src/DescopeKit.swift index bc086e1..2192fd4 100644 --- a/src/DescopeKit.swift +++ b/src/DescopeKit.swift @@ -56,8 +56,6 @@ public enum Descope { } /// The underlying ``DescopeSDK`` object used by the ``Descope`` singleton. - /// - /// FIXME: can be private but left internal for now until deprecations are removed static var sdk: DescopeSDK = .initial } diff --git a/src/flows/FlowBridge.swift b/src/flows/FlowBridge.swift new file mode 100644 index 0000000..96ff864 --- /dev/null +++ b/src/flows/FlowBridge.swift @@ -0,0 +1,158 @@ + +import WebKit + +protocol FlowBridgeDelegate: AnyObject { + func bridgeDidStartLoading(_ bridge: FlowBridge) + func bridgeDidFailLoading(_ bridge: FlowBridge, error: DescopeError) + func bridgeDidFinishLoading(_ bridge: FlowBridge) + func bridgeDidBecomeReady(_ bridge: FlowBridge) + func bridgeDidFailAuthentication(_ bridge: FlowBridge, error: DescopeError) + func bridgeDidFinishAuthentication(_ bridge: FlowBridge, data: Data) +} + +class FlowBridge: NSObject, LoggerProvider { + var logger: DescopeLogger? + + weak var delegate: FlowBridgeDelegate? + + weak var webView: WKWebView? { + willSet { + webView?.navigationDelegate = nil + webView?.uiDelegate = nil + } + didSet { + webView?.navigationDelegate = self + webView?.uiDelegate = self + } + } + + public func prepare(configuration: WKWebViewConfiguration) { + let setup = WKUserScript(source: setupScript, injectionTime: .atDocumentStart, forMainFrameOnly: false) + configuration.userContentController.addUserScript(setup) + + for name in FlowMessage.allCases { + configuration.userContentController.add(self, name: name.rawValue) + } + } +} + +extension FlowBridge: WKScriptMessageHandler { + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + switch FlowMessage(rawValue: message.name) { + case .log: + log(.debug, "Console Log", message.body) + case .warn: + log(.debug, "Console Warn", message.body) + case .error: + log(.debug, "Console Error", message.body) + case .ready: + log(.info, "Bridge received ready event") + delegate?.bridgeDidBecomeReady(self) + case .failure: + log(.error, "Bridge received failure event", message.body) + delegate?.bridgeDidFailAuthentication(self, error: DescopeError.flowFailed.with(message: "Unexpected authentication failure [\(message.body)]")) + case .success: + log(.info, "Bridge received success event") + guard let json = message.body as? String, case let data = Data(json.utf8) else { + delegate?.bridgeDidFailAuthentication(self, error: DescopeError.flowFailed.with(message: "Invalid JSON data in flow authentication")) + return + } + delegate?.bridgeDidFinishAuthentication(self, data: data) + case nil: + log(.error, "Unexpected message in bridge", message.name) + } + } +} + +extension FlowBridge: WKNavigationDelegate { + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { + log(.info, "Webview decide policy for url", navigationAction.navigationType.rawValue, navigationAction.request.url?.absoluteString) + if let url = navigationAction.request.url, url.scheme == "descopeauth" { + // TODO + return .cancel + } + return .allow + } + + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + log(.info, "Webview started loading url", webView.url) + delegate?.bridgeDidStartLoading(self) + } + + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + log(.error, "Webview failed provisional navigation", error) + delegate?.bridgeDidFailLoading(self, error: DescopeError.networkError.with(cause: error)) + } + + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + log(.info, "Webview commited navigation", webView.url) + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + log(.info, "Webview finished navigation", webView.url) + delegate?.bridgeDidFinishLoading(self) + webView.evaluateJavaScript("waitWebComponent()") + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + log(.error, "Webview failed navigation", error) + delegate?.bridgeDidFailLoading(self, error: DescopeError.networkError.with(cause: error)) + } +} + +extension FlowBridge: WKUIDelegate { + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + // TODO + log(.info, "Webview createWebViewWith", navigationAction.request, navigationAction, windowFeatures) + return nil + } +} + +private enum FlowMessage: String, CaseIterable { + case log, warn, error, ready, failure, success +} + +private let setupScript = """ + +/* Javascript code that's executed once the script finishes running */ + +// Redirect console logs to bridge +window.console.log = (s) => { window.webkit.messageHandlers.log.postMessage(s) } +window.console.warn = (s) => { window.webkit.messageHandlers.warn.postMessage(s) } +window.console.error = (s) => { window.webkit.messageHandlers.error.postMessage(s) } + +// Called by bridge when the WebView finished loading +function waitWebComponent() { + document.body.style.background = 'transparent' + + let id + id = setInterval(() => { + let wc = document.getElementsByTagName('descope-wc')[0] + if (wc) { + clearInterval(id) + prepareWebComponent(wc) + } + }, 20) +} + +// Attaches event listeners once the Descope web-component is ready +function prepareWebComponent(wc) { + const parent = wc?.parentElement?.parentElement + if (parent) { + parent.style.boxShadow = 'unset' + } + + wc.addEventListener('success', (e) => { + window.webkit.messageHandlers.success.postMessage(JSON.stringify(e.detail)) + }) + + wc.addEventListener('error', (e) => { + window.webkit.messageHandlers.failure.postMessage(e.detail) + }) + + wc.addEventListener('ready', () => { + window.webkit.messageHandlers.ready.postMessage('') + }) +} + +""" diff --git a/src/flows/FlowCoordinator.swift b/src/flows/FlowCoordinator.swift new file mode 100644 index 0000000..6ef7fd4 --- /dev/null +++ b/src/flows/FlowCoordinator.swift @@ -0,0 +1,102 @@ + +import WebKit + +public protocol DescopeFlowCoordinatorDelegate: AnyObject { + func coordinatorFlowDidStartLoading(_ coordinator: DescopeFlowCoordinator) + func coordinatorFlowDidFailLoading(_ coordinator: DescopeFlowCoordinator, error: DescopeError) + func coordinatorFlowDidFinishLoading(_ coordinator: DescopeFlowCoordinator) + func coordinatorFlowDidBecomeReady(_ coordinator: DescopeFlowCoordinator) + func coordinatorFlowDidFailAuthentication(_ coordinator: DescopeFlowCoordinator, error: DescopeError) + func coordinatorFlowDidFinishAuthentication(_ coordinator: DescopeFlowCoordinator, response: AuthenticationResponse) +} + +@MainActor +public class DescopeFlowCoordinator { + let descope: DescopeSDK + let bridge: FlowBridge + + weak var delegate: DescopeFlowCoordinatorDelegate? + + public var webView: WKWebView? { + didSet { + bridge.webView = webView + } + } + + public convenience init() { + self.init(sdk: Descope.sdk) + } + + public convenience init(using descope: DescopeSDK) { + self.init(sdk: descope) + } + + private init(sdk: DescopeSDK) { + descope = sdk + bridge = FlowBridge() + bridge.logger = sdk.config.logger + bridge.delegate = self + } + + public func prepare(configuration: WKWebViewConfiguration) { + bridge.prepare(configuration: configuration) + } + + public func start(runner: DescopeFlowRunner) { + log(.info, "Starting flow authentication", runner.flowURL) + let request = URLRequest(url: runner.flowURL) + webView?.load(request) + } + + // Actions + + private func handleAuthentication(_ data: Data) async { + do { + let cookies = await webView?.configuration.websiteDataStore.httpCookieStore.allCookies() ?? [] + var jwtResponse = try JSONDecoder().decode(DescopeClient.JWTResponse.self, from: data) + try jwtResponse.setValues(from: data, cookies: cookies) + let authResponse: AuthenticationResponse = try jwtResponse.convert() + delegate?.coordinatorFlowDidFinishAuthentication(self, response: authResponse) + } catch let error as DescopeError { + log(.error, "Failed to parse authentication response", error) + delegate?.coordinatorFlowDidFailAuthentication(self, error: error) + } catch { + log(.error, "Error parsing authentication response", error) + delegate?.coordinatorFlowDidFailAuthentication(self, error: DescopeError.flowFailed.with(cause: error)) + } + } +} + +extension DescopeFlowCoordinator: FlowBridgeDelegate { + func bridgeDidStartLoading(_ bridge: FlowBridge) { + delegate?.coordinatorFlowDidStartLoading(self) + } + + func bridgeDidFailLoading(_ bridge: FlowBridge, error: DescopeError) { + delegate?.coordinatorFlowDidFailLoading(self, error: error) + } + + func bridgeDidFinishLoading(_ bridge: FlowBridge) { + delegate?.coordinatorFlowDidFinishLoading(self) + } + + func bridgeDidBecomeReady(_ bridge: FlowBridge) { + delegate?.coordinatorFlowDidBecomeReady(self) + } + + func bridgeDidFailAuthentication(_ bridge: FlowBridge, error: DescopeError) { + delegate?.coordinatorFlowDidFailAuthentication(self, error: error) + } + + func bridgeDidFinishAuthentication(_ bridge: FlowBridge, data: Data) { + Task { + await handleAuthentication(data) + } + } +} + +extension DescopeFlowCoordinator: LoggerProvider { + var logger: DescopeLogger? { + return descope.config.logger + } +} diff --git a/src/flows/FlowView.swift b/src/flows/FlowView.swift new file mode 100644 index 0000000..9127f58 --- /dev/null +++ b/src/flows/FlowView.swift @@ -0,0 +1,147 @@ + +import WebKit + +public protocol DescopeFlowViewDelegate: AnyObject { + func flowViewDidStartLoading(_ flowView: DescopeFlowView) + func flowViewDidFailLoading(_ flowView: DescopeFlowView, error: DescopeError) + func flowViewDidFinishLoading(_ flowView: DescopeFlowView) + func flowViewDidBecomeReady(_ flowView: DescopeFlowView) + func flowViewDidFailAuthentication(_ flowView: DescopeFlowView, error: DescopeError) + func flowViewDidFinishAuthentication(_ flowView: DescopeFlowView, response: AuthenticationResponse) +} + +open class DescopeFlowView: UIView { + + private let coordinator = DescopeFlowCoordinator() + + private lazy var webView = createWebView() + + public weak var delegate: DescopeFlowViewDelegate? + + /// Setup + + public convenience init() { + self.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + setupSubviews() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + open override func awakeFromNib() { + super.awakeFromNib() + setupSubviews() + } + + private func setupView() { + coordinator.delegate = self + } + + private func setupSubviews() { + coordinator.webView = webView + addSubview(webView) + } + + /// UIView + + open override func layoutSubviews() { + super.layoutSubviews() + webView.frame = bounds + } + + /// Flow + + public func start(runner: DescopeFlowRunner) { + coordinator.start(runner: runner) + } + + /// WebView + + private func createWebView() -> WKWebView { + let configuration = WKWebViewConfiguration() + _prepareConfiguration(configuration) + + let webViewClass = Self.webViewClass + let webView = webViewClass.init(frame: bounds, configuration: configuration) + _prepareWebView(webView) + + return webView + } + + /// Overrides + + open class var webViewClass: WKWebView.Type { + return WKWebView.self + } + + private func _prepareConfiguration(_ configuration: WKWebViewConfiguration) { + prepareConfiguration(configuration) + coordinator.prepare(configuration: configuration) + } + + open func prepareConfiguration(_ configuration: WKWebViewConfiguration) { + } + + private func _prepareWebView(_ webView: WKWebView) { + webView.isOpaque = false + webView.backgroundColor = .clear + webView.scrollView.isScrollEnabled = false + webView.scrollView.showsVerticalScrollIndicator = false + webView.scrollView.showsHorizontalScrollIndicator = false + prepareWebView(webView) + } + + open func prepareWebView(_ webView: WKWebView) { + } +} + +extension DescopeFlowView: DescopeFlowCoordinatorDelegate { + public func coordinatorFlowDidStartLoading(_ coordinator: DescopeFlowCoordinator) { + delegate?.flowViewDidStartLoading(self) + } + + public func coordinatorFlowDidFailLoading(_ coordinator: DescopeFlowCoordinator, error: DescopeError) { + delegate?.flowViewDidFailLoading(self, error: error) + } + + public func coordinatorFlowDidFinishLoading(_ coordinator: DescopeFlowCoordinator) { + delegate?.flowViewDidFinishLoading(self) + } + + public func coordinatorFlowDidBecomeReady(_ coordinator: DescopeFlowCoordinator) { + delegate?.flowViewDidBecomeReady(self) + } + + public func coordinatorFlowDidFailAuthentication(_ coordinator: DescopeFlowCoordinator, error: DescopeError) { + delegate?.flowViewDidFailAuthentication(self, error: error) + } + + public func coordinatorFlowDidFinishAuthentication(_ coordinator: DescopeFlowCoordinator, response: AuthenticationResponse) { + delegate?.flowViewDidFinishAuthentication(self, response: response) + } +} + +func foo() { + let view = DescopeFlowView() +} + +class RichEditorWebView: WKWebView { + var accessoryView: UIView? + + override var inputAccessoryView: UIView? { + return accessoryView + } +} + +open class DescopeFlowView2: DescopeFlowView { + override open class var webViewClass: WKWebView.Type { + return RichEditorWebView.self + } +} diff --git a/src/internal/http/DescopeClient.swift b/src/internal/http/DescopeClient.swift index bb9c3b7..1a4c9f8 100644 --- a/src/internal/http/DescopeClient.swift +++ b/src/internal/http/DescopeClient.swift @@ -411,14 +411,18 @@ class DescopeClient: HTTPClient { var firstSeen: Bool mutating func setValues(from data: Data, response: HTTPURLResponse) throws { - // extract JWTs from the cookies if configured to not return them in the response body guard let url = response.url, let fields = response.allHeaderFields as? [String: String] else { return } let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields, for: url) + try setValues(from: data, cookies: cookies) + } + + mutating func setValues(from data: Data, cookies: [HTTPCookie]) throws { + // extract JWTs from the cookies if configured to not return them in the response body for cookie in cookies where !cookie.value.isEmpty { - if cookie.name == sessionCookieName { + if cookie.name.caseInsensitiveCompare(sessionCookieName) == .orderedSame { sessionJwt = cookie.value } - if cookie.name == refreshCookieName { + if cookie.name.caseInsensitiveCompare(refreshCookieName) == .orderedSame { refreshJwt = cookie.value } } diff --git a/src/internal/http/HTTPClient.swift b/src/internal/http/HTTPClient.swift index 69715d0..a703b29 100644 --- a/src/internal/http/HTTPClient.swift +++ b/src/internal/http/HTTPClient.swift @@ -1,7 +1,7 @@ import Foundation -class HTTPClient { +class HTTPClient: LoggerProvider { let baseURL: String let logger: DescopeLogger? let networkClient: DescopeNetworkClient @@ -58,10 +58,10 @@ class HTTPClient { private func call(_ route: String, method: String, headers: [String: String], params: [String: String?], body: Data?) async throws -> (Data, HTTPURLResponse) { let request = try makeRequest(route: route, method: method, headers: headers, params: params, body: body) - logger?.log(.info, "Starting network call", request.url) + log(.info, "Starting network call", request.url) #if DEBUG if let body = request.httpBody, let requestBody = String(bytes: body, encoding: .utf8) { - logger?.log(.debug, "Sending request body", requestBody) + log(.debug, "Sending request body", requestBody) } #endif @@ -70,20 +70,20 @@ class HTTPClient { guard let response = response as? HTTPURLResponse else { throw DescopeError(httpError: .invalidResponse) } #if DEBUG if let responseBody = String(bytes: data, encoding: .utf8) { - logger?.log(.debug, "Received response body", responseBody) + log(.debug, "Received response body", responseBody) } #endif if let error = DescopeError(httpStatusCode: response.statusCode) { if let responseError = errorForResponseData(data) { - logger?.log(.info, "Network call failed with server error", request.url, responseError) + log(.info, "Network call failed with server error", request.url, responseError) throw responseError } - logger?.log(.info, "Network call failed with http error", request.url, error) + log(.info, "Network call failed with http error", request.url, error) throw error } - logger?.log(.info, "Network call finished", request.url) + log(.info, "Network call finished", request.url) return (data, response) } @@ -114,7 +114,7 @@ class HTTPClient { do { return try await networkClient.call(request: request) } catch { - logger?.log(.error, "Network call failed with network error", request.url, error) + log(.error, "Network call failed with network error", request.url, error) throw DescopeError.networkError.with(cause: error) } } diff --git a/src/internal/others/Deprecated.swift b/src/internal/others/Deprecated.swift index 4d16698..75839b8 100644 --- a/src/internal/others/Deprecated.swift +++ b/src/internal/others/Deprecated.swift @@ -2,57 +2,57 @@ import Foundation public extension DescopeOTP { - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func signIn(with method: DeliveryMethod, loginId: String) async throws -> String { return try await signIn(with: method, loginId: loginId, options: []) } - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func signUpOrIn(with method: DeliveryMethod, loginId: String) async throws -> String { return try await signUpOrIn(with: method, loginId: loginId, options: []) } } public extension DescopeTOTP { - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func verify(loginId: String, code: String) async throws -> AuthenticationResponse { return try await verify(loginId: loginId, code: code, options: []) } } public extension DescopeMagicLink { - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func signIn(with method: DeliveryMethod, loginId: String, redirectURL: String?) async throws -> String { return try await signIn(with: method, loginId: loginId, redirectURL: redirectURL, options: []) } - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func signUpOrIn(with method: DeliveryMethod, loginId: String, redirectURL: String?) async throws -> String { return try await signUpOrIn(with: method, loginId: loginId, redirectURL: redirectURL, options: []) } } public extension DescopeEnchantedLink { - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func signIn(loginId: String, redirectURL: String?) async throws -> EnchantedLinkResponse { return try await signIn(loginId: loginId, redirectURL: redirectURL, options: []) } - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func signUpOrIn(loginId: String, redirectURL: String?) async throws -> EnchantedLinkResponse { return try await signUpOrIn(loginId: loginId, redirectURL: redirectURL, options: []) } } public extension DescopeOAuth { - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func start(provider: OAuthProvider, redirectURL: String?) async throws -> URL { return try await start(provider: provider, redirectURL: redirectURL, options: []) } } public extension DescopeSSO { - @available(*, deprecated, message: "Pass a value (or an empty array) for the options parameter") + @available(*, unavailable, message: "Pass a value (or an empty array) for the options parameter") func start(emailOrTenantName: String, redirectURL: String?) async throws -> URL { return try await start(emailOrTenantName: emailOrTenantName, redirectURL: redirectURL, options: []) } @@ -88,3 +88,11 @@ public extension DescopeConfig { self.logger = logger } } + +public extension DescopeFlowRunner { + @available(*, deprecated, message: "Use the init(flowURL: URL) initializer instead") + convenience init(flowURL: String) { + let url = URL(string: flowURL)! + self.init(flowURL: url) + } +} diff --git a/src/internal/others/Logging.swift b/src/internal/others/Logging.swift new file mode 100644 index 0000000..2a48fdf --- /dev/null +++ b/src/internal/others/Logging.swift @@ -0,0 +1,14 @@ + +protocol LoggingProvider { + func log(_ level: DescopeLogger.Level, _ message: StaticString, _ values: Any?...) +} + +protocol LoggerProvider: LoggingProvider { + var logger: DescopeLogger? { get } +} + +extension LoggerProvider { + func log(_ level: DescopeLogger.Level, _ message: StaticString, _ values: Any?...) { + logger?.log(level, message, values) + } +} diff --git a/src/internal/routes/EnchantedLink.swift b/src/internal/routes/EnchantedLink.swift index 56bfd55..4a1c952 100644 --- a/src/internal/routes/EnchantedLink.swift +++ b/src/internal/routes/EnchantedLink.swift @@ -3,7 +3,7 @@ import Foundation private let defaultPollDuration: TimeInterval = 2 /* mins */ * 60 /* secs */ -class EnchantedLink: Route, DescopeEnchantedLink { +class EnchantedLink: DescopeEnchantedLink, Route { let client: DescopeClient init(client: DescopeClient) { diff --git a/src/internal/routes/Flow.swift b/src/internal/routes/Flow.swift index 53c9e87..e55ef07 100644 --- a/src/internal/routes/Flow.swift +++ b/src/internal/routes/Flow.swift @@ -5,7 +5,7 @@ import CryptoKit private let redirectScheme = "descopeauth" private let redirectURL = "\(redirectScheme)://flow" -class Flow: Route, DescopeFlow { +class Flow: DescopeFlow, Route { let client: DescopeClient init(client: DescopeClient) { @@ -13,7 +13,7 @@ class Flow: Route, DescopeFlow { } var current: DescopeFlowRunner? - + @MainActor func start(runner: DescopeFlowRunner) async throws -> AuthenticationResponse { // adds some required query parameters to the flow URL to facilitate PKCE and @@ -199,8 +199,7 @@ private func prepareInitialRequest(for runner: DescopeFlowRunner) throws -> (url let codeVerifier = randomBytes.base64URLEncodedString() let codeChallenge = hashedBytes.base64URLEncodedString() - guard let url = URL(string: runner.flowURL) else { throw DescopeError.flowFailed.with(message: "Invalid flow URL") } - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { throw DescopeError.flowFailed.with(message: "Malformed flow URL") } + guard var components = URLComponents(url: runner.flowURL, resolvingAgainstBaseURL: false) else { throw DescopeError.flowFailed.with(message: "Malformed flow URL") } components.queryItems = components.queryItems ?? [] components.queryItems?.append(URLQueryItem(name: "ra-callback", value: redirectURL)) components.queryItems?.append(URLQueryItem(name: "ra-challenge", value: codeChallenge)) @@ -217,7 +216,7 @@ private func prepareInitialRequest(for runner: DescopeFlowRunner) throws -> (url private func prepareRedirectRequest(for runner: DescopeFlowRunner, redirectURL: URL) -> URL? { guard let pendingComponents = URLComponents(url: redirectURL, resolvingAgainstBaseURL: false) else { return nil } - guard let url = URL(string: runner.flowURL), var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil } + guard var components = URLComponents(url: runner.flowURL, resolvingAgainstBaseURL: false) else { return nil } components.queryItems = components.queryItems ?? [] for item in pendingComponents.queryItems ?? [] { components.queryItems?.append(item) diff --git a/src/internal/routes/OAuth.swift b/src/internal/routes/OAuth.swift index 374bc80..833f539 100644 --- a/src/internal/routes/OAuth.swift +++ b/src/internal/routes/OAuth.swift @@ -1,7 +1,7 @@ import AuthenticationServices -class OAuth: Route, DescopeOAuth { +class OAuth: DescopeOAuth, Route { let client: DescopeClient init(client: DescopeClient) { diff --git a/src/internal/routes/Passkey.swift b/src/internal/routes/Passkey.swift index e6f22d3..284fbaf 100644 --- a/src/internal/routes/Passkey.swift +++ b/src/internal/routes/Passkey.swift @@ -2,7 +2,7 @@ import AuthenticationServices import CryptoKit -class Passkey: Route, DescopePasskey { +class Passkey: DescopePasskey, Route { let client: DescopeClient init(client: DescopeClient) { diff --git a/src/internal/routes/Shared.swift b/src/internal/routes/Shared.swift index f94c52a..776a5a0 100644 --- a/src/internal/routes/Shared.swift +++ b/src/internal/routes/Shared.swift @@ -1,13 +1,13 @@ import Foundation -protocol Route { +protocol Route: LoggerProvider { var client: DescopeClient { get } } extension Route { - func log(_ level: DescopeLogger.Level, _ message: StaticString, _ values: Any?...) { - client.config.logger?.log(level, message, values) + var logger: DescopeLogger? { + return client.config.logger } } diff --git a/src/types/Flows.swift b/src/types/Flows.swift index beb3cd0..e363d2c 100644 --- a/src/types/Flows.swift +++ b/src/types/Flows.swift @@ -50,8 +50,8 @@ public class DescopeFlowRunner { } /// The URL where the flow is hosted. - public let flowURL: String - + public let flowURL: URL + /// Optional authentication info to allow running flows for authenticated users public var flowAuthentication: Authentication? @@ -85,10 +85,10 @@ public class DescopeFlowRunner { /// Creates a new ``DescopeFlowRunner`` object that encapsulates a single flow run. /// /// - Parameter flowURL: The URL where the flow is hosted. - public init(flowURL: String) { + public init(flowURL: URL) { self.flowURL = flowURL } - + /// Resumes a running flow that's waiting for Magic Link authentication. /// /// When a flow performs authentication with Magic Link at some point it will wait