JavaScriptWorker.swift 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import Foundation
  2. import JavaScriptCore
  3. private let contextLock = NSRecursiveLock()
  4. final class JavaScriptWorker {
  5. private let processQueue = DispatchQueue(label: "DispatchQueue.JavaScriptWorker")
  6. private let virtualMachine: JSVirtualMachine
  7. @SyncAccess(lock: contextLock) private var commonContext: JSContext? = nil
  8. init() {
  9. virtualMachine = processQueue.sync { JSVirtualMachine()! }
  10. }
  11. private func createContext() -> JSContext {
  12. let context = JSContext(virtualMachine: virtualMachine)!
  13. context.exceptionHandler = { _, exception in
  14. if let error = exception?.toString() {
  15. warning(.openAPS, "JavaScript Error: \(error)")
  16. }
  17. }
  18. let consoleLog: @convention(block) (String) -> Void = { message in
  19. debug(.openAPS, "JavaScript log: \(message)")
  20. }
  21. context.setObject(
  22. consoleLog,
  23. forKeyedSubscript: "_consoleLog" as NSString
  24. )
  25. return context
  26. }
  27. @discardableResult func evaluate(script: Script) -> JSValue! {
  28. evaluate(string: script.body)
  29. }
  30. private func evaluate(string: String) -> JSValue! {
  31. let ctx = commonContext ?? createContext()
  32. return ctx.evaluateScript(string)
  33. }
  34. private func json(for string: String) -> RawJSON {
  35. evaluate(string: "JSON.stringify(\(string), null, 4);")!.toString()!
  36. }
  37. func call(function: String, with arguments: [JSON]) -> RawJSON {
  38. let joined = arguments.map(\.rawJSON).joined(separator: ",")
  39. return json(for: "\(function)(\(joined))")
  40. }
  41. func inCommonContext<Value>(execute: (JavaScriptWorker) -> Value) -> Value {
  42. commonContext = createContext()
  43. defer {
  44. commonContext = nil
  45. }
  46. return execute(self)
  47. }
  48. }