import SwiftUI import WebKit struct EmbeddedWebAppView: View { @Bindable var settings: SettingsStore var body: some View { PlatformWebView(script: settings.injectionScript, reloadToken: settings.webStateVersion) .ignoresSafeArea() } } #if os(macOS) struct PlatformWebView: NSViewRepresentable { let script: String let reloadToken: UUID func makeCoordinator() -> Coordinator { Coordinator() } func makeNSView(context: Context) -> WKWebView { context.coordinator.makeWebView(script: script) } func updateNSView(_ webView: WKWebView, context: Context) { context.coordinator.update(webView: webView, script: script, reloadToken: reloadToken) } } #else struct PlatformWebView: UIViewRepresentable { let script: String let reloadToken: UUID func makeCoordinator() -> Coordinator { Coordinator() } func makeUIView(context: Context) -> WKWebView { context.coordinator.makeWebView(script: script) } func updateUIView(_ webView: WKWebView, context: Context) { context.coordinator.update(webView: webView, script: script, reloadToken: reloadToken) } } #endif final class Coordinator: NSObject, WKNavigationDelegate { private var lastReloadToken: UUID? private var pendingRefreshTask: Task? func makeWebView(script: String) -> WKWebView { let configuration = WKWebViewConfiguration() let contentController = WKUserContentController() contentController.addUserScript( WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: true) ) configuration.userContentController = contentController let webView = WKWebView(frame: .zero, configuration: configuration) webView.navigationDelegate = self #if os(macOS) webView.setValue(false, forKey: "drawsBackground") #endif loadIndex(into: webView) return webView } func update(webView: WKWebView, script: String, reloadToken: UUID) { guard lastReloadToken != reloadToken else { return } lastReloadToken = reloadToken pendingRefreshTask?.cancel() pendingRefreshTask = Task { @MainActor [weak webView] in await Task.yield() guard !Task.isCancelled, let webView else { return } webView.evaluateJavaScript(script) { _, _ in DispatchQueue.main.async { webView.reload() } } } } private func loadIndex(into webView: WKWebView) { let indexURL = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "WebApp/browser") ?? Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "WebApp") guard let indexURL else { let fallbackHTML = """

Embedded chat bundle not found

The Apple app expects the Angular client to be built into the bundled WebApp folder.

""" webView.loadHTMLString(fallbackHTML, baseURL: nil) return } webView.loadFileURL(indexURL, allowingReadAccessTo: indexURL.deletingLastPathComponent()) } }