| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- import Foundation
- import JavaScriptCore
- private let contextLock = NSRecursiveLock()
- extension String {
- var lowercasingFirst: String { prefix(1).lowercased() + dropFirst() }
- var uppercasingFirst: String { prefix(1).uppercased() + dropFirst() }
- var camelCased: String {
- guard !isEmpty else { return "" }
- let parts = components(separatedBy: .alphanumerics.inverted)
- let first = parts.first!.lowercasingFirst
- let rest = parts.dropFirst().map(\.uppercasingFirst)
- return ([first] + rest).joined()
- }
- var pascalCased: String {
- guard !isEmpty else { return "" }
- let parts = components(separatedBy: .alphanumerics.inverted)
- let first = parts.first!.uppercasingFirst
- let rest = parts.dropFirst().map(\.uppercasingFirst)
- return ([first] + rest).joined()
- }
- }
- final class JavaScriptWorker {
- private let processQueue = DispatchQueue(label: "DispatchQueue.JavaScriptWorker", attributes: .concurrent)
- private let virtualMachine: JSVirtualMachine
- private var contextPool: [JSContext] = []
- private let contextPoolLock = NSLock()
- init(poolSize: Int = 5) {
- virtualMachine = JSVirtualMachine()!
- // Pre-create a pool of JSContext instances
- for _ in 0 ..< poolSize {
- contextPool.append(createContext())
- }
- }
- private func createContext() -> JSContext {
- let context = JSContext(virtualMachine: virtualMachine)!
- context.exceptionHandler = { _, exception in
- if let error = exception?.toString() {
- warning(.openAPS, "JavaScript Error: \(error)")
- }
- }
- let consoleLog: @convention(block) (String) -> Void = { [weak context] message in
- guard let context = context else { return }
- let trimmedMessage = message.trimmingCharacters(in: .whitespacesAndNewlines)
- if !trimmedMessage.isEmpty {
- let fileName = context.objectForKeyedSubscript("scriptName").toString() ?? "Unknown"
- let threadSafeLog = "\(trimmedMessage)"
- self.processQueue.async(flags: .barrier) {
- self.outputLogs(for: fileName, message: threadSafeLog)
- }
- }
- }
- context.setObject(consoleLog, forKeyedSubscript: "_consoleLog" as NSString)
- return context
- }
- private func getContext() -> JSContext {
- contextPoolLock.lock()
- let context = contextPool.popLast() ?? createContext()
- contextPoolLock.unlock()
- return context
- }
- private func returnContext(_ context: JSContext) {
- contextPoolLock.lock()
- contextPool.append(context)
- contextPoolLock.unlock()
- }
- private func outputLogs(for fileName: String, message: String) {
- let logs = message.trimmingCharacters(in: .whitespacesAndNewlines)
- if logs.isEmpty { return }
- if fileName == "autosens.js" {
- let sanitizedLogs = logs.split(separator: "\n").map { logLine in
- logLine.replacingOccurrences(
- of: "^[-+=x!]|u\\(|\\)|\\d{1,2}h$",
- with: "",
- options: .regularExpression
- )
- }.joined(separator: "\n")
- sanitizedLogs.split(separator: "\n").forEach { logLine in
- if !logLine.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
- debug(.openAPS, "\(fileName): \(logLine)")
- }
- }
- } else {
- logs.split(separator: "\n").forEach { logLine in
- if !logLine.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
- debug(.openAPS, "\(fileName): \(logLine)")
- }
- }
- }
- }
- @discardableResult func evaluate(script: Script) -> JSValue! {
- let context = getContext()
- defer { returnContext(context) }
- let fileName = URL(fileURLWithPath: script.name).lastPathComponent
- context.setObject(fileName, forKeyedSubscript: "scriptName" as NSString)
- let result = context.evaluateScript(script.body)
- return result
- }
- private func evaluate(string: String) -> JSValue! {
- let context = getContext()
- defer { returnContext(context) }
- return context.evaluateScript(string)
- }
- private func json(for string: String) -> RawJSON {
- evaluate(string: "JSON.stringify(\(string), null, 4);")!.toString()!
- }
- func call(function: String, with arguments: [JSON]) -> RawJSON {
- let joined = arguments.map(\.rawJSON).joined(separator: ",")
- return json(for: "\(function)(\(joined))")
- }
- func inCommonContext<Value>(execute: (JavaScriptWorker) -> Value) -> Value {
- let context = getContext()
- defer {
- returnContext(context)
- }
- return execute(self)
- }
- func evaluateBatch(scripts: [Script]) {
- let context = getContext()
- defer {
- returnContext(context)
- }
- scripts.forEach { script in
- let fileName = URL(fileURLWithPath: script.name).lastPathComponent
- context.setObject(fileName, forKeyedSubscript: "scriptName" as NSString)
- context.evaluateScript(script.body)
- }
- }
- }
|