1 // Copyright 2023-present 650 Industries. All rights reserved. 2 3 /** 4 Represents a JavaScript function that can be called by the native code and that must return the given generic `ReturnType`. 5 */ 6 public final class JavaScriptFunction<ReturnType>: AnyArgument, AnyJavaScriptValue { 7 /** 8 Raw representation of the JavaScript function that doesn't impose any restrictions on the returned type. 9 */ 10 private let rawFunction: RawJavaScriptFunction 11 12 /** 13 Weak reference to the app context that is necessary to convert some arguments associated with the context (e.g. shared objects). 14 */ 15 private weak var appContext: AppContext? 16 17 init(rawFunction: RawJavaScriptFunction, appContext: AppContext) { 18 self.rawFunction = rawFunction 19 self.appContext = appContext 20 } 21 22 // MARK: - Calling 23 24 /** 25 Calls the function with the given `this` object and arguments. 26 */ 27 public func call(_ arguments: Any..., usingThis this: JavaScriptObject? = nil) throws -> ReturnType { 28 return try call(withArguments: arguments, asConstructor: false, usingThis: this) 29 } 30 31 /** 32 Calls the function as a constructor with the given arguments. It's like calling a function with the `new` keyword. 33 */ 34 public func callAsConstructor(_ arguments: Any...) throws -> ReturnType { 35 return try call(withArguments: arguments, asConstructor: true, usingThis: nil) 36 } 37 38 /** 39 Universal function that calls the function with given arguments, this object and whether to call it as a constructor. 40 */ 41 private func call(withArguments arguments: [Any] = [], asConstructor: Bool = false, usingThis this: JavaScriptObject? = nil) throws -> ReturnType { 42 guard let appContext else { 43 throw AppContextLostException() 44 } 45 let value = rawFunction.call(withArguments: arguments, thisObject: this, asConstructor: false) 46 let dynamicType = ~ReturnType.self 47 48 guard let result = try dynamicType.cast(jsValue: value, appContext: appContext) as? ReturnType else { 49 throw UnexpectedReturnType(dynamicType.description) 50 } 51 return result 52 } 53 54 // MARK: - AnyJavaScriptValue 55 56 internal static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self { 57 guard value.kind == .function else { 58 throw Conversions.ConvertingException<JavaScriptFunction<ReturnType>>(value) 59 } 60 return Self(rawFunction: value.getFunction(), appContext: appContext) 61 } 62 } 63 64 private final class UnexpectedReturnType: GenericException<String> { 65 override var reason: String { 66 return "The function returned a value that cannot be converted to \(param)" 67 } 68 } 69