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; 110 111 try { 112 result = std::make_shared<jsi::Value>(_runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>")); 113 } catch (jsi::JSError &error) { 114 NSString *reason = [NSString stringWithUTF8String:error.getMessage().c_str()]; 115 NSString *stack = [NSString stringWithUTF8String:error.getStack().c_str()]; 116 117 @throw [NSException exceptionWithName:@"ScriptEvaluationException" reason:reason userInfo:@{ 118 @"message": reason, 119 @"stack": stack, 120 }]; 121 } 122 return [[EXJavaScriptValue alloc] initWithRuntime:self value:result]; 123} 124 125#pragma mark - Private 126 127typedef jsi::Value (^JSHostFunctionBlock)(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray * _Nonnull arguments); 128 129- (jsi::Function)createHostFunction:(nonnull NSString *)name 130 argsCount:(NSInteger)argsCount 131 block:(nonnull JSHostFunctionBlock)block 132{ 133 jsi::PropNameID propNameId = jsi::PropNameID::forAscii(*_runtime, [name UTF8String], [name length]); 134 std::weak_ptr<react::CallInvoker> weakCallInvoker = _jsCallInvoker; 135 jsi::HostFunctionType function = [weakCallInvoker, block](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value { 136 if (auto callInvoker = weakCallInvoker.lock()) { 137 NSArray *arguments = expo::convertJSIValuesToNSArray(runtime, args, count, callInvoker); 138 return block(runtime, callInvoker, arguments); 139 } 140 // TODO: We should throw some kind of error. 141 return jsi::Value::undefined(); 142 }; 143 return jsi::Function::createFromHostFunction(*_runtime, propNameId, (unsigned int)argsCount, function); 144} 145 146@end 147