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    Evaluates the JavaScript code made by joining an array of strings with a newline separator.
35    See the other ``eval(_:)`` for more details.
36    */
37   @discardableResult
38   func eval(_ source: [String]) throws -> JavaScriptValue {
39     try eval(source.joined(separator: "\n"))
40   }
41 
42   /**
43    Creates a synchronous host function that runs the given closure when it's called.
44    The value returned by the closure is synchronously returned to JS.
45    - Returns: A JavaScript function represented as a `JavaScriptObject`.
46    - Note: It refines the ObjC implementation from `EXJavaScriptRuntime` to properly catch Swift errors and rethrow them as ObjC `NSError`.
47    */
48   func createSyncFunction(_ name: String, argsCount: Int = 0, closure: @escaping SyncFunctionClosure) -> JavaScriptObject {
49     return __createSyncFunction(name, argsCount: argsCount) { this, args, errorPointer in
50       do {
51         return try runWithErrorPointer(errorPointer) {
52           return try closure(this, args)
53         }
54       } catch {
55         // Nicely log all errors to the console.
56         log.error(error)
57 
58         // Can return anything as the error will be caught through the error pointer already.
59         return nil
60       }
61     }
62   }
63 }
64 
65 internal final class JavaScriptEvalException: GenericException<NSError> {
66   override var reason: String {
67     return param.userInfo["message"] as? String ?? "unknown reason"
68   }
69 }
70