1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 public extension JavaScriptRuntime {
4   /**
5    A type of the closure that you pass to the `createSyncFunction` function.
6    */
7   typealias SyncFunctionClosure = (_ this: JavaScriptValue, _ arguments: [JavaScriptValue]) throws -> Any
8 
9   /**
10    Evaluates JavaScript code represented as a string.
11 
12    - Parameter source: A string representing a JavaScript expression, statement, or sequence of statements.
13                        The expression can include variables and properties of existing objects.
14    - Returns: The completion value of evaluating the given code represented as `JavaScriptValue`.
15               If the completion value is empty, `undefined` is returned.
16    - Throws: `JavaScriptEvalException` when evaluated code has invalid syntax or throws an error.
17    - Note: It wraps the original `evaluateScript` to better handle and rethrow exceptions.
18    */
19   @discardableResult
20   func eval(_ source: String) throws -> JavaScriptValue {
21     do {
22       var result: JavaScriptValue?
23       try EXUtilities.catchException {
24         result = self.evaluateScript(source)
25       }
26       // There is no risk to force unwrapping as long as the `evaluateScript` returns nonnull value.
27       return result!
28     } catch {
29       throw JavaScriptEvalException(error as NSError)
30     }
31   }
32 
33   /**
34    Creates a synchronous host function that runs the given closure when it's called.
35    The value returned by the closure is synchronously returned to JS.
36    - Returns: A JavaScript function represented as a `JavaScriptObject`.
37    - Note: It refines the ObjC implementation from `EXJavaScriptRuntime` to properly catch Swift errors and rethrow them as ObjC `NSError`.
38    */
39   func createSyncFunction(_ name: String, argsCount: Int = 0, closure: @escaping SyncFunctionClosure) -> JavaScriptObject {
40     return __createSyncFunction(name, argsCount: argsCount) { this, args, errorPointer in
41       do {
42         return try runWithErrorPointer(errorPointer) {
43           return try closure(this, args)
44         }
45       } catch {
46         // Nicely log all errors to the console.
47         log.error(error)
48 
49         // Can return anything as the error will be caught through the error pointer already.
50         return nil
51       }
52     }
53   }
54 }
55 
56 internal final class JavaScriptEvalException: GenericException<NSError> {
57   override var reason: String {
58     return param.userInfo["message"] as? String ?? "unknown reason"
59   }
60 }
61