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