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