Files
bundesmessenger-ios/RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaWebView.swift
T
Doug 1b74f87b35 Add Email/Terms/ReCaptcha into the Authentication flow
Replace ReCaptcha navigation delegate with a WKUserContentController.
Move callback property closures onto the MainActor.
Show a loading indicator whilst waiting for the authentication service to start.
Move nextUncompletedStage into FlowResult.
Handle text field actions during authentication.
Remove scroll view tweaks in server selection screen following EMS banner removal.
2022-05-19 11:43:38 +01:00

139 lines
5.1 KiB
Swift

//
// 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<Bool>) {
self._isLoading = isLoading
}
/// Generates the HTML page to show for the given `siteKey` and `theme`.
func htmlString(with siteKey: String, using theme: ReCaptchaTheme) -> String {
"""
<html>
<head>
<meta name='viewport' content='initial-scale=1.0' />
<style>@media (prefers-color-scheme: dark) { body { background-color: #15191E; } }</style>
<script type="text/javascript">
var verifyCallback = function(response) {
window.webkit.messageHandlers.recaptcha.postMessage(response);
alert('Testing 1234');
};
var onloadCallback = function() {
grecaptcha.render('recaptcha_widget', {
'sitekey' : '\(siteKey)',
'callback': verifyCallback,
'theme': '\(theme.rawValue)'
});
};
</script>
</head>
<body style="margin: 16px;">
<div id="recaptcha_widget"></div>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer>
</script>
</body>
</html>
"""
}
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)
}
}
}