1 #include "MethodMetadata.h"
2 
3 #include "JSIInteropModuleRegistry.h"
4 
5 namespace jni = facebook::jni;
6 namespace jsi = facebook::jsi;
7 namespace react = facebook::react;
8 
9 namespace expo {
10 
11 // Modified version of the RN implementation
12 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
13 jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
14   jsi::Function &&function,
15   jsi::Runtime &rt,
16   std::shared_ptr<react::CallInvoker> jsInvoker
17 ) {
18   auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
19                                                         std::move(jsInvoker));
20 
21   // This needs to be a shared_ptr because:
22   // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
23   // not.
24   // 2. It cannot be weak_ptr since we need this object to live on.
25   // 3. It cannot be a value, because that would be deleted as soon as this
26   // function returns.
27   auto callbackWrapperOwner =
28     std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
29 
30   std::function<void(folly::dynamic)> fn =
31     [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false](
32       folly::dynamic responses) mutable {
33       if (wrapperWasCalled) {
34         throw std::runtime_error(
35           "callback 2 arg cannot be called more than once");
36       }
37 
38       auto strongWrapper = weakWrapper.lock();
39       if (!strongWrapper) {
40         return;
41       }
42 
43       strongWrapper->jsInvoker().invokeAsync(
44         [weakWrapper, callbackWrapperOwner, responses]() mutable {
45           auto strongWrapper2 = weakWrapper.lock();
46           if (!strongWrapper2) {
47             return;
48           }
49 
50           jsi::Value args =
51             jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
52           auto argsArray = args.getObject(strongWrapper2->runtime())
53             .asArray(strongWrapper2->runtime());
54           jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0);
55 
56           strongWrapper2->callback().call(
57             strongWrapper2->runtime(),
58             (const jsi::Value *) &arg,
59             (size_t) 1
60           );
61 
62           callbackWrapperOwner.reset();
63         });
64 
65       wrapperWasCalled = true;
66     };
67 
68   return react::JCxxCallbackImpl::newObjectCxxArgs(fn);
69 }
70 
71 MethodMetadata::MethodMetadata(
72   std::string name,
73   int args,
74   bool isAsync,
75   jni::global_ref<jobject> &&jBodyReference
76 ) : name(name),
77     args(args),
78     isAsync(isAsync),
79     jBodyReference(jBodyReference) {}
80 
81 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
82   jsi::Runtime &runtime,
83   JSIInteropModuleRegistry *moduleRegistry
84 ) {
85   if (body == nullptr) {
86     if (isAsync) {
87       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
88     } else {
89       body = std::make_shared<jsi::Function>(toSyncFunction(runtime));
90     }
91   }
92 
93   return body;
94 }
95 
96 jsi::Function MethodMetadata::toSyncFunction(jsi::Runtime &runtime) {
97   return jsi::Function::createFromHostFunction(
98     runtime,
99     jsi::PropNameID::forAscii(runtime, name),
100     args,
101     [this](
102       jsi::Runtime &rt,
103       const jsi::Value &thisValue,
104       const jsi::Value *args,
105       size_t count
106     ) -> jsi::Value {
107       auto dynamicArray = folly::dynamic::array();
108       for (int i = 0; i < count; i++) {
109         auto &arg = args[i];
110         dynamicArray.push_back(jsi::dynamicFromValue(rt, arg));
111       }
112 
113       // Cast in this place is safe, cause we know that this function is promise-less.
114       auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
115       auto result = syncFunction->invoke(
116         react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamicArray)).get()
117       );
118 
119       if (result == nullptr) {
120         return jsi::Value::undefined();
121       }
122 
123       return jsi::valueFromDynamic(rt, result->cthis()->consume())
124         .asObject(rt)
125         .asArray(rt)
126         .getValueAtIndex(rt, 0);
127     });
128 }
129 
130 jsi::Function MethodMetadata::toAsyncFunction(
131   jsi::Runtime &runtime,
132   JSIInteropModuleRegistry *moduleRegistry
133 ) {
134   return jsi::Function::createFromHostFunction(
135     runtime,
136     jsi::PropNameID::forAscii(runtime, name),
137     args,
138     [this, moduleRegistry](
139       jsi::Runtime &rt,
140       const jsi::Value &thisValue,
141       const jsi::Value *args,
142       size_t count
143     ) -> jsi::Value {
144       auto dynamicArray = folly::dynamic::array();
145       for (int i = 0; i < count; i++) {
146         auto &arg = args[i];
147         dynamicArray.push_back(jsi::dynamicFromValue(rt, arg));
148       }
149 
150       auto Promise = rt.global().getPropertyAsFunction(rt, "Promise");
151       // Creates a JSI promise
152       jsi::Value promise = Promise.callAsConstructor(
153         rt,
154         createPromiseBody(rt, moduleRegistry, std::move(dynamicArray))
155       );
156       return promise;
157     }
158   );
159 }
160 
161 jsi::Function MethodMetadata::createPromiseBody(
162   jsi::Runtime &runtime,
163   JSIInteropModuleRegistry *moduleRegistry,
164   folly::dynamic &&args
165 ) {
166   return jsi::Function::createFromHostFunction(
167     runtime,
168     jsi::PropNameID::forAscii(runtime, "promiseFn"),
169     2,
170     [this, args = std::move(args), moduleRegistry](
171       jsi::Runtime &rt,
172       const jsi::Value &thisVal,
173       const jsi::Value *promiseConstructorArgs,
174       size_t promiseConstructorArgCount
175     ) {
176       if (promiseConstructorArgCount != 2) {
177         throw std::invalid_argument("Promise fn arg count must be 2");
178       }
179 
180       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
181       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
182 
183       jobject resolve = createJavaCallbackFromJSIFunction(
184         std::move(resolveJSIFn),
185         rt,
186         moduleRegistry->jsInvoker
187       ).release();
188 
189       jobject reject = createJavaCallbackFromJSIFunction(
190         std::move(rejectJSIFn),
191         rt,
192         moduleRegistry->jsInvoker
193       ).release();
194 
195       JNIEnv *env = jni::Environment::current();
196 
197       jclass jPromiseImpl =
198         env->FindClass("com/facebook/react/bridge/PromiseImpl");
199       jmethodID jPromiseImplConstructor = env->GetMethodID(
200         jPromiseImpl,
201         "<init>",
202         "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V");
203 
204       // Creates a promise object
205       jobject promise = env->NewObject(
206         jPromiseImpl,
207         jPromiseImplConstructor,
208         resolve,
209         reject
210       );
211 
212       // Cast in this place is safe, cause we know that this function expects promise.
213       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
214       asyncFunction->invoke(
215         react::ReadableNativeArray::newObjectCxxArgs(args).get(),
216         promise
217       );
218 
219       // We have to remove the local reference to the promise object.
220       // It doesn't mean that the promise will be deallocated, but rather that we move
221       // the ownership to the `JNIAsyncFunctionBody`.
222       env->DeleteLocalRef(promise);
223 
224       return jsi::Value::undefined();
225     }
226   );
227 }
228 
229 } // namespace expo
230