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