bern-target-api 0.1.0

Interface between the Bern compiler and independent UI target modules
Documentation

import WebKit

@MainActor
private final class BernScriptHost: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
    static let shared = BernScriptHost()
    private var view: WKWebView?
    private var timer: Timer?
    private var ready = false
    private var pending: [String] = []
    func start() {
        guard timer == nil else { return }
        bern_script_start()
        timer = Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true) { _ in
            Task { @MainActor in BernScriptHost.shared.pump() }
        }
    }
    private func open() {
        let configuration = WKWebViewConfiguration()
        configuration.websiteDataStore = .nonPersistent()
        configuration.userContentController.add(self, name: "bernScript")
        let view = WKWebView(frame: .zero, configuration: configuration)
        self.view = view
        view.navigationDelegate = self
        view.loadHTMLString(/*bern:script-html*/, baseURL: nil)
    }
    func stop() {
        timer?.invalidate(); timer = nil
        view?.configuration.userContentController.removeScriptMessageHandler(forName: "bernScript")
        view?.stopLoading(); view = nil; ready = false; pending.removeAll()
        bern_script_stop()
    }
    private func pump() {
        let buffer = bern_script_poll(ProcessInfo.processInfo.systemUptime * 1000)
        if let ptr = buffer.ptr, buffer.len > 0 {
            pending.append(String(decoding: UnsafeBufferPointer(start: ptr, count: Int(buffer.len)), as: UTF8.self))
        }
        bern_buffer_free(buffer)
        if view == nil && !pending.isEmpty { open() }
        guard ready, let view else { return }
        let batches = pending; pending.removeAll()
        for batch in batches {
            view.evaluateJavaScript("bernScriptDispatch(\(batch))") { _, error in
                if error != nil { BernScriptHost.shared.stop() }
            }
        }
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { ready = true }
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { stop() }
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { stop() }
    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { stop() }
    func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.frameInfo.isMainFrame, let json = message.body as? String else { return }
        Array(json.utf8).withUnsafeBufferPointer { bern_script_complete($0.baseAddress, $0.count) }
    }
}