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