SHA256
Require iOS 18.4 for explicit upload denial in the isolated authentication window. Add origin-bound generated download policy, app-switcher privacy, typed metadata regressions and original community icon assets. Seven portable tests and Swift formatting checks pass; Apple SDK validation remains external.
78 lines
3.3 KiB
Swift
78 lines
3.3 KiB
Swift
import SwiftUI
|
|
import WebKit
|
|
|
|
struct AuthenticationView: View {
|
|
let destination: URL
|
|
@ObservedObject var model: BrowserModel
|
|
var body: some View {
|
|
NavigationStack {
|
|
IsolatedAuthentication(url: destination, model: model)
|
|
.navigationTitle(model.authenticationHost)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar { Button("Close") { model.authentication = nil } }
|
|
}
|
|
}
|
|
}
|
|
|
|
struct IsolatedAuthentication: UIViewRepresentable {
|
|
let url: URL
|
|
let model: BrowserModel
|
|
func makeCoordinator() -> Coordinator { Coordinator(model) }
|
|
func makeUIView(context: Context) -> WKWebView {
|
|
let configuration = WKWebViewConfiguration()
|
|
// Same cookie store as the initiating Yuvomi handshake; no manual cookie export.
|
|
configuration.websiteDataStore = model.web.configuration.websiteDataStore
|
|
configuration.preferences.javaScriptCanOpenWindowsAutomatically = false
|
|
configuration.mediaTypesRequiringUserActionForPlayback = .all
|
|
let view = WKWebView(frame: .zero, configuration: configuration)
|
|
view.navigationDelegate = context.coordinator
|
|
view.uiDelegate = context.coordinator
|
|
if #available(iOS 16.4, *) { view.isInspectable = false }
|
|
view.load(URLRequest(url: url))
|
|
return view
|
|
}
|
|
func updateUIView(_ uiView: WKWebView, context: Context) {}
|
|
static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {
|
|
uiView.stopLoading()
|
|
uiView.navigationDelegate = nil
|
|
uiView.uiDelegate = nil
|
|
}
|
|
final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
|
|
let model: BrowserModel
|
|
init(_ model: BrowserModel) { self.model = model }
|
|
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
|
model.authenticationHost = webView.url?.host ?? "Sign in"
|
|
}
|
|
func webView(
|
|
_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
|
|
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
|
|
) {
|
|
guard let url = navigationAction.request.url, url.scheme == "https",
|
|
url.user == nil, url.password == nil
|
|
else {
|
|
decisionHandler(.cancel)
|
|
return
|
|
}
|
|
decisionHandler(.allow)
|
|
}
|
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
if let url = webView.url, model.server?.owns(url) == true, !url.path.hasPrefix("/api/") {
|
|
model.authentication = nil
|
|
model.web.load(URLRequest(url: url))
|
|
}
|
|
}
|
|
func webView(
|
|
_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin,
|
|
initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType,
|
|
decisionHandler: @escaping (WKPermissionDecision) -> Void
|
|
) { decisionHandler(.deny) }
|
|
func webView(
|
|
_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters,
|
|
initiatedByFrame frame: WKFrameInfo,
|
|
completionHandler: @escaping @MainActor @Sendable ([URL]?) -> Void
|
|
) {
|
|
completionHandler(nil)
|
|
}
|
|
}
|
|
}
|