// // Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import SwiftUI import WebKit struct AuthenticationRecaptchaWebView: UIViewRepresentable { // MARK: - Properties // MARK: Public /// The `siteKey` string to pass to the ReCaptcha widget. let siteKey: String /// The homeserver's URL, used so ReCaptcha can validate where the request is coming from. let homeserverURL: URL /// A binding to boolean that controls whether or not a loading spinner should be shown. @Binding var isLoading: Bool /// The completion called when the ReCaptcha was successful. The response string /// is passed into the closure as the only argument. let completion: (String) -> Void // MARK: Private @Environment(\.theme) private var theme // MARK: - Setup func makeUIView(context: Context) -> WKWebView { let userContentController = WKUserContentController() userContentController.add(context.coordinator, name: "recaptcha") let configuration = WKWebViewConfiguration() configuration.userContentController = userContentController let webView = WKWebView(frame: .zero, configuration: configuration) webView.navigationDelegate = context.coordinator #if DEBUG // Use a randomised user agent to encourage the ReCaptcha to show a challenge. webView.customUserAgent = "Show Me The Traffic Lights \(Float.random(in: 1...100))" #endif return webView } func updateUIView(_ webView: WKWebView, context: Context) { let recaptchaTheme: Coordinator.ReCaptchaTheme = theme.isDark ? .dark : .light webView.loadHTMLString(context.coordinator.htmlString(with: siteKey, using: recaptchaTheme), baseURL: homeserverURL) } func makeCoordinator() -> Coordinator { let coordinator = Coordinator(isLoading: $isLoading) coordinator.completion = completion return coordinator } // MARK: - Coordinator class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { /// The theme used to render the ReCaptcha enum ReCaptchaTheme: String { case light, dark } /// A binding to boolean that controls whether or not a loading spinner should be shown. @Binding var isLoading: Bool /// The completion called when the ReCaptcha was successful. The response string /// is passed into the closure as the only argument. var completion: ((String) -> Void)? init(isLoading: Binding) { self._isLoading = isLoading } /// Generates the HTML page to show for the given `siteKey` and `theme`. func htmlString(with siteKey: String, using theme: ReCaptchaTheme) -> String { """
""" } func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { isLoading = true } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { isLoading = false } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { isLoading = false } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let response = message.body as? String else { return } completion?(response) } } }