1370fa39dSKudo Chien// Copyright 2018-present 650 Industries. All rights reserved. 2370fa39dSKudo Chien 3370fa39dSKudo Chien#import <jsi/jsi.h> 4370fa39dSKudo Chien 54bcb0d98STomasz Sapeta#if __has_include(<reacthermes/HermesExecutorFactory.h>) 64bcb0d98STomasz Sapeta#import <reacthermes/HermesExecutorFactory.h> 7d4cd4dfcSKudo Chien#elif __has_include(<React-jsc/JSCRuntime.h>) 8d4cd4dfcSKudo Chien// react-native@>=0.71 has a specific React-jsc pod 9d4cd4dfcSKudo Chien#import <React-jsc/JSCRuntime.h> 1032c7a27aSGabriel Donadel Dall'Agnol// use_frameworks! transforms the dash to underscore 1132c7a27aSGabriel Donadel Dall'Agnol#elif __has_include(<React_jsc/JSCRuntime.h>) 1232c7a27aSGabriel Donadel Dall'Agnol#import <React_jsc/JSCRuntime.h> 134bcb0d98STomasz Sapeta#else 144bcb0d98STomasz Sapeta#import <jsi/JSCRuntime.h> 154bcb0d98STomasz Sapeta#endif 16370fa39dSKudo Chien 174bcb0d98STomasz Sapeta#import <ExpoModulesCore/EXJavaScriptRuntime.h> 18370fa39dSKudo Chien#import <ExpoModulesCore/ExpoModulesHostObject.h> 19bd0cffe7STomasz Sapeta#import <ExpoModulesCore/EXJSIUtils.h> 20370fa39dSKudo Chien#import <ExpoModulesCore/EXJSIConversions.h> 21370fa39dSKudo Chien#import <ExpoModulesCore/Swift.h> 22370fa39dSKudo Chien 23370fa39dSKudo Chien@implementation EXJavaScriptRuntime { 244bcb0d98STomasz Sapeta std::shared_ptr<jsi::Runtime> _runtime; 25370fa39dSKudo Chien std::shared_ptr<react::CallInvoker> _jsCallInvoker; 26370fa39dSKudo Chien} 27370fa39dSKudo Chien 28f57544baSTomasz Sapeta/** 29f57544baSTomasz Sapeta Initializes a runtime that is independent from React Native and its runtime initialization. 30f57544baSTomasz Sapeta This flow is mostly intended for tests. The JS call invoker is unavailable thus calling async functions is not supported. 31f57544baSTomasz Sapeta TODO: Implement the call invoker when it becomes necessary. 32f57544baSTomasz Sapeta */ 334bcb0d98STomasz Sapeta- (nonnull instancetype)init 34370fa39dSKudo Chien{ 35370fa39dSKudo Chien if (self = [super init]) { 36e68edf7aSKudo Chien#if __has_include(<reacthermes/HermesExecutorFactory.h>) 37e68edf7aSKudo Chien _runtime = facebook::hermes::makeHermesRuntime(); 384bcb0d98STomasz Sapeta#else 394bcb0d98STomasz Sapeta _runtime = jsc::makeJSCRuntime(); 404bcb0d98STomasz Sapeta#endif 414bcb0d98STomasz Sapeta _jsCallInvoker = nil; 424bcb0d98STomasz Sapeta } 434bcb0d98STomasz Sapeta return self; 444bcb0d98STomasz Sapeta} 45370fa39dSKudo Chien 46ec62a260STomasz Sapeta- (nonnull instancetype)initWithRuntime:(nonnull jsi::Runtime *)runtime 47ec62a260STomasz Sapeta callInvoker:(std::shared_ptr<react::CallInvoker>)callInvoker 484bcb0d98STomasz Sapeta{ 494bcb0d98STomasz Sapeta if (self = [super init]) { 5060410a3bSTomasz Sapeta // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it. 5160410a3bSTomasz Sapeta // In this code flow, the runtime should be owned by something else like the RCTBridge. 5260410a3bSTomasz Sapeta // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr 5360410a3bSTomasz Sapeta _runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), runtime); 544bcb0d98STomasz Sapeta _jsCallInvoker = callInvoker; 55370fa39dSKudo Chien } 56370fa39dSKudo Chien return self; 57370fa39dSKudo Chien} 58370fa39dSKudo Chien 59370fa39dSKudo Chien- (nonnull jsi::Runtime *)get 60370fa39dSKudo Chien{ 614bcb0d98STomasz Sapeta return _runtime.get(); 62370fa39dSKudo Chien} 63370fa39dSKudo Chien 64370fa39dSKudo Chien- (std::shared_ptr<react::CallInvoker>)callInvoker 65370fa39dSKudo Chien{ 66370fa39dSKudo Chien return _jsCallInvoker; 67370fa39dSKudo Chien} 68370fa39dSKudo Chien 69370fa39dSKudo Chien- (nonnull EXJavaScriptObject *)createObject 70370fa39dSKudo Chien{ 71370fa39dSKudo Chien auto jsObjectPtr = std::make_shared<jsi::Object>(*_runtime); 72370fa39dSKudo Chien return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self]; 73370fa39dSKudo Chien} 74370fa39dSKudo Chien 75370fa39dSKudo Chien- (nonnull EXJavaScriptObject *)createHostObject:(std::shared_ptr<jsi::HostObject>)jsiHostObjectPtr 76370fa39dSKudo Chien{ 77370fa39dSKudo Chien auto jsObjectPtr = std::make_shared<jsi::Object>(jsi::Object::createFromHostObject(*_runtime, jsiHostObjectPtr)); 78370fa39dSKudo Chien return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self]; 79370fa39dSKudo Chien} 80370fa39dSKudo Chien 81370fa39dSKudo Chien- (nonnull EXJavaScriptObject *)global 82370fa39dSKudo Chien{ 834bcb0d98STomasz Sapeta auto jsGlobalPtr = std::make_shared<jsi::Object>(_runtime->global()); 844bcb0d98STomasz Sapeta return [[EXJavaScriptObject alloc] initWith:jsGlobalPtr runtime:self]; 85370fa39dSKudo Chien} 86370fa39dSKudo Chien 87447e3428STomasz Sapeta- (nonnull EXJavaScriptObject *)createSyncFunction:(nonnull NSString *)name 88370fa39dSKudo Chien argsCount:(NSInteger)argsCount 89370fa39dSKudo Chien block:(nonnull JSSyncFunctionBlock)block 90370fa39dSKudo Chien{ 919b8bcdc4STomasz Sapeta JSHostFunctionBlock hostFunctionBlock = ^jsi::Value( 929b8bcdc4STomasz Sapeta jsi::Runtime &runtime, 939b8bcdc4STomasz Sapeta std::shared_ptr<react::CallInvoker> callInvoker, 949b8bcdc4STomasz Sapeta EXJavaScriptValue * _Nonnull thisValue, 959b8bcdc4STomasz Sapeta NSArray<EXJavaScriptValue *> * _Nonnull arguments) { 964d7ea828STomasz Sapeta NSError *error; 974d7ea828STomasz Sapeta id result = block(thisValue, arguments, &error); 984d7ea828STomasz Sapeta 994d7ea828STomasz Sapeta if (error == nil) { 1004d7ea828STomasz Sapeta return expo::convertObjCObjectToJSIValue(runtime, result); 1014d7ea828STomasz Sapeta } else { 1024d7ea828STomasz Sapeta throw jsi::JSError(runtime, [error.userInfo[@"message"] UTF8String]); 1034d7ea828STomasz Sapeta } 1049b8bcdc4STomasz Sapeta }; 1059b8bcdc4STomasz Sapeta return [self createHostFunction:name argsCount:argsCount block:hostFunctionBlock]; 106370fa39dSKudo Chien} 107370fa39dSKudo Chien 108447e3428STomasz Sapeta- (nonnull EXJavaScriptObject *)createAsyncFunction:(nonnull NSString *)name 109370fa39dSKudo Chien argsCount:(NSInteger)argsCount 110370fa39dSKudo Chien block:(nonnull JSAsyncFunctionBlock)block 111370fa39dSKudo Chien{ 1129b8bcdc4STomasz Sapeta JSHostFunctionBlock hostFunctionBlock = ^jsi::Value( 1139b8bcdc4STomasz Sapeta jsi::Runtime &runtime, 1149b8bcdc4STomasz Sapeta std::shared_ptr<react::CallInvoker> callInvoker, 1159b8bcdc4STomasz Sapeta EXJavaScriptValue * _Nonnull thisValue, 1169b8bcdc4STomasz Sapeta NSArray<EXJavaScriptValue *> * _Nonnull arguments) { 117f57544baSTomasz Sapeta if (!callInvoker) { 118f57544baSTomasz Sapeta // In mocked environment the call invoker may be null so it's not supported to call async functions. 119f57544baSTomasz Sapeta // Testing async functions is a bit more complicated anyway. See `init` description for more. 120f57544baSTomasz Sapeta throw jsi::JSError(runtime, "Calling async functions is not supported when the call invoker is unavailable"); 121f57544baSTomasz Sapeta } 122370fa39dSKudo Chien // The function that is invoked as a setup of the EXJavaScript `Promise`. 1239b8bcdc4STomasz Sapeta auto promiseSetup = [callInvoker, block, thisValue, arguments](jsi::Runtime &runtime, std::shared_ptr<Promise> promise) { 124370fa39dSKudo Chien expo::callPromiseSetupWithBlock(runtime, callInvoker, promise, ^(RCTPromiseResolveBlock resolver, RCTPromiseRejectBlock rejecter) { 1259b8bcdc4STomasz Sapeta block(thisValue, arguments, resolver, rejecter); 126370fa39dSKudo Chien }); 127370fa39dSKudo Chien }; 128370fa39dSKudo Chien return createPromiseAsJSIValue(runtime, promiseSetup); 1299b8bcdc4STomasz Sapeta }; 1309b8bcdc4STomasz Sapeta return [self createHostFunction:name argsCount:argsCount block:hostFunctionBlock]; 131370fa39dSKudo Chien} 132370fa39dSKudo Chien 1337e0c1e78STomasz Sapeta#pragma mark - Classes 1347e0c1e78STomasz Sapeta 1357e0c1e78STomasz Sapeta- (nonnull EXJavaScriptObject *)createClass:(nonnull NSString *)name 1367e0c1e78STomasz Sapeta constructor:(nonnull ClassConstructorBlock)constructor 1377e0c1e78STomasz Sapeta{ 1389b8bcdc4STomasz Sapeta expo::ClassConstructor jsConstructor = [self, constructor](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { 1397e0c1e78STomasz Sapeta std::shared_ptr<jsi::Object> thisPtr = std::make_shared<jsi::Object>(thisValue.asObject(runtime)); 1407e0c1e78STomasz Sapeta EXJavaScriptObject *caller = [[EXJavaScriptObject alloc] initWith:thisPtr runtime:self]; 1419b8bcdc4STomasz Sapeta NSArray<EXJavaScriptValue *> *arguments = expo::convertJSIValuesToNSArray(self, args, count); 1427e0c1e78STomasz Sapeta 1437e0c1e78STomasz Sapeta constructor(caller, arguments); 1449b8bcdc4STomasz Sapeta }; 1459b8bcdc4STomasz Sapeta std::shared_ptr<jsi::Function> klass = expo::createClass(*_runtime, [name UTF8String], jsConstructor); 1467e0c1e78STomasz Sapeta return [[EXJavaScriptObject alloc] initWith:klass runtime:self]; 1477e0c1e78STomasz Sapeta} 1487e0c1e78STomasz Sapeta 149*7a261681STomasz Sapeta- (nullable EXJavaScriptObject *)createObjectWithPrototype:(nonnull EXJavaScriptObject *)prototype 150*7a261681STomasz Sapeta{ 151*7a261681STomasz Sapeta std::shared_ptr<jsi::Object> object = expo::createObjectWithPrototype(*_runtime, [prototype getShared]); 152*7a261681STomasz Sapeta return object ? [[EXJavaScriptObject alloc] initWith:object runtime:self] : nil; 153*7a261681STomasz Sapeta} 154*7a261681STomasz Sapeta 1554bcb0d98STomasz Sapeta#pragma mark - Script evaluation 1564bcb0d98STomasz Sapeta 157ec62a260STomasz Sapeta- (nonnull EXJavaScriptValue *)evaluateScript:(nonnull NSString *)scriptSource 1584bcb0d98STomasz Sapeta{ 15956c10913STomasz Sapeta std::shared_ptr<jsi::StringBuffer> scriptBuffer = std::make_shared<jsi::StringBuffer>([scriptSource UTF8String]); 160d84b7ae9STomasz Sapeta std::shared_ptr<jsi::Value> result; 161d84b7ae9STomasz Sapeta 162d84b7ae9STomasz Sapeta try { 163d84b7ae9STomasz Sapeta result = std::make_shared<jsi::Value>(_runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>")); 164d84b7ae9STomasz Sapeta } catch (jsi::JSError &error) { 165d84b7ae9STomasz Sapeta NSString *reason = [NSString stringWithUTF8String:error.getMessage().c_str()]; 166d84b7ae9STomasz Sapeta NSString *stack = [NSString stringWithUTF8String:error.getStack().c_str()]; 167d84b7ae9STomasz Sapeta 168d84b7ae9STomasz Sapeta @throw [NSException exceptionWithName:@"ScriptEvaluationException" reason:reason userInfo:@{ 169d84b7ae9STomasz Sapeta @"message": reason, 170d84b7ae9STomasz Sapeta @"stack": stack, 171d84b7ae9STomasz Sapeta }]; 172159a70f7SŁukasz Kosmaty } catch (jsi::JSIException &error) { 173159a70f7SŁukasz Kosmaty NSString *reason = [NSString stringWithUTF8String:error.what()]; 174159a70f7SŁukasz Kosmaty 175159a70f7SŁukasz Kosmaty @throw [NSException exceptionWithName:@"ScriptEvaluationException" reason:reason userInfo:@{ 176159a70f7SŁukasz Kosmaty @"message": reason 177159a70f7SŁukasz Kosmaty }]; 178d84b7ae9STomasz Sapeta } 179ec62a260STomasz Sapeta return [[EXJavaScriptValue alloc] initWithRuntime:self value:result]; 1804bcb0d98STomasz Sapeta} 1814bcb0d98STomasz Sapeta 182370fa39dSKudo Chien#pragma mark - Private 183370fa39dSKudo Chien 184447e3428STomasz Sapeta- (nonnull EXJavaScriptObject *)createHostFunction:(nonnull NSString *)name 185370fa39dSKudo Chien argsCount:(NSInteger)argsCount 186370fa39dSKudo Chien block:(nonnull JSHostFunctionBlock)block 187370fa39dSKudo Chien{ 188370fa39dSKudo Chien jsi::PropNameID propNameId = jsi::PropNameID::forAscii(*_runtime, [name UTF8String], [name length]); 189370fa39dSKudo Chien std::weak_ptr<react::CallInvoker> weakCallInvoker = _jsCallInvoker; 190c4fc6f47STomasz Sapeta jsi::HostFunctionType function = [weakCallInvoker, block, self](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value { 191f57544baSTomasz Sapeta // Theoretically should check here whether the call invoker isn't null, but in mocked environment 192f57544baSTomasz Sapeta // there is no need to care about that for synchronous calls, so it's ensured in `createAsyncFunction` instead. 193f57544baSTomasz Sapeta auto callInvoker = weakCallInvoker.lock(); 194c4fc6f47STomasz Sapeta NSArray<EXJavaScriptValue *> *arguments = expo::convertJSIValuesToNSArray(self, args, count); 1959b8bcdc4STomasz Sapeta std::shared_ptr<jsi::Value> thisValPtr = std::make_shared<jsi::Value>(runtime, std::move(thisVal)); 1969b8bcdc4STomasz Sapeta EXJavaScriptValue *thisValue = [[EXJavaScriptValue alloc] initWithRuntime:self value:thisValPtr]; 1979b8bcdc4STomasz Sapeta 1989b8bcdc4STomasz Sapeta return block(runtime, callInvoker, thisValue, arguments); 199370fa39dSKudo Chien }; 200447e3428STomasz Sapeta std::shared_ptr<jsi::Object> fnPtr = std::make_shared<jsi::Object>(jsi::Function::createFromHostFunction(*_runtime, propNameId, (unsigned int)argsCount, function)); 201447e3428STomasz Sapeta return [[EXJavaScriptObject alloc] initWith:fnPtr runtime:self]; 202370fa39dSKudo Chien} 203370fa39dSKudo Chien 204370fa39dSKudo Chien@end 205