164f5c95fSŁukasz Kosmaty #include "MethodMetadata.h"
264f5c95fSŁukasz Kosmaty #include "JSIInteropModuleRegistry.h"
39ebf31e6SŁukasz Kosmaty #include "JavaScriptValue.h"
49ebf31e6SŁukasz Kosmaty #include "JavaScriptObject.h"
505c5e37dSŁukasz Kosmaty #include "JavaScriptTypedArray.h"
6b7d1787dSŁukasz Kosmaty #include "JavaReferencesCache.h"
78bd57a9aSŁukasz Kosmaty #include "Exceptions.h"
85e538c67SŁukasz Kosmaty #include "JavaCallback.h"
9879827bbSŁukasz Kosmaty #include "types/JNIToJSIConverter.h"
109ebf31e6SŁukasz Kosmaty 
119ebf31e6SŁukasz Kosmaty #include <utility>
12b627df43SŁukasz Kosmaty #include <functional>
139ebf31e6SŁukasz Kosmaty 
14b7d1787dSŁukasz Kosmaty #include "JSReferencesCache.h"
1564f5c95fSŁukasz Kosmaty 
1664f5c95fSŁukasz Kosmaty namespace jni = facebook::jni;
1764f5c95fSŁukasz Kosmaty namespace jsi = facebook::jsi;
1864f5c95fSŁukasz Kosmaty namespace react = facebook::react;
1964f5c95fSŁukasz Kosmaty 
2064f5c95fSŁukasz Kosmaty namespace expo {
2164f5c95fSŁukasz Kosmaty 
2264f5c95fSŁukasz Kosmaty // Modified version of the RN implementation
2364f5c95fSŁukasz Kosmaty // 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)245e538c67SŁukasz Kosmaty jni::local_ref<JavaCallback::JavaPart> createJavaCallbackFromJSIFunction(
2564f5c95fSŁukasz Kosmaty   jsi::Function &&function,
2664f5c95fSŁukasz Kosmaty   jsi::Runtime &rt,
27d8bd928cSŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry,
28d8bd928cSŁukasz Kosmaty   bool isRejectCallback = false
2964f5c95fSŁukasz Kosmaty ) {
30d8bd928cSŁukasz Kosmaty   std::shared_ptr<react::CallInvoker> jsInvoker = moduleRegistry->runtimeHolder->jsInvoker;
31695fef9bSKudo Chien   auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
3264f5c95fSŁukasz Kosmaty                                                         std::move(jsInvoker));
3364f5c95fSŁukasz Kosmaty 
3464f5c95fSŁukasz Kosmaty   // This needs to be a shared_ptr because:
3564f5c95fSŁukasz Kosmaty   // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
3664f5c95fSŁukasz Kosmaty   // not.
3764f5c95fSŁukasz Kosmaty   // 2. It cannot be weak_ptr since we need this object to live on.
3864f5c95fSŁukasz Kosmaty   // 3. It cannot be a value, because that would be deleted as soon as this
3964f5c95fSŁukasz Kosmaty   // function returns.
4064f5c95fSŁukasz Kosmaty   auto callbackWrapperOwner =
4164f5c95fSŁukasz Kosmaty     std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
4264f5c95fSŁukasz Kosmaty 
4364f5c95fSŁukasz Kosmaty   std::function<void(folly::dynamic)> fn =
445e538c67SŁukasz Kosmaty     [
455e538c67SŁukasz Kosmaty       weakWrapper,
465e538c67SŁukasz Kosmaty       callbackWrapperOwner = std::move(callbackWrapperOwner),
47d8bd928cSŁukasz Kosmaty       wrapperWasCalled = false,
48d8bd928cSŁukasz Kosmaty       isRejectCallback
495e538c67SŁukasz Kosmaty     ](
5064f5c95fSŁukasz Kosmaty       folly::dynamic responses) mutable {
5164f5c95fSŁukasz Kosmaty       if (wrapperWasCalled) {
5264f5c95fSŁukasz Kosmaty         throw std::runtime_error(
5364f5c95fSŁukasz Kosmaty           "callback 2 arg cannot be called more than once");
5464f5c95fSŁukasz Kosmaty       }
5564f5c95fSŁukasz Kosmaty 
5664f5c95fSŁukasz Kosmaty       auto strongWrapper = weakWrapper.lock();
5764f5c95fSŁukasz Kosmaty       if (!strongWrapper) {
5864f5c95fSŁukasz Kosmaty         return;
5964f5c95fSŁukasz Kosmaty       }
6064f5c95fSŁukasz Kosmaty 
6164f5c95fSŁukasz Kosmaty       strongWrapper->jsInvoker().invokeAsync(
625e538c67SŁukasz Kosmaty         [
635e538c67SŁukasz Kosmaty           weakWrapper,
645e538c67SŁukasz Kosmaty           callbackWrapperOwner = std::move(callbackWrapperOwner),
65d8bd928cSŁukasz Kosmaty           responses = std::move(responses),
66d8bd928cSŁukasz Kosmaty           isRejectCallback
675e538c67SŁukasz Kosmaty         ]() mutable {
6864f5c95fSŁukasz Kosmaty           auto strongWrapper2 = weakWrapper.lock();
6964f5c95fSŁukasz Kosmaty           if (!strongWrapper2) {
7064f5c95fSŁukasz Kosmaty             return;
7164f5c95fSŁukasz Kosmaty           }
7264f5c95fSŁukasz Kosmaty 
735e538c67SŁukasz Kosmaty           jsi::Value arg = jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
74d8bd928cSŁukasz Kosmaty           if (!isRejectCallback) {
7564f5c95fSŁukasz Kosmaty             strongWrapper2->callback().call(
7664f5c95fSŁukasz Kosmaty               strongWrapper2->runtime(),
7764f5c95fSŁukasz Kosmaty               (const jsi::Value *) &arg,
7864f5c95fSŁukasz Kosmaty               (size_t) 1
7964f5c95fSŁukasz Kosmaty             );
80d8bd928cSŁukasz Kosmaty           } else {
81d8bd928cSŁukasz Kosmaty             auto &rt = strongWrapper2->runtime();
82d8bd928cSŁukasz Kosmaty             auto jsErrorObject = arg.getObject(rt);
83d8bd928cSŁukasz Kosmaty             auto errorCode = jsErrorObject.getProperty(rt, "code").asString(rt);
84d8bd928cSŁukasz Kosmaty             auto message = jsErrorObject.getProperty(rt, "message").asString(rt);
85d8bd928cSŁukasz Kosmaty 
86d8bd928cSŁukasz Kosmaty             auto codedError = makeCodedError(
87d8bd928cSŁukasz Kosmaty               rt,
88d8bd928cSŁukasz Kosmaty               std::move(errorCode),
89d8bd928cSŁukasz Kosmaty               std::move(message)
90d8bd928cSŁukasz Kosmaty             );
91d8bd928cSŁukasz Kosmaty 
92d8bd928cSŁukasz Kosmaty             strongWrapper2->callback().call(
93d8bd928cSŁukasz Kosmaty               strongWrapper2->runtime(),
94d8bd928cSŁukasz Kosmaty               (const jsi::Value *) &codedError,
95d8bd928cSŁukasz Kosmaty               (size_t) 1
96d8bd928cSŁukasz Kosmaty             );
97d8bd928cSŁukasz Kosmaty           }
9864f5c95fSŁukasz Kosmaty 
9964f5c95fSŁukasz Kosmaty           callbackWrapperOwner.reset();
10064f5c95fSŁukasz Kosmaty         });
10164f5c95fSŁukasz Kosmaty 
10264f5c95fSŁukasz Kosmaty       wrapperWasCalled = true;
10364f5c95fSŁukasz Kosmaty     };
10464f5c95fSŁukasz Kosmaty 
10529e8b6f8SŁukasz Kosmaty   return JavaCallback::newInstance(moduleRegistry, std::move(fn));
10664f5c95fSŁukasz Kosmaty }
10764f5c95fSŁukasz Kosmaty 
convertJSIArgsToJNI(JSIInteropModuleRegistry * moduleRegistry,JNIEnv * env,jsi::Runtime & rt,const jsi::Value & thisValue,const jsi::Value * args,size_t count)1087ed3a4bfSŁukasz Kosmaty jobjectArray MethodMetadata::convertJSIArgsToJNI(
1099ebf31e6SŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry,
1109ebf31e6SŁukasz Kosmaty   JNIEnv *env,
1119ebf31e6SŁukasz Kosmaty   jsi::Runtime &rt,
112cf4fff55SŁukasz Kosmaty   const jsi::Value &thisValue,
1139ebf31e6SŁukasz Kosmaty   const jsi::Value *args,
1147ed3a4bfSŁukasz Kosmaty   size_t count
1159ebf31e6SŁukasz Kosmaty ) {
116cf4fff55SŁukasz Kosmaty   // This function takes the owner, so the args number is higher because we have access to the thisValue.
117cf4fff55SŁukasz Kosmaty   if (takesOwner) {
118cf4fff55SŁukasz Kosmaty     count++;
119cf4fff55SŁukasz Kosmaty   }
120cf4fff55SŁukasz Kosmaty 
121732f0c04SŁukasz Kosmaty   // The `count < this->args` case is handled by the Kotlin part
122732f0c04SŁukasz Kosmaty   if (count > this->args) {
123732f0c04SŁukasz Kosmaty     throwNewJavaException(
124732f0c04SŁukasz Kosmaty       InvalidArgsNumberException::create(
125732f0c04SŁukasz Kosmaty         count,
126732f0c04SŁukasz Kosmaty         this->args
127732f0c04SŁukasz Kosmaty       ).get()
128732f0c04SŁukasz Kosmaty     );
129732f0c04SŁukasz Kosmaty   }
130732f0c04SŁukasz Kosmaty 
1317ed3a4bfSŁukasz Kosmaty   auto argumentArray = env->NewObjectArray(
1327ed3a4bfSŁukasz Kosmaty     count,
1337ed3a4bfSŁukasz Kosmaty     JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
1347ed3a4bfSŁukasz Kosmaty     nullptr
1357ed3a4bfSŁukasz Kosmaty   );
1369ebf31e6SŁukasz Kosmaty 
13722d99763SŁukasz Kosmaty 
138cf4fff55SŁukasz Kosmaty   const auto getCurrentArg = [&thisValue, args, takesOwner = takesOwner](
139cf4fff55SŁukasz Kosmaty     size_t index
140cf4fff55SŁukasz Kosmaty   ) -> const jsi::Value & {
141cf4fff55SŁukasz Kosmaty     if (!takesOwner) {
142cf4fff55SŁukasz Kosmaty       return args[index];
143cf4fff55SŁukasz Kosmaty     } else {
144cf4fff55SŁukasz Kosmaty       if (index != 0) {
145cf4fff55SŁukasz Kosmaty         return args[index - 1];
146cf4fff55SŁukasz Kosmaty       }
147cf4fff55SŁukasz Kosmaty       return thisValue;
148cf4fff55SŁukasz Kosmaty     }
149cf4fff55SŁukasz Kosmaty   };
150cf4fff55SŁukasz Kosmaty 
151cf4fff55SŁukasz Kosmaty   for (size_t argIndex = 0; argIndex < count; argIndex++) {
152cf4fff55SŁukasz Kosmaty     const jsi::Value &arg = getCurrentArg(argIndex);
15365a981ddSŁukasz Kosmaty     auto &type = argTypes[argIndex];
15465a981ddSŁukasz Kosmaty     if (arg.isNull() || arg.isUndefined()) {
15565a981ddSŁukasz Kosmaty       // If value is null or undefined, we just passes a null
15665a981ddSŁukasz Kosmaty       // Kotlin code will check if expected type is nullable.
157*b1cbccf5SŁukasz Kosmaty       continue;
158*b1cbccf5SŁukasz Kosmaty     }
159*b1cbccf5SŁukasz Kosmaty 
16065a981ddSŁukasz Kosmaty     if (type->converter->canConvert(rt, arg)) {
1617ed3a4bfSŁukasz Kosmaty       auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg);
1627ed3a4bfSŁukasz Kosmaty       env->SetObjectArrayElement(argumentArray, argIndex, converterValue);
1637ed3a4bfSŁukasz Kosmaty       env->DeleteLocalRef(converterValue);
1649ebf31e6SŁukasz Kosmaty     } else {
16565a981ddSŁukasz Kosmaty       auto stringRepresentation = arg.toString(rt).utf8(rt);
166e0f520f5SKudo Chien       throwNewJavaException(
167ce6f2823SŁukasz Kosmaty         UnexpectedException::create(
168ce6f2823SŁukasz Kosmaty           "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
169ce6f2823SŁukasz Kosmaty       );
1709ebf31e6SŁukasz Kosmaty     }
1719ebf31e6SŁukasz Kosmaty   }
1729ebf31e6SŁukasz Kosmaty 
1737ed3a4bfSŁukasz Kosmaty   return argumentArray;
1749ebf31e6SŁukasz Kosmaty }
1759ebf31e6SŁukasz Kosmaty 
MethodMetadata(std::string name,bool takesOwner,int args,bool isAsync,jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,jni::global_ref<jobject> && jBodyReference)17664f5c95fSŁukasz Kosmaty MethodMetadata::MethodMetadata(
17764f5c95fSŁukasz Kosmaty   std::string name,
178cf4fff55SŁukasz Kosmaty   bool takesOwner,
17964f5c95fSŁukasz Kosmaty   int args,
18064f5c95fSŁukasz Kosmaty   bool isAsync,
18165a981ddSŁukasz Kosmaty   jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
18264f5c95fSŁukasz Kosmaty   jni::global_ref<jobject> &&jBodyReference
1839ebf31e6SŁukasz Kosmaty ) : name(std::move(name)),
184cf4fff55SŁukasz Kosmaty     takesOwner(takesOwner),
18564f5c95fSŁukasz Kosmaty     args(args),
18664f5c95fSŁukasz Kosmaty     isAsync(isAsync),
187695fef9bSKudo Chien     jBodyReference(std::move(jBodyReference)) {
18865a981ddSŁukasz Kosmaty   argTypes.reserve(args);
18965a981ddSŁukasz Kosmaty   for (size_t i = 0; i < args; i++) {
19065a981ddSŁukasz Kosmaty     auto expectedType = expectedArgTypes->getElement(i);
19165a981ddSŁukasz Kosmaty     argTypes.push_back(
19265a981ddSŁukasz Kosmaty       std::make_unique<AnyType>(std::move(expectedType))
19365a981ddSŁukasz Kosmaty     );
19465a981ddSŁukasz Kosmaty   }
19565a981ddSŁukasz Kosmaty }
19665a981ddSŁukasz Kosmaty 
MethodMetadata(std::string name,bool takesOwner,int args,bool isAsync,std::vector<std::unique_ptr<AnyType>> && expectedArgTypes,jni::global_ref<jobject> && jBodyReference)19765a981ddSŁukasz Kosmaty MethodMetadata::MethodMetadata(
19865a981ddSŁukasz Kosmaty   std::string name,
199cf4fff55SŁukasz Kosmaty   bool takesOwner,
20065a981ddSŁukasz Kosmaty   int args,
20165a981ddSŁukasz Kosmaty   bool isAsync,
20265a981ddSŁukasz Kosmaty   std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes,
20365a981ddSŁukasz Kosmaty   jni::global_ref<jobject> &&jBodyReference
20465a981ddSŁukasz Kosmaty ) : name(std::move(name)),
205cf4fff55SŁukasz Kosmaty     takesOwner(takesOwner),
20665a981ddSŁukasz Kosmaty     args(args),
20765a981ddSŁukasz Kosmaty     isAsync(isAsync),
20865a981ddSŁukasz Kosmaty     argTypes(std::move(expectedArgTypes)),
209695fef9bSKudo Chien     jBodyReference(std::move(jBodyReference)) {
210aaf1c10dSKudo Chien }
21164f5c95fSŁukasz Kosmaty 
toJSFunction(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry)21264f5c95fSŁukasz Kosmaty std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
21364f5c95fSŁukasz Kosmaty   jsi::Runtime &runtime,
21464f5c95fSŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry
21564f5c95fSŁukasz Kosmaty ) {
21664f5c95fSŁukasz Kosmaty   if (body == nullptr) {
21764f5c95fSŁukasz Kosmaty     if (isAsync) {
21864f5c95fSŁukasz Kosmaty       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
21964f5c95fSŁukasz Kosmaty     } else {
2209ebf31e6SŁukasz Kosmaty       body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
22164f5c95fSŁukasz Kosmaty     }
22264f5c95fSŁukasz Kosmaty   }
22364f5c95fSŁukasz Kosmaty 
22464f5c95fSŁukasz Kosmaty   return body;
22564f5c95fSŁukasz Kosmaty }
22664f5c95fSŁukasz Kosmaty 
toSyncFunction(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry)2279ebf31e6SŁukasz Kosmaty jsi::Function MethodMetadata::toSyncFunction(
2289ebf31e6SŁukasz Kosmaty   jsi::Runtime &runtime,
2299ebf31e6SŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry
2309ebf31e6SŁukasz Kosmaty ) {
23164f5c95fSŁukasz Kosmaty   return jsi::Function::createFromHostFunction(
23264f5c95fSŁukasz Kosmaty     runtime,
233b7d1787dSŁukasz Kosmaty     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
23464f5c95fSŁukasz Kosmaty     args,
2359ebf31e6SŁukasz Kosmaty     [this, moduleRegistry](
23664f5c95fSŁukasz Kosmaty       jsi::Runtime &rt,
23764f5c95fSŁukasz Kosmaty       const jsi::Value &thisValue,
23864f5c95fSŁukasz Kosmaty       const jsi::Value *args,
23964f5c95fSŁukasz Kosmaty       size_t count
24064f5c95fSŁukasz Kosmaty     ) -> jsi::Value {
2418bd57a9aSŁukasz Kosmaty       try {
24247a022e6SŁukasz Kosmaty         return this->callSync(
24347a022e6SŁukasz Kosmaty           rt,
24447a022e6SŁukasz Kosmaty           moduleRegistry,
245cf4fff55SŁukasz Kosmaty           thisValue,
24647a022e6SŁukasz Kosmaty           args,
24747a022e6SŁukasz Kosmaty           count
24847a022e6SŁukasz Kosmaty         );
2498bd57a9aSŁukasz Kosmaty       } catch (jni::JniException &jniException) {
250d8bd928cSŁukasz Kosmaty         rethrowAsCodedError(rt, jniException);
2518bd57a9aSŁukasz Kosmaty       }
25247a022e6SŁukasz Kosmaty     });
25347a022e6SŁukasz Kosmaty }
25447a022e6SŁukasz Kosmaty 
callJNISync(JNIEnv * env,jsi::Runtime & rt,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & thisValue,const jsi::Value * args,size_t count)255ecb7f347SŁukasz Kosmaty jni::local_ref<jobject> MethodMetadata::callJNISync(
256ecb7f347SŁukasz Kosmaty   JNIEnv *env,
25747a022e6SŁukasz Kosmaty   jsi::Runtime &rt,
25847a022e6SŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry,
259cf4fff55SŁukasz Kosmaty   const jsi::Value &thisValue,
26047a022e6SŁukasz Kosmaty   const jsi::Value *args,
26147a022e6SŁukasz Kosmaty   size_t count
26247a022e6SŁukasz Kosmaty ) {
26347a022e6SŁukasz Kosmaty   if (this->jBodyReference == nullptr) {
264ecb7f347SŁukasz Kosmaty     return nullptr;
26547a022e6SŁukasz Kosmaty   }
26647a022e6SŁukasz Kosmaty 
267cf4fff55SŁukasz Kosmaty   auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
26864f5c95fSŁukasz Kosmaty 
26964f5c95fSŁukasz Kosmaty   // Cast in this place is safe, cause we know that this function is promise-less.
27064f5c95fSŁukasz Kosmaty   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
27164f5c95fSŁukasz Kosmaty   auto result = syncFunction->invoke(
2727ed3a4bfSŁukasz Kosmaty     convertedArgs
27364f5c95fSŁukasz Kosmaty   );
27464f5c95fSŁukasz Kosmaty 
2757ed3a4bfSŁukasz Kosmaty   env->DeleteLocalRef(convertedArgs);
276ecb7f347SŁukasz Kosmaty   return result;
277ecb7f347SŁukasz Kosmaty }
278ecb7f347SŁukasz Kosmaty 
callSync(jsi::Runtime & rt,JSIInteropModuleRegistry * moduleRegistry,const jsi::Value & thisValue,const jsi::Value * args,size_t count)279ecb7f347SŁukasz Kosmaty jsi::Value MethodMetadata::callSync(
280ecb7f347SŁukasz Kosmaty   jsi::Runtime &rt,
281ecb7f347SŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry,
282ecb7f347SŁukasz Kosmaty   const jsi::Value &thisValue,
283ecb7f347SŁukasz Kosmaty   const jsi::Value *args,
284ecb7f347SŁukasz Kosmaty   size_t count
285ecb7f347SŁukasz Kosmaty ) {
286ecb7f347SŁukasz Kosmaty   JNIEnv *env = jni::Environment::current();
287ecb7f347SŁukasz Kosmaty   /**
288ecb7f347SŁukasz Kosmaty   * This will push a new JNI stack frame for the LocalReferences in this
289ecb7f347SŁukasz Kosmaty   * function call. When the stack frame for this lambda is popped,
290ecb7f347SŁukasz Kosmaty   * all LocalReferences are deleted.
291ecb7f347SŁukasz Kosmaty   */
292ecb7f347SŁukasz Kosmaty   jni::JniLocalScope scope(env, (int) count);
293ecb7f347SŁukasz Kosmaty 
294ecb7f347SŁukasz Kosmaty   auto result = this->callJNISync(env, rt, moduleRegistry, thisValue, args, count);
295879827bbSŁukasz Kosmaty   return convert(moduleRegistry, env, rt, std::move(result));
29664f5c95fSŁukasz Kosmaty }
29764f5c95fSŁukasz Kosmaty 
toAsyncFunction(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry)29864f5c95fSŁukasz Kosmaty jsi::Function MethodMetadata::toAsyncFunction(
29964f5c95fSŁukasz Kosmaty   jsi::Runtime &runtime,
30064f5c95fSŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry
30164f5c95fSŁukasz Kosmaty ) {
30264f5c95fSŁukasz Kosmaty   return jsi::Function::createFromHostFunction(
30364f5c95fSŁukasz Kosmaty     runtime,
304b7d1787dSŁukasz Kosmaty     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
30564f5c95fSŁukasz Kosmaty     args,
30664f5c95fSŁukasz Kosmaty     [this, moduleRegistry](
30764f5c95fSŁukasz Kosmaty       jsi::Runtime &rt,
30864f5c95fSŁukasz Kosmaty       const jsi::Value &thisValue,
30964f5c95fSŁukasz Kosmaty       const jsi::Value *args,
31064f5c95fSŁukasz Kosmaty       size_t count
31164f5c95fSŁukasz Kosmaty     ) -> jsi::Value {
3129ebf31e6SŁukasz Kosmaty       JNIEnv *env = jni::Environment::current();
3139ebf31e6SŁukasz Kosmaty 
3149ebf31e6SŁukasz Kosmaty       /**
3159ebf31e6SŁukasz Kosmaty        * This will push a new JNI stack frame for the LocalReferences in this
3169ebf31e6SŁukasz Kosmaty        * function call. When the stack frame for this lambda is popped,
3179ebf31e6SŁukasz Kosmaty        * all LocalReferences are deleted.
3189ebf31e6SŁukasz Kosmaty        */
3199ebf31e6SŁukasz Kosmaty       jni::JniLocalScope scope(env, (int) count);
32064f5c95fSŁukasz Kosmaty 
321d8bd928cSŁukasz Kosmaty       auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
322d8bd928cSŁukasz Kosmaty         JSReferencesCache::JSKeys::PROMISE
323d8bd928cSŁukasz Kosmaty       );
324d8bd928cSŁukasz Kosmaty 
325b7d1787dSŁukasz Kosmaty       try {
326cf4fff55SŁukasz Kosmaty         auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
3277ed3a4bfSŁukasz Kosmaty         auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs);
3287ed3a4bfSŁukasz Kosmaty         env->DeleteLocalRef(convertedArgs);
3297ed3a4bfSŁukasz Kosmaty 
33064f5c95fSŁukasz Kosmaty         // Creates a JSI promise
33164f5c95fSŁukasz Kosmaty         jsi::Value promise = Promise.callAsConstructor(
33264f5c95fSŁukasz Kosmaty           rt,
3337ed3a4bfSŁukasz Kosmaty           createPromiseBody(rt, moduleRegistry, globalConvertedArgs)
33464f5c95fSŁukasz Kosmaty         );
33564f5c95fSŁukasz Kosmaty         return promise;
336b7d1787dSŁukasz Kosmaty       } catch (jni::JniException &jniException) {
337d8bd928cSŁukasz Kosmaty         jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
338d8bd928cSŁukasz Kosmaty         if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
339d8bd928cSŁukasz Kosmaty           unboxedThrowable = UnexpectedException::create(jniException.what());
340d8bd928cSŁukasz Kosmaty         }
341d8bd928cSŁukasz Kosmaty 
342d8bd928cSŁukasz Kosmaty         auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
343d8bd928cSŁukasz Kosmaty         auto code = codedException->getCode();
344d8bd928cSŁukasz Kosmaty         auto message = codedException->getLocalizedMessage().value_or("");
345d8bd928cSŁukasz Kosmaty 
346d8bd928cSŁukasz Kosmaty         jsi::Value promise = Promise.callAsConstructor(
347d8bd928cSŁukasz Kosmaty           rt,
348d8bd928cSŁukasz Kosmaty           jsi::Function::createFromHostFunction(
349d8bd928cSŁukasz Kosmaty             rt,
350d8bd928cSŁukasz Kosmaty             moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"),
351d8bd928cSŁukasz Kosmaty             2,
352d8bd928cSŁukasz Kosmaty             [code, message](
353d8bd928cSŁukasz Kosmaty               jsi::Runtime &rt,
354d8bd928cSŁukasz Kosmaty               const jsi::Value &thisVal,
355d8bd928cSŁukasz Kosmaty               const jsi::Value *promiseConstructorArgs,
356d8bd928cSŁukasz Kosmaty               size_t promiseConstructorArgCount
357d8bd928cSŁukasz Kosmaty             ) {
358d8bd928cSŁukasz Kosmaty               if (promiseConstructorArgCount != 2) {
359d8bd928cSŁukasz Kosmaty                 throw std::invalid_argument("Promise fn arg count must be 2");
360d8bd928cSŁukasz Kosmaty               }
361d8bd928cSŁukasz Kosmaty 
362d8bd928cSŁukasz Kosmaty               jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
363d8bd928cSŁukasz Kosmaty               rejectJSIFn.call(
364d8bd928cSŁukasz Kosmaty                 rt,
365d8bd928cSŁukasz Kosmaty                 makeCodedError(
366d8bd928cSŁukasz Kosmaty                   rt,
367d8bd928cSŁukasz Kosmaty                   jsi::String::createFromUtf8(rt, code),
368d8bd928cSŁukasz Kosmaty                   jsi::String::createFromUtf8(rt, message)
369d8bd928cSŁukasz Kosmaty                 )
370d8bd928cSŁukasz Kosmaty               );
371d8bd928cSŁukasz Kosmaty               return jsi::Value::undefined();
372d8bd928cSŁukasz Kosmaty             }
373d8bd928cSŁukasz Kosmaty           )
374d8bd928cSŁukasz Kosmaty         );
375d8bd928cSŁukasz Kosmaty 
376d8bd928cSŁukasz Kosmaty         return promise;
377b7d1787dSŁukasz Kosmaty       }
37864f5c95fSŁukasz Kosmaty     }
37964f5c95fSŁukasz Kosmaty   );
38064f5c95fSŁukasz Kosmaty }
38164f5c95fSŁukasz Kosmaty 
createPromiseBody(jsi::Runtime & runtime,JSIInteropModuleRegistry * moduleRegistry,jobjectArray globalArgs)38264f5c95fSŁukasz Kosmaty jsi::Function MethodMetadata::createPromiseBody(
38364f5c95fSŁukasz Kosmaty   jsi::Runtime &runtime,
38464f5c95fSŁukasz Kosmaty   JSIInteropModuleRegistry *moduleRegistry,
3857ed3a4bfSŁukasz Kosmaty   jobjectArray globalArgs
38664f5c95fSŁukasz Kosmaty ) {
38764f5c95fSŁukasz Kosmaty   return jsi::Function::createFromHostFunction(
38864f5c95fSŁukasz Kosmaty     runtime,
389b7d1787dSŁukasz Kosmaty     moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
39064f5c95fSŁukasz Kosmaty     2,
3917ed3a4bfSŁukasz Kosmaty     [this, globalArgs, moduleRegistry](
39264f5c95fSŁukasz Kosmaty       jsi::Runtime &rt,
39364f5c95fSŁukasz Kosmaty       const jsi::Value &thisVal,
39464f5c95fSŁukasz Kosmaty       const jsi::Value *promiseConstructorArgs,
39564f5c95fSŁukasz Kosmaty       size_t promiseConstructorArgCount
39664f5c95fSŁukasz Kosmaty     ) {
39764f5c95fSŁukasz Kosmaty       if (promiseConstructorArgCount != 2) {
39864f5c95fSŁukasz Kosmaty         throw std::invalid_argument("Promise fn arg count must be 2");
39964f5c95fSŁukasz Kosmaty       }
40064f5c95fSŁukasz Kosmaty 
40164f5c95fSŁukasz Kosmaty       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
40264f5c95fSŁukasz Kosmaty       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
40364f5c95fSŁukasz Kosmaty 
40464f5c95fSŁukasz Kosmaty       jobject resolve = createJavaCallbackFromJSIFunction(
40564f5c95fSŁukasz Kosmaty         std::move(resolveJSIFn),
40664f5c95fSŁukasz Kosmaty         rt,
407d8bd928cSŁukasz Kosmaty         moduleRegistry
40864f5c95fSŁukasz Kosmaty       ).release();
40964f5c95fSŁukasz Kosmaty 
41064f5c95fSŁukasz Kosmaty       jobject reject = createJavaCallbackFromJSIFunction(
41164f5c95fSŁukasz Kosmaty         std::move(rejectJSIFn),
41264f5c95fSŁukasz Kosmaty         rt,
413d8bd928cSŁukasz Kosmaty         moduleRegistry,
414d8bd928cSŁukasz Kosmaty         true
41564f5c95fSŁukasz Kosmaty       ).release();
41664f5c95fSŁukasz Kosmaty 
41764f5c95fSŁukasz Kosmaty       JNIEnv *env = jni::Environment::current();
41864f5c95fSŁukasz Kosmaty 
419b7d1787dSŁukasz Kosmaty       auto &jPromise = JavaReferencesCache::instance()->getJClass(
4205e538c67SŁukasz Kosmaty         "expo/modules/kotlin/jni/PromiseImpl");
4219ebf31e6SŁukasz Kosmaty       jmethodID jPromiseConstructor = jPromise.getMethod(
42264f5c95fSŁukasz Kosmaty         "<init>",
4235e538c67SŁukasz Kosmaty         "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V"
4249ebf31e6SŁukasz Kosmaty       );
42564f5c95fSŁukasz Kosmaty 
42664f5c95fSŁukasz Kosmaty       // Creates a promise object
42764f5c95fSŁukasz Kosmaty       jobject promise = env->NewObject(
4289ebf31e6SŁukasz Kosmaty         jPromise.clazz,
4299ebf31e6SŁukasz Kosmaty         jPromiseConstructor,
43064f5c95fSŁukasz Kosmaty         resolve,
43164f5c95fSŁukasz Kosmaty         reject
43264f5c95fSŁukasz Kosmaty       );
43364f5c95fSŁukasz Kosmaty 
43464f5c95fSŁukasz Kosmaty       // Cast in this place is safe, cause we know that this function expects promise.
43564f5c95fSŁukasz Kosmaty       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
43664f5c95fSŁukasz Kosmaty       asyncFunction->invoke(
4377ed3a4bfSŁukasz Kosmaty         globalArgs,
43864f5c95fSŁukasz Kosmaty         promise
43964f5c95fSŁukasz Kosmaty       );
44064f5c95fSŁukasz Kosmaty 
44164f5c95fSŁukasz Kosmaty       // We have to remove the local reference to the promise object.
44264f5c95fSŁukasz Kosmaty       // It doesn't mean that the promise will be deallocated, but rather that we move
44364f5c95fSŁukasz Kosmaty       // the ownership to the `JNIAsyncFunctionBody`.
44464f5c95fSŁukasz Kosmaty       env->DeleteLocalRef(promise);
4457ed3a4bfSŁukasz Kosmaty       env->DeleteGlobalRef(globalArgs);
44622d99763SŁukasz Kosmaty 
44764f5c95fSŁukasz Kosmaty       return jsi::Value::undefined();
44864f5c95fSŁukasz Kosmaty     }
44964f5c95fSŁukasz Kosmaty   );
45064f5c95fSŁukasz Kosmaty }
45164f5c95fSŁukasz Kosmaty } // namespace expo
452