1 #include "MethodMetadata.h"
2 #include "JSIInteropModuleRegistry.h"
3 #include "JavaScriptValue.h"
4 #include "JavaScriptObject.h"
5 #include "JavaScriptTypedArray.h"
6 #include "JavaReferencesCache.h"
7 #include "Exceptions.h"
8 #include "JavaCallback.h"
9 #include "types/JNIToJSIConverter.h"
10
11 #include <utility>
12 #include <functional>
13
14 #include "JSReferencesCache.h"
15
16 namespace jni = facebook::jni;
17 namespace jsi = facebook::jsi;
18 namespace react = facebook::react;
19
20 namespace expo {
21
22 // Modified version of the RN implementation
23 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
createJavaCallbackFromJSIFunction(jsi::Function && function,jsi::Runtime & rt,JSIInteropModuleRegistry * moduleRegistry,bool isRejectCallback=false)24 jni::local_ref<JavaCallback::JavaPart> createJavaCallbackFromJSIFunction(
25 jsi::Function &&function,
26 jsi::Runtime &rt,
27 JSIInteropModuleRegistry *moduleRegistry,
28 bool isRejectCallback = false
29 ) {
30 std::shared_ptr<react::CallInvoker> jsInvoker = moduleRegistry->runtimeHolder->jsInvoker;
31 auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
32 std::move(jsInvoker));
33
34 // This needs to be a shared_ptr because:
35 // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
36 // not.
37 // 2. It cannot be weak_ptr since we need this object to live on.
38 // 3. It cannot be a value, because that would be deleted as soon as this
39 // function returns.
40 auto callbackWrapperOwner =
41 std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
42
43 std::function<void(folly::dynamic)> fn =
44 [
45 weakWrapper,
46 callbackWrapperOwner = std::move(callbackWrapperOwner),
47 wrapperWasCalled = false,
48 isRejectCallback
49 ](
50 folly::dynamic responses) mutable {
51 if (wrapperWasCalled) {
52 throw std::runtime_error(
53 "callback 2 arg cannot be called more than once");
54 }
55
56 auto strongWrapper = weakWrapper.lock();
57 if (!strongWrapper) {
58 return;
59 }
60
61 strongWrapper->jsInvoker().invokeAsync(
62 [
63 weakWrapper,
64 callbackWrapperOwner = std::move(callbackWrapperOwner),
65 responses = std::move(responses),
66 isRejectCallback
67 ]() mutable {
68 auto strongWrapper2 = weakWrapper.lock();
69 if (!strongWrapper2) {
70 return;
71 }
72
73 jsi::Value arg = jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
74 if (!isRejectCallback) {
75 strongWrapper2->callback().call(
76 strongWrapper2->runtime(),
77 (const jsi::Value *) &arg,
78 (size_t) 1
79 );
80 } else {
81 auto &rt = strongWrapper2->runtime();
82 auto jsErrorObject = arg.getObject(rt);
83 auto errorCode = jsErrorObject.getProperty(rt, "code").asString(rt);
84 auto message = jsErrorObject.getProperty(rt, "message").asString(rt);
85
86 auto codedError = makeCodedError(
87 rt,
88 std::move(errorCode),
89 std::move(message)
90 );
91
92 strongWrapper2->callback().call(
93 strongWrapper2->runtime(),
94 (const jsi::Value *) &codedError,
95 (size_t) 1
96 );
97 }
98
99 callbackWrapperOwner.reset();
100 });
101
102 wrapperWasCalled = true;
103 };
104
105 return JavaCallback::newInstance(moduleRegistry, std::move(fn));
106 }
107
convertJSIArgsToJNI(JSIInteropModuleRegistry * moduleRegistry,JNIEnv * env,jsi::Runtime & rt,const jsi::Value & thisValue,const jsi::Value * args,size_t count)108 jobjectArray MethodMetadata::convertJSIArgsToJNI(
109 JSIInteropModuleRegistry *moduleRegistry,
110 JNIEnv *env,
111 jsi::Runtime &rt,
112 const jsi::Value &thisValue,
113 const jsi::Value *args,
114 size_t count
115 ) {
116 // This function takes the owner, so the args number is higher because we have access to the thisValue.
117 if (takesOwner) {
118 count++;
119 }
120
121 // The `count < this->args` case is handled by the Kotlin part
122 if (count > this->args) {
123 throwNewJavaException(
124 InvalidArgsNumberException::create(
125 count,
126 this->args
127 ).get()
128 );
129 }
130
131 auto argumentArray = env->NewObjectArray(
132 count,
133 JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
134 nullptr
135 );
136
137
138 const auto getCurrentArg = [&thisValue, args, takesOwner = takesOwner](
139 size_t index
140 ) -> const jsi::Value & {
141 if (!takesOwner) {
142 return args[index];
143 } else {
144 if (index != 0) {
145 return args[index - 1];
146 }
147 return thisValue;
148 }
149 };
150
151 for (size_t argIndex = 0; argIndex < count; argIndex++) {
152 const jsi::Value &arg = getCurrentArg(argIndex);
153 auto &type = argTypes[argIndex];
154 if (arg.isNull() || arg.isUndefined()) {
155 // If value is null or undefined, we just passes a null
156 // Kotlin code will check if expected type is nullable.
157 continue;
158 }
159
160 if (type->converter->canConvert(rt, arg)) {
161 auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg);
162 env->SetObjectArrayElement(argumentArray, argIndex, converterValue);
163 env->DeleteLocalRef(converterValue);
164 } else {
165 auto stringRepresentation = arg.toString(rt).utf8(rt);
166 throwNewJavaException(
167 UnexpectedException::create(
168 "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
169 );
170 }
171 }
172
173 return argumentArray;
174 }
175
MethodMetadata(std::string name,bool takesOwner,int args,bool isAsync,jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,jni::global_ref<jobject> && jBodyReference)176 MethodMetadata::MethodMetadata(
177 std::string name,
178 bool takesOwner,
179 int args,
180 bool isAsync,
181 jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
182 jni::global_ref<jobject> &&jBodyReference
183 ) : name(std::move(name)),
184 takesOwner(takesOwner),
185 args(args),
186 isAsync(isAsync),
187 jBodyReference(std::move(jBodyReference)) {
188 argTypes.reserve(args);
189 for (size_t i = 0; i < args; i++) {
190 auto expectedType = expectedArgTypes->getElement(i);
191 argTypes.push_back(
192 std::make_unique<AnyType>(std::move(expectedType))
193 );
194 }
195 }
196
MethodMetadata(std::string name,bool takesOwner,int args,bool isAsync,std::vector<std::unique_ptr<AnyType>> && expectedArgTypes,jni::global_ref<jobject> && jBodyReference)197 MethodMetadata::MethodMetadata(
198 std::string name,
199 bool takesOwner,
200 int args,
201 bool isAsync,
202 std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes,
203 jni::global_ref<jobject> &&jBodyReference
204 ) : name(std::move(name)),
205 takesOwner(takesOwner),
206 args(args),
207 isAsync(isAsync),
208 argTypes(std::move(expectedArgTypes)),
209 jBodyReference(std::move(jBodyReference)) {
210 }
211
toJSFunction(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry)212 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
213 jsi::Runtime &runtime,
214 JSIInteropModuleRegistry *moduleRegistry
215 ) {
216 if (body == nullptr) {
217 if (isAsync) {
218 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
219 } else {
220 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
221 }
222 }
223
224 return body;
225 }
226
toSyncFunction(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry)227 jsi::Function MethodMetadata::toSyncFunction(
228 jsi::Runtime &runtime,
229 JSIInteropModuleRegistry *moduleRegistry
230 ) {
231 return jsi::Function::createFromHostFunction(
232 runtime,
233 moduleRegistry->jsRegistry->getPropNameID(runtime, name),
234 args,
235 [this, moduleRegistry](
236 jsi::Runtime &rt,
237 const jsi::Value &thisValue,
238 const jsi::Value *args,
239 size_t count
240 ) -> jsi::Value {
241 try {
242 return this->callSync(
243 rt,
244 moduleRegistry,
245 thisValue,
246 args,
247 count
248 );
249 } catch (jni::JniException &jniException) {
250 rethrowAsCodedError(rt, jniException);
251 }
252 });
253 }
254
callJNISync(JNIEnv * env,jsi::Runtime & rt,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & thisValue,const jsi::Value * args,size_t count)255 jni::local_ref<jobject> MethodMetadata::callJNISync(
256 JNIEnv *env,
257 jsi::Runtime &rt,
258 JSIInteropModuleRegistry *moduleRegistry,
259 const jsi::Value &thisValue,
260 const jsi::Value *args,
261 size_t count
262 ) {
263 if (this->jBodyReference == nullptr) {
264 return nullptr;
265 }
266
267 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
268
269 // Cast in this place is safe, cause we know that this function is promise-less.
270 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
271 auto result = syncFunction->invoke(
272 convertedArgs
273 );
274
275 env->DeleteLocalRef(convertedArgs);
276 return result;
277 }
278
callSync(jsi::Runtime & rt,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & thisValue,const jsi::Value * args,size_t count)279 jsi::Value MethodMetadata::callSync(
280 jsi::Runtime &rt,
281 JSIInteropModuleRegistry *moduleRegistry,
282 const jsi::Value &thisValue,
283 const jsi::Value *args,
284 size_t count
285 ) {
286 JNIEnv *env = jni::Environment::current();
287 /**
288 * This will push a new JNI stack frame for the LocalReferences in this
289 * function call. When the stack frame for this lambda is popped,
290 * all LocalReferences are deleted.
291 */
292 jni::JniLocalScope scope(env, (int) count);
293
294 auto result = this->callJNISync(env, rt, moduleRegistry, thisValue, args, count);
295 return convert(moduleRegistry, env, rt, std::move(result));
296 }
297
toAsyncFunction(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry)298 jsi::Function MethodMetadata::toAsyncFunction(
299 jsi::Runtime &runtime,
300 JSIInteropModuleRegistry *moduleRegistry
301 ) {
302 return jsi::Function::createFromHostFunction(
303 runtime,
304 moduleRegistry->jsRegistry->getPropNameID(runtime, name),
305 args,
306 [this, moduleRegistry](
307 jsi::Runtime &rt,
308 const jsi::Value &thisValue,
309 const jsi::Value *args,
310 size_t count
311 ) -> jsi::Value {
312 JNIEnv *env = jni::Environment::current();
313
314 /**
315 * This will push a new JNI stack frame for the LocalReferences in this
316 * function call. When the stack frame for this lambda is popped,
317 * all LocalReferences are deleted.
318 */
319 jni::JniLocalScope scope(env, (int) count);
320
321 auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
322 JSReferencesCache::JSKeys::PROMISE
323 );
324
325 try {
326 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
327 auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs);
328 env->DeleteLocalRef(convertedArgs);
329
330 // Creates a JSI promise
331 jsi::Value promise = Promise.callAsConstructor(
332 rt,
333 createPromiseBody(rt, moduleRegistry, globalConvertedArgs)
334 );
335 return promise;
336 } catch (jni::JniException &jniException) {
337 jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
338 if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
339 unboxedThrowable = UnexpectedException::create(jniException.what());
340 }
341
342 auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
343 auto code = codedException->getCode();
344 auto message = codedException->getLocalizedMessage().value_or("");
345
346 jsi::Value promise = Promise.callAsConstructor(
347 rt,
348 jsi::Function::createFromHostFunction(
349 rt,
350 moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"),
351 2,
352 [code, message](
353 jsi::Runtime &rt,
354 const jsi::Value &thisVal,
355 const jsi::Value *promiseConstructorArgs,
356 size_t promiseConstructorArgCount
357 ) {
358 if (promiseConstructorArgCount != 2) {
359 throw std::invalid_argument("Promise fn arg count must be 2");
360 }
361
362 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
363 rejectJSIFn.call(
364 rt,
365 makeCodedError(
366 rt,
367 jsi::String::createFromUtf8(rt, code),
368 jsi::String::createFromUtf8(rt, message)
369 )
370 );
371 return jsi::Value::undefined();
372 }
373 )
374 );
375
376 return promise;
377 }
378 }
379 );
380 }
381
createPromiseBody(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry,jobjectArray globalArgs)382 jsi::Function MethodMetadata::createPromiseBody(
383 jsi::Runtime &runtime,
384 JSIInteropModuleRegistry *moduleRegistry,
385 jobjectArray globalArgs
386 ) {
387 return jsi::Function::createFromHostFunction(
388 runtime,
389 moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
390 2,
391 [this, globalArgs, moduleRegistry](
392 jsi::Runtime &rt,
393 const jsi::Value &thisVal,
394 const jsi::Value *promiseConstructorArgs,
395 size_t promiseConstructorArgCount
396 ) {
397 if (promiseConstructorArgCount != 2) {
398 throw std::invalid_argument("Promise fn arg count must be 2");
399 }
400
401 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
402 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
403
404 jobject resolve = createJavaCallbackFromJSIFunction(
405 std::move(resolveJSIFn),
406 rt,
407 moduleRegistry
408 ).release();
409
410 jobject reject = createJavaCallbackFromJSIFunction(
411 std::move(rejectJSIFn),
412 rt,
413 moduleRegistry,
414 true
415 ).release();
416
417 JNIEnv *env = jni::Environment::current();
418
419 auto &jPromise = JavaReferencesCache::instance()->getJClass(
420 "expo/modules/kotlin/jni/PromiseImpl");
421 jmethodID jPromiseConstructor = jPromise.getMethod(
422 "<init>",
423 "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V"
424 );
425
426 // Creates a promise object
427 jobject promise = env->NewObject(
428 jPromise.clazz,
429 jPromiseConstructor,
430 resolve,
431 reject
432 );
433
434 // Cast in this place is safe, cause we know that this function expects promise.
435 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
436 asyncFunction->invoke(
437 globalArgs,
438 promise
439 );
440
441 // We have to remove the local reference to the promise object.
442 // It doesn't mean that the promise will be deallocated, but rather that we move
443 // the ownership to the `JNIAsyncFunctionBody`.
444 env->DeleteLocalRef(promise);
445 env->DeleteGlobalRef(globalArgs);
446
447 return jsi::Value::undefined();
448 }
449 );
450 }
451 } // namespace expo
452