1// Copyright 2018-present 650 Industries. All rights reserved.
2
3#import <jsi/jsi.h>
4
5#import <ExpoModulesCore/EXJavaScriptRuntime.h>
6
7#import <ExpoModulesCore/ExpoModulesHostObject.h>
8#import <ExpoModulesCore/ExpoModulesProxySpec.h>
9#import <ExpoModulesCore/EXJSIConversions.h>
10#import <ExpoModulesCore/Swift.h>
11
12using namespace facebook;
13
14@implementation EXJavaScriptRuntime {
15  jsi::Runtime *_runtime;
16  std::shared_ptr<react::CallInvoker> _jsCallInvoker;
17
18  EXJavaScriptObject *_global;
19}
20
21- (nonnull instancetype)initWithRuntime:(jsi::Runtime &)runtime callInvoker:(std::shared_ptr<react::CallInvoker>)callInvoker
22{
23  if (self = [super init]) {
24    _runtime = &runtime;
25    _jsCallInvoker = callInvoker;
26
27    auto jsGlobalPtr = std::make_shared<jsi::Object>(_runtime->global());
28    _global = [[EXJavaScriptObject alloc] initWith:jsGlobalPtr runtime:self];
29  }
30  return self;
31}
32
33- (nonnull jsi::Runtime *)get
34{
35  return _runtime;
36}
37
38- (std::shared_ptr<react::CallInvoker>)callInvoker
39{
40  return _jsCallInvoker;
41}
42
43- (nonnull EXJavaScriptObject *)createObject
44{
45  auto jsObjectPtr = std::make_shared<jsi::Object>(*_runtime);
46  return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
47}
48
49- (nonnull EXJavaScriptObject *)createHostObject:(std::shared_ptr<jsi::HostObject>)jsiHostObjectPtr
50{
51  auto jsObjectPtr = std::make_shared<jsi::Object>(jsi::Object::createFromHostObject(*_runtime, jsiHostObjectPtr));
52  return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
53}
54
55- (nonnull EXJavaScriptObject *)global
56{
57  return _global;
58}
59
60- (jsi::Function)createSyncFunction:(nonnull NSString *)name
61                          argsCount:(NSInteger)argsCount
62                              block:(nonnull JSSyncFunctionBlock)block
63{
64  return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray * _Nonnull arguments) {
65    return expo::convertObjCObjectToJSIValue(runtime, block(arguments));
66  }];
67}
68
69- (jsi::Function)createAsyncFunction:(nonnull NSString *)name
70                           argsCount:(NSInteger)argsCount
71                               block:(nonnull JSAsyncFunctionBlock)block
72{
73  return [self createHostFunction:name argsCount:argsCount block:^jsi::Value(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray *arguments) {
74    // The function that is invoked as a setup of the EXJavaScript `Promise`.
75    auto promiseSetup = [callInvoker, block, arguments](jsi::Runtime &runtime, std::shared_ptr<Promise> promise) {
76      expo::callPromiseSetupWithBlock(runtime, callInvoker, promise, ^(RCTPromiseResolveBlock resolver, RCTPromiseRejectBlock rejecter) {
77        block(arguments, resolver, rejecter);
78      });
79    };
80    return createPromiseAsJSIValue(runtime, promiseSetup);
81  }];
82}
83
84#pragma mark - Private
85
86typedef jsi::Value (^JSHostFunctionBlock)(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker, NSArray * _Nonnull arguments);
87
88- (jsi::Function)createHostFunction:(nonnull NSString *)name
89                          argsCount:(NSInteger)argsCount
90                              block:(nonnull JSHostFunctionBlock)block
91{
92  jsi::PropNameID propNameId = jsi::PropNameID::forAscii(*_runtime, [name UTF8String], [name length]);
93  std::weak_ptr<react::CallInvoker> weakCallInvoker = _jsCallInvoker;
94  jsi::HostFunctionType function = [weakCallInvoker, block](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
95    if (auto callInvoker = weakCallInvoker.lock()) {
96      NSArray *arguments = expo::convertJSIValuesToNSArray(runtime, args, count, callInvoker);
97      return block(runtime, callInvoker, arguments);
98    }
99    // TODO: We should throw some kind of error.
100    return jsi::Value::undefined();
101  };
102  return jsi::Function::createFromHostFunction(*_runtime, propNameId, (unsigned int)argsCount, function);
103}
104
105@end
106