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:(jsi::Runtime *)runtime callInvoker:(std::shared_ptr<react::CallInvoker>)callInvoker
40{
41  if (self = [super init]) {
42    // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it.
43    // In this code flow, the runtime should be owned by something else like the RCTBridge.
44    // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
45    _runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), runtime);
46    _jsCallInvoker = callInvoker;
47  }
48  return self;
49}
50
51- (nonnull jsi::Runtime *)get
52{
53  return _runtime.get();
54}
55
56- (std::shared_ptr<react::CallInvoker>)callInvoker
57{
58  return _jsCallInvoker;
59}
60
61- (nonnull EXJavaScriptObject *)createObject
62{
63  auto jsObjectPtr = std::make_shared<jsi::Object>(*_runtime);
64  return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
65}
66
67- (nonnull EXJavaScriptObject *)createHostObject:(std::shared_ptr<jsi::HostObject>)jsiHostObjectPtr
68{
69  auto jsObjectPtr = std::make_shared<jsi::Object>(jsi::Object::createFromHostObject(*_runtime, jsiHostObjectPtr));
70  return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
71}
72
73- (nonnull EXJavaScriptObject *)global
74{
75  auto jsGlobalPtr = std::make_shared<jsi::Object>(_runtime->global());
76  return [[EXJavaScriptObject alloc] initWith:jsGlobalPtr runtime:self];
77}
78
79- (jsi::Function)createSyncFunction:(nonnull NSString *)name
80                          argsCount:(NSInteger)argsCount
81                              block:(nonnull JSSyncFunctionBlock)block
82{
83  return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray * _Nonnull arguments) {
84    return expo::convertObjCObjectToJSIValue(runtime, block(arguments));
85  }];
86}
87
88- (jsi::Function)createAsyncFunction:(nonnull NSString *)name
89                           argsCount:(NSInteger)argsCount
90                               block:(nonnull JSAsyncFunctionBlock)block
91{
92  return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray *arguments) {
93    // The function that is invoked as a setup of the EXJavaScript `Promise`.
94    auto promiseSetup = [callInvoker, block, arguments](jsi::Runtime &runtime, std::shared_ptr<Promise> promise) {
95      expo::callPromiseSetupWithBlock(runtime, callInvoker, promise, ^(RCTPromiseResolveBlock resolver, RCTPromiseRejectBlock rejecter) {
96        block(arguments, resolver, rejecter);
97      });
98    };
99    return createPromiseAsJSIValue(runtime, promiseSetup);
100  }];
101}
102
103#pragma mark - Script evaluation
104
105- (nullable id)evaluateScript:(nonnull NSString *)scriptSource
106{
107  auto scriptBuffer = std::make_shared<jsi::StringBuffer>([[NSString stringWithFormat:@"(%@)", scriptSource] UTF8String]);
108  auto result = _runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>");
109  return expo::convertJSIValueToObjCObject(*_runtime, result, _jsCallInvoker);
110}
111
112#pragma mark - Private
113
114typedef jsi::Value (^JSHostFunctionBlock)(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray * _Nonnull arguments);
115
116- (jsi::Function)createHostFunction:(nonnull NSString *)name
117                          argsCount:(NSInteger)argsCount
118                              block:(nonnull JSHostFunctionBlock)block
119{
120  jsi::PropNameID propNameId = jsi::PropNameID::forAscii(*_runtime, [name UTF8String], [name length]);
121  std::weak_ptr<react::CallInvoker> weakCallInvoker = _jsCallInvoker;
122  jsi::HostFunctionType function = [weakCallInvoker, block](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
123    if (auto callInvoker = weakCallInvoker.lock()) {
124      NSArray *arguments = expo::convertJSIValuesToNSArray(runtime, args, count, callInvoker);
125      return block(runtime, callInvoker, arguments);
126    }
127    // TODO: We should throw some kind of error.
128    return jsi::Value::undefined();
129  };
130  return jsi::Function::createFromHostFunction(*_runtime, propNameId, (unsigned int)argsCount, function);
131}
132
133@end
134