// // WebRTCView.swift // SimpleX (iOS) // // Created by Ian Davies on 29/04/2022. // Copyright © 2022 SimpleX Chat. All rights reserved. // import SwiftUI import WebKit import SimpleXChat class WebRTCCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate { var rtcWebView: Binding var webViewMsg: Binding internal init(rtcWebView: Binding, webViewMsg: Binding) { self.rtcWebView = rtcWebView self.webViewMsg = webViewMsg } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.allowsBackForwardNavigationGestures = false self.rtcWebView.wrappedValue = webView ChatModel.shared.callWebView = webView } func webView(_ webView: WKWebView, decideMediaCapturePermissionsFor origin : WKSecurityOrigin, initiatedBy frame: WKFrameInfo, type: WKMediaCaptureType) async -> WKPermissionDecision { print("webView", #function) return .grant } // receive message from WKWebView func userContentController( _ userContentController: WKUserContentController, didReceive message: WKScriptMessage ) { logger.debug("WebRTCCoordinator.userContentController") switch message.name { case "webrtc": if let msgStr = message.body as? String, let msg: WVAPIMessage = decodeJSON(msgStr) { // this is the binding that communicates messages from webview to swift view webViewMsg.wrappedValue = msg if case .invalid = msg.resp { logger.error("WebRTCCoordinator.userContentController: invalid message \(String(describing: message.body))") } } else { logger.error("WebRTCCoordinator.userContentController: message parsing error \(String(describing: message.body))") } case "logger": if let msgStr = message.body as? String { logger.error("WebRTCCoordinator console.log: \(msgStr)") } else { logger.error("WebRTCCoordinator console.log: \(String(describing: message.body))") } default: logger.error("WebRTCCoordinator.userContentController: invalid message.name \(message.name)") } } } struct WebRTCView: UIViewRepresentable { @State private var coordinator: WebRTCCoordinator? @Binding var rtcWebView: WKWebView? @Binding var webViewMsg: WVAPIMessage? func makeCoordinator() -> WebRTCCoordinator { WebRTCCoordinator(rtcWebView: $rtcWebView, webViewMsg: $webViewMsg) } func makeUIView(context: Context) -> WKWebView { let wkCoordinator = makeCoordinator() DispatchQueue.main.async { coordinator = wkCoordinator } let wkController = WKUserContentController() let cfg = WKWebViewConfiguration() cfg.userContentController = wkController cfg.mediaTypesRequiringUserActionForPlayback = [] cfg.allowsInlineMediaPlayback = true let addScript = { (handler: String, source: String) in let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false) wkController.addUserScript(script) wkController.add(wkCoordinator, name: handler) } addScript("webrtc", "sendMessageToNative = (msg) => webkit.messageHandlers.webrtc.postMessage(JSON.stringify(msg))") addScript("logger", "console.log = (arg) => webkit.messageHandlers.logger.postMessage(JSON.stringify(arg))") let wkWebView = WKWebView(frame: .zero, configuration: cfg) wkWebView.navigationDelegate = wkCoordinator guard let path: String = Bundle.main.path(forResource: "call", ofType: "html", inDirectory: "www") else { logger.error("WebRTCView.makeUIView call.html not found") return wkWebView } let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false) wkWebView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl) return wkWebView } func updateUIView(_ webView: WKWebView, context: Context) { logger.debug("WebRTCView.updateUIView") } } func sendCallCommand(_ webView: WKWebView, _ command: WCallCommand) { logger.debug("sendCallCommand: \(command.cmdType)") let apiCmd = encodeJSON(WVAPICall(command: command)) let js = "processCommand(\(apiCmd))" webView.evaluateJavaScript(js) } //struct CallViewDebug: View { // @State private var commandStr = "" // @State private var rtcWebView: WKWebView? = nil // @State private var webViewMsg: WVAPIMessage? = nil // @FocusState private var keyboardVisible: Bool // // var body: some View { // VStack(spacing: 30) { // WebRTCView(rtcWebView: $rtcWebView, webViewMsg: $webViewMsg).frame(maxHeight: 260) // .onChange(of: webViewMsg) { _ in // if let resp = webViewMsg { // commandStr = encodeJSON(resp) // } // } // TextEditor(text: $commandStr) // .focused($keyboardVisible) // .disableAutocorrection(true) // .textInputAutocapitalization(.never) // .padding(.horizontal, 5) // .padding(.top, 2) // .frame(height: 112) // .overlay( // RoundedRectangle(cornerRadius: 10) // .strokeBorder(.secondary, lineWidth: 0.3, antialiased: true) // ) // HStack(spacing: 20) { // Button("Copy") { // UIPasteboard.general.string = commandStr // } // Button("Paste") { // commandStr = UIPasteboard.general.string ?? "" // } // Button("Clear") { // commandStr = "" // } // Button("Send") { // if let wv = rtcWebView, // let command: WCallCommand = decodeJSON(commandStr) { // sendCallCommand(wv, command) // } // } // } // HStack(spacing: 20) { // Button("Capabilities") { // if let wv = rtcWebView { // sendCallCommand(wv, .capabilities(useWorker: true)) // } // } // Button("Start") { // if let wv = rtcWebView { // sendCallCommand(wv, .start(media: .video)) // } // } // Button("Accept") { // // } // Button("Answer") { // // } // Button("ICE") { // // } // Button("End") { // // } // } // } // } //}