import Foundation import JavaScriptCore private let contextLock = NSRecursiveLock() final class JavaScriptWorker { private let processQueue = DispatchQueue(label: "DispatchQueue.JavaScriptWorker") private let virtualMachine: JSVirtualMachine @SyncAccess(lock: contextLock) private var commonContext: JSContext? = nil init() { virtualMachine = processQueue.sync { JSVirtualMachine()! } } private func createContext() -> JSContext { let context = JSContext(virtualMachine: virtualMachine)! context.exceptionHandler = { _, exception in if let error = exception?.toString() { print(error) } } return context } @discardableResult func evaluate(script: Script) -> JSValue! { evaluate(string: script.body) } private func evaluate(string: String) -> JSValue! { let ctx = commonContext ?? createContext() return ctx.evaluateScript(string) } private func json(for string: String) -> JSON { evaluate(string: "JSON.stringify(\(string));")!.toString()! } func call(function: String, with arguments: [JSON]) -> JSON { let joined = arguments.map(\.string).joined(separator: ",") return json(for: "\(function)(\(joined))") } func inCommonContext(execute: (JavaScriptWorker) -> Value) -> Value { commonContext = createContext() defer { commonContext = nil } return execute(self) } }