1// Copyright 2018-present 650 Industries. All rights reserved.
2
3#import <jsi/jsi.h>
4
5#if __has_include(<reacthermes/HermesExecutorFactory.h>)
6#import <reacthermes/HermesExecutorFactory.h>
7#elif __has_include(<hermes/hermes.h>)
8#import <hermes/hermes.h>
9#else
10#import <jsi/JSCRuntime.h>
11#endif
12
13#import <ExpoModulesCore/EXJavaScriptRuntime.h>
14#import <ExpoModulesCore/ExpoModulesHostObject.h>
15#import <ExpoModulesCore/EXJSIUtils.h>
16#import <ExpoModulesCore/EXJSIConversions.h>
17#import <ExpoModulesCore/Swift.h>
18
19using namespace facebook;
20
21@implementation EXJavaScriptRuntime {
22  std::shared_ptr<jsi::Runtime> _runtime;
23  std::shared_ptr<react::CallInvoker> _jsCallInvoker;
24}
25
26- (nonnull instancetype)init
27{
28  if (self = [super init]) {
29#if __has_include(<reacthermes/HermesExecutorFactory.h>) || __has_include(<hermes/hermes.h>)
30    _runtime = hermes::makeHermesRuntime();
31#else
32    _runtime = jsc::makeJSCRuntime();
33#endif
34    _jsCallInvoker = nil;
35  }
36  return self;
37}
38
39- (nonnull instancetype)initWithRuntime:(std::shared_ptr<jsi::Runtime>)runtime callInvoker:(std::shared_ptr<react::CallInvoker>)callInvoker
40{
41  if (self = [super init]) {
42    _runtime = runtime;
43    _jsCallInvoker = callInvoker;
44  }
45  return self;
46}
47
48- (nonnull jsi::Runtime *)get
49{
50  return _runtime.get();
51}
52
53- (std::shared_ptr<react::CallInvoker>)callInvoker
54{
55  return _jsCallInvoker;
56}
57
58- (nonnull EXJavaScriptObject *)createObject
59{
60  auto jsObjectPtr = std::make_shared<jsi::Object>(*_runtime);
61  return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
62}
63
64- (nonnull EXJavaScriptObject *)createHostObject:(std::shared_ptr<jsi::HostObject>)jsiHostObjectPtr
65{
66  auto jsObjectPtr = std::make_shared<jsi::Object>(jsi::Object::createFromHostObject(*_runtime, jsiHostObjectPtr));
67  return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
68}
69
70- (nonnull EXJavaScriptObject *)global
71{
72  auto jsGlobalPtr = std::make_shared<jsi::Object>(_runtime->global());
73  return [[EXJavaScriptObject alloc] initWith:jsGlobalPtr runtime:self];
74}
75
76- (jsi::Function)createSyncFunction:(nonnull NSString *)name
77                          argsCount:(NSInteger)argsCount
78                              block:(nonnull JSSyncFunctionBlock)block
79{
80  return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray * _Nonnull arguments) {
81    return expo::convertObjCObjectToJSIValue(runtime, block(arguments));
82  }];
83}
84
85- (jsi::Function)createAsyncFunction:(nonnull NSString *)name
86                           argsCount:(NSInteger)argsCount
87                               block:(nonnull JSAsyncFunctionBlock)block
88{
89  return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray *arguments) {
90    // The function that is invoked as a setup of the EXJavaScript `Promise`.
91    auto promiseSetup = [callInvoker, block, arguments](jsi::Runtime &runtime, std::shared_ptr<Promise> promise) {
92      expo::callPromiseSetupWithBlock(runtime, callInvoker, promise, ^(RCTPromiseResolveBlock resolver, RCTPromiseRejectBlock rejecter) {
93        block(arguments, resolver, rejecter);
94      });
95    };
96    return createPromiseAsJSIValue(runtime, promiseSetup);
97  }];
98}
99
100#pragma mark - Script evaluation
101
102- (nullable id)evaluateScript:(nonnull NSString *)scriptSource
103{
104  auto scriptBuffer = std::make_shared<jsi::StringBuffer>([[NSString stringWithFormat:@"(%@)", scriptSource] UTF8String]);
105  auto result = _runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>");
106  return expo::convertJSIValueToObjCObject(*_runtime, result, _jsCallInvoker);
107}
108
109#pragma mark - Private
110
111typedef jsi::Value (^JSHostFunctionBlock)(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray * _Nonnull arguments);
112
113- (jsi::Function)createHostFunction:(nonnull NSString *)name
114                          argsCount:(NSInteger)argsCount
115                              block:(nonnull JSHostFunctionBlock)block
116{
117  jsi::PropNameID propNameId = jsi::PropNameID::forAscii(*_runtime, [name UTF8String], [name length]);
118  std::weak_ptr<react::CallInvoker> weakCallInvoker = _jsCallInvoker;
119  jsi::HostFunctionType function = [weakCallInvoker, block](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
120    if (auto callInvoker = weakCallInvoker.lock()) {
121      NSArray *arguments = expo::convertJSIValuesToNSArray(runtime, args, count, callInvoker);
122      return block(runtime, callInvoker, arguments);
123    }
124    // TODO: We should throw some kind of error.
125    return jsi::Value::undefined();
126  };
127  return jsi::Function::createFromHostFunction(*_runtime, propNameId, (unsigned int)argsCount, function);
128}
129
130@end
131