1ec62a260STomasz Sapeta // Copyright 2022-present 650 Industries. All rights reserved. 2ec62a260STomasz Sapeta 3ec62a260STomasz Sapeta public extension JavaScriptRuntime { 4ec62a260STomasz Sapeta /** 54d7ea828STomasz Sapeta A type of the closure that you pass to the `createSyncFunction` function. 64d7ea828STomasz Sapeta */ 74d7ea828STomasz Sapeta typealias SyncFunctionClosure = (_ this: JavaScriptValue, _ arguments: [JavaScriptValue]) throws -> Any 84d7ea828STomasz Sapeta 94d7ea828STomasz Sapeta /** 10ec62a260STomasz Sapeta Evaluates JavaScript code represented as a string. 11ec62a260STomasz Sapeta 12ec62a260STomasz Sapeta - Parameter source: A string representing a JavaScript expression, statement, or sequence of statements. 13ec62a260STomasz Sapeta The expression can include variables and properties of existing objects. 14ec62a260STomasz Sapeta - Returns: The completion value of evaluating the given code represented as `JavaScriptValue`. 15ec62a260STomasz Sapeta If the completion value is empty, `undefined` is returned. 16d84b7ae9STomasz Sapeta - Throws: `JavaScriptEvalException` when evaluated code has invalid syntax or throws an error. 17ec62a260STomasz Sapeta - Note: It wraps the original `evaluateScript` to better handle and rethrow exceptions. 18ec62a260STomasz Sapeta */ 19ce45e284STomasz Sapeta @discardableResult evalnull20ec62a260STomasz Sapeta func eval(_ source: String) throws -> JavaScriptValue { 21d84b7ae9STomasz Sapeta do { 22d84b7ae9STomasz Sapeta var result: JavaScriptValue? 23d84b7ae9STomasz Sapeta try EXUtilities.catchException { 24*419852c4STomasz Sapeta result = self.__evaluateScript(source) 25d84b7ae9STomasz Sapeta } 26d84b7ae9STomasz Sapeta // There is no risk to force unwrapping as long as the `evaluateScript` returns nonnull value. 27d84b7ae9STomasz Sapeta return result! 28d84b7ae9STomasz Sapeta } catch { 29d84b7ae9STomasz Sapeta throw JavaScriptEvalException(error as NSError) 30d84b7ae9STomasz Sapeta } 31d84b7ae9STomasz Sapeta } 324d7ea828STomasz Sapeta 334d7ea828STomasz Sapeta /** 34*419852c4STomasz Sapeta Evaluates the JavaScript code made by joining an array of strings with a newline separator. 35*419852c4STomasz Sapeta See the other ``eval(_:)`` for more details. 36*419852c4STomasz Sapeta */ 37*419852c4STomasz Sapeta @discardableResult evalnull38*419852c4STomasz Sapeta func eval(_ source: [String]) throws -> JavaScriptValue { 39*419852c4STomasz Sapeta try eval(source.joined(separator: "\n")) 40*419852c4STomasz Sapeta } 41*419852c4STomasz Sapeta 42*419852c4STomasz Sapeta /** 434d7ea828STomasz Sapeta Creates a synchronous host function that runs the given closure when it's called. 444d7ea828STomasz Sapeta The value returned by the closure is synchronously returned to JS. 454d7ea828STomasz Sapeta - Returns: A JavaScript function represented as a `JavaScriptObject`. 464d7ea828STomasz Sapeta - Note: It refines the ObjC implementation from `EXJavaScriptRuntime` to properly catch Swift errors and rethrow them as ObjC `NSError`. 474d7ea828STomasz Sapeta */ createSyncFunctionnull484d7ea828STomasz Sapeta func createSyncFunction(_ name: String, argsCount: Int = 0, closure: @escaping SyncFunctionClosure) -> JavaScriptObject { 494d7ea828STomasz Sapeta return __createSyncFunction(name, argsCount: argsCount) { this, args, errorPointer in 504d7ea828STomasz Sapeta do { 514d7ea828STomasz Sapeta return try runWithErrorPointer(errorPointer) { 524d7ea828STomasz Sapeta return try closure(this, args) 534d7ea828STomasz Sapeta } 544d7ea828STomasz Sapeta } catch { 554d7ea828STomasz Sapeta // Nicely log all errors to the console. 564d7ea828STomasz Sapeta log.error(error) 574d7ea828STomasz Sapeta 584d7ea828STomasz Sapeta // Can return anything as the error will be caught through the error pointer already. 594d7ea828STomasz Sapeta return nil 604d7ea828STomasz Sapeta } 614d7ea828STomasz Sapeta } 624d7ea828STomasz Sapeta } 63d84b7ae9STomasz Sapeta } 64d84b7ae9STomasz Sapeta 65d84b7ae9STomasz Sapeta internal final class JavaScriptEvalException: GenericException<NSError> { 66d84b7ae9STomasz Sapeta override var reason: String { 67d84b7ae9STomasz Sapeta return param.userInfo["message"] as? String ?? "unknown reason" 68ec62a260STomasz Sapeta } 69ec62a260STomasz Sapeta } 70