// Copyright 2018-present 650 Industries. All rights reserved. #import #if __has_include() #import #elif __has_include() #import #else #import #endif #import #import #import #import #import using namespace facebook; @implementation EXJavaScriptRuntime { std::shared_ptr _runtime; std::shared_ptr _jsCallInvoker; } /** Initializes a runtime that is independent from React Native and its runtime initialization. This flow is mostly intended for tests. The JS call invoker is unavailable thus calling async functions is not supported. TODO: Implement the call invoker when it becomes necessary. */ - (nonnull instancetype)init { if (self = [super init]) { #if __has_include() || __has_include() _runtime = hermes::makeHermesRuntime(); #else _runtime = jsc::makeJSCRuntime(); #endif _jsCallInvoker = nil; } return self; } - (nonnull instancetype)initWithRuntime:(nonnull jsi::Runtime *)runtime callInvoker:(std::shared_ptr)callInvoker { if (self = [super init]) { // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it. // In this code flow, the runtime should be owned by something else like the RCTBridge. // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr _runtime = std::shared_ptr(std::shared_ptr(), runtime); _jsCallInvoker = callInvoker; } return self; } - (nonnull jsi::Runtime *)get { return _runtime.get(); } - (std::shared_ptr)callInvoker { return _jsCallInvoker; } - (nonnull EXJavaScriptObject *)createObject { auto jsObjectPtr = std::make_shared(*_runtime); return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self]; } - (nonnull EXJavaScriptObject *)createHostObject:(std::shared_ptr)jsiHostObjectPtr { auto jsObjectPtr = std::make_shared(jsi::Object::createFromHostObject(*_runtime, jsiHostObjectPtr)); return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self]; } - (nonnull EXJavaScriptObject *)global { auto jsGlobalPtr = std::make_shared(_runtime->global()); return [[EXJavaScriptObject alloc] initWith:jsGlobalPtr runtime:self]; } - (nonnull EXJavaScriptObject *)createSyncFunction:(nonnull NSString *)name argsCount:(NSInteger)argsCount block:(nonnull JSSyncFunctionBlock)block { return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr callInvoker, NSArray * _Nonnull arguments) { return expo::convertObjCObjectToJSIValue(runtime, block(arguments)); }]; } - (nonnull EXJavaScriptObject *)createAsyncFunction:(nonnull NSString *)name argsCount:(NSInteger)argsCount block:(nonnull JSAsyncFunctionBlock)block { return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr callInvoker, NSArray *arguments) { if (!callInvoker) { // In mocked environment the call invoker may be null so it's not supported to call async functions. // Testing async functions is a bit more complicated anyway. See `init` description for more. throw jsi::JSError(runtime, "Calling async functions is not supported when the call invoker is unavailable"); } // The function that is invoked as a setup of the EXJavaScript `Promise`. auto promiseSetup = [callInvoker, block, arguments](jsi::Runtime &runtime, std::shared_ptr promise) { expo::callPromiseSetupWithBlock(runtime, callInvoker, promise, ^(RCTPromiseResolveBlock resolver, RCTPromiseRejectBlock rejecter) { block(arguments, resolver, rejecter); }); }; return createPromiseAsJSIValue(runtime, promiseSetup); }]; } #pragma mark - Script evaluation - (nonnull EXJavaScriptValue *)evaluateScript:(nonnull NSString *)scriptSource { std::shared_ptr scriptBuffer = std::make_shared([scriptSource UTF8String]); std::shared_ptr result; try { result = std::make_shared(_runtime->evaluateJavaScript(scriptBuffer, "<>")); } catch (jsi::JSError &error) { NSString *reason = [NSString stringWithUTF8String:error.getMessage().c_str()]; NSString *stack = [NSString stringWithUTF8String:error.getStack().c_str()]; @throw [NSException exceptionWithName:@"ScriptEvaluationException" reason:reason userInfo:@{ @"message": reason, @"stack": stack, }]; } return [[EXJavaScriptValue alloc] initWithRuntime:self value:result]; } #pragma mark - Private - (nonnull EXJavaScriptObject *)createHostFunction:(nonnull NSString *)name argsCount:(NSInteger)argsCount block:(nonnull JSHostFunctionBlock)block { jsi::PropNameID propNameId = jsi::PropNameID::forAscii(*_runtime, [name UTF8String], [name length]); std::weak_ptr weakCallInvoker = _jsCallInvoker; jsi::HostFunctionType function = [weakCallInvoker, block, self](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value { // Theoretically should check here whether the call invoker isn't null, but in mocked environment // there is no need to care about that for synchronous calls, so it's ensured in `createAsyncFunction` instead. auto callInvoker = weakCallInvoker.lock(); NSArray *arguments = expo::convertJSIValuesToNSArray(self, args, count); return block(runtime, callInvoker, arguments); }; std::shared_ptr fnPtr = std::make_shared(jsi::Function::createFromHostFunction(*_runtime, propNameId, (unsigned int)argsCount, function)); return [[EXJavaScriptObject alloc] initWith:fnPtr runtime:self]; } @end