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 10 #include <utility> 11 12 #include <react/jni/ReadableNativeMap.h> 13 #include <react/jni/ReadableNativeArray.h> 14 #include <react/jni/WritableNativeArray.h> 15 #include <react/jni/WritableNativeMap.h> 16 #include "JSReferencesCache.h" 17 18 namespace jni = facebook::jni; 19 namespace jsi = facebook::jsi; 20 namespace react = facebook::react; 21 22 namespace expo { 23 24 // Modified version of the RN implementation 25 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57 26 jni::local_ref<JavaCallback::JavaPart> createJavaCallbackFromJSIFunction( 27 jsi::Function &&function, 28 jsi::Runtime &rt, 29 std::shared_ptr<react::CallInvoker> jsInvoker 30 ) { 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 ]( 49 folly::dynamic responses) mutable { 50 if (wrapperWasCalled) { 51 throw std::runtime_error( 52 "callback 2 arg cannot be called more than once"); 53 } 54 55 auto strongWrapper = weakWrapper.lock(); 56 if (!strongWrapper) { 57 return; 58 } 59 60 strongWrapper->jsInvoker().invokeAsync( 61 [ 62 weakWrapper, 63 callbackWrapperOwner = std::move(callbackWrapperOwner), 64 responses = std::move(responses) 65 ]() mutable { 66 auto strongWrapper2 = weakWrapper.lock(); 67 if (!strongWrapper2) { 68 return; 69 } 70 71 jsi::Value arg = jsi::valueFromDynamic(strongWrapper2->runtime(), responses); 72 73 strongWrapper2->callback().call( 74 strongWrapper2->runtime(), 75 (const jsi::Value *) &arg, 76 (size_t) 1 77 ); 78 79 callbackWrapperOwner.reset(); 80 }); 81 82 wrapperWasCalled = true; 83 }; 84 85 return JavaCallback::newObjectCxxArgs(std::move(fn)); 86 } 87 88 jobjectArray MethodMetadata::convertJSIArgsToJNI( 89 JSIInteropModuleRegistry *moduleRegistry, 90 JNIEnv *env, 91 jsi::Runtime &rt, 92 const jsi::Value *args, 93 size_t count 94 ) { 95 auto argumentArray = env->NewObjectArray( 96 count, 97 JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz, 98 nullptr 99 ); 100 101 std::vector<jobject> result(count); 102 103 for (unsigned int argIndex = 0; argIndex < count; argIndex++) { 104 const jsi::Value &arg = args[argIndex]; 105 auto &type = argTypes[argIndex]; 106 if (arg.isNull() || arg.isUndefined()) { 107 // If value is null or undefined, we just passes a null 108 // Kotlin code will check if expected type is nullable. 109 result[argIndex] = nullptr; 110 } else { 111 if (type->converter->canConvert(rt, arg)) { 112 auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg); 113 env->SetObjectArrayElement(argumentArray, argIndex, converterValue); 114 env->DeleteLocalRef(converterValue); 115 } else { 116 auto stringRepresentation = arg.toString(rt).utf8(rt); 117 jni::throwNewJavaException( 118 UnexpectedException::create( 119 "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get() 120 ); 121 } 122 } 123 } 124 125 return argumentArray; 126 } 127 128 MethodMetadata::MethodMetadata( 129 std::string name, 130 int args, 131 bool isAsync, 132 jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 133 jni::global_ref<jobject> &&jBodyReference 134 ) : name(std::move(name)), 135 args(args), 136 isAsync(isAsync), 137 jBodyReference(std::move(jBodyReference)) { 138 argTypes.reserve(args); 139 for (size_t i = 0; i < args; i++) { 140 auto expectedType = expectedArgTypes->getElement(i); 141 argTypes.push_back( 142 std::make_unique<AnyType>(std::move(expectedType)) 143 ); 144 } 145 } 146 147 MethodMetadata::MethodMetadata( 148 std::string name, 149 int args, 150 bool isAsync, 151 std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes, 152 jni::global_ref<jobject> &&jBodyReference 153 ) : name(std::move(name)), 154 args(args), 155 isAsync(isAsync), 156 argTypes(std::move(expectedArgTypes)), 157 jBodyReference(std::move(jBodyReference) 158 ) {} 159 160 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 161 jsi::Runtime &runtime, 162 JSIInteropModuleRegistry *moduleRegistry 163 ) { 164 if (body == nullptr) { 165 if (isAsync) { 166 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 167 } else { 168 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 169 } 170 } 171 172 return body; 173 } 174 175 jsi::Function MethodMetadata::toSyncFunction( 176 jsi::Runtime &runtime, 177 JSIInteropModuleRegistry *moduleRegistry 178 ) { 179 return jsi::Function::createFromHostFunction( 180 runtime, 181 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 182 args, 183 [this, moduleRegistry]( 184 jsi::Runtime &rt, 185 const jsi::Value &thisValue, 186 const jsi::Value *args, 187 size_t count 188 ) -> jsi::Value { 189 try { 190 return this->callSync( 191 rt, 192 moduleRegistry, 193 args, 194 count 195 ); 196 } catch (jni::JniException &jniException) { 197 rethrowAsCodedError(rt, moduleRegistry, jniException); 198 } 199 }); 200 } 201 202 jsi::Value MethodMetadata::callSync( 203 jsi::Runtime &rt, 204 JSIInteropModuleRegistry *moduleRegistry, 205 const jsi::Value *args, 206 size_t count 207 ) { 208 if (this->jBodyReference == nullptr) { 209 return jsi::Value::undefined(); 210 } 211 212 JNIEnv *env = jni::Environment::current(); 213 214 /** 215 * This will push a new JNI stack frame for the LocalReferences in this 216 * function call. When the stack frame for this lambda is popped, 217 * all LocalReferences are deleted. 218 */ 219 jni::JniLocalScope scope(env, (int) count); 220 221 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count); 222 223 // Cast in this place is safe, cause we know that this function is promise-less. 224 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 225 auto result = syncFunction->invoke( 226 convertedArgs 227 ); 228 229 env->DeleteLocalRef(convertedArgs); 230 if (result == nullptr) { 231 return jsi::Value::undefined(); 232 } 233 auto unpackedResult = result.get(); 234 auto cache = JavaReferencesCache::instance(); 235 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Double").clazz)) { 236 return {jni::static_ref_cast<jni::JDouble>(result)->value()}; 237 } 238 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Integer").clazz)) { 239 return {jni::static_ref_cast<jni::JInteger>(result)->value()}; 240 } 241 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/String").clazz)) { 242 return jsi::String::createFromUtf8( 243 rt, 244 jni::static_ref_cast<jni::JString>(result)->toStdString() 245 ); 246 } 247 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Boolean").clazz)) { 248 return {(bool) jni::static_ref_cast<jni::JBoolean>(result)->value()}; 249 } 250 if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Float").clazz)) { 251 return {(double) jni::static_ref_cast<jni::JFloat>(result)->value()}; 252 } 253 if (env->IsInstanceOf( 254 unpackedResult, 255 cache->getJClass("com/facebook/react/bridge/WritableNativeArray").clazz 256 )) { 257 auto dynamic = jni::static_ref_cast<react::WritableNativeArray::javaobject>(result) 258 ->cthis() 259 ->consume(); 260 return jsi::valueFromDynamic(rt, dynamic); 261 } 262 if (env->IsInstanceOf( 263 unpackedResult, 264 cache->getJClass("com/facebook/react/bridge/WritableNativeMap").clazz 265 )) { 266 auto dynamic = jni::static_ref_cast<react::WritableNativeMap::javaobject>(result) 267 ->cthis() 268 ->consume(); 269 return jsi::valueFromDynamic(rt, dynamic); 270 } 271 272 return jsi::Value::undefined(); 273 } 274 275 jsi::Function MethodMetadata::toAsyncFunction( 276 jsi::Runtime &runtime, 277 JSIInteropModuleRegistry *moduleRegistry 278 ) { 279 return jsi::Function::createFromHostFunction( 280 runtime, 281 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 282 args, 283 [this, moduleRegistry]( 284 jsi::Runtime &rt, 285 const jsi::Value &thisValue, 286 const jsi::Value *args, 287 size_t count 288 ) -> jsi::Value { 289 JNIEnv *env = jni::Environment::current(); 290 291 /** 292 * This will push a new JNI stack frame for the LocalReferences in this 293 * function call. When the stack frame for this lambda is popped, 294 * all LocalReferences are deleted. 295 */ 296 jni::JniLocalScope scope(env, (int) count); 297 298 try { 299 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count); 300 auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs); 301 env->DeleteLocalRef(convertedArgs); 302 303 auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>( 304 JSReferencesCache::JSKeys::PROMISE 305 ); 306 // Creates a JSI promise 307 jsi::Value promise = Promise.callAsConstructor( 308 rt, 309 createPromiseBody(rt, moduleRegistry, globalConvertedArgs) 310 ); 311 return promise; 312 } catch (jni::JniException &jniException) { 313 rethrowAsCodedError(rt, moduleRegistry, jniException); 314 } 315 } 316 ); 317 } 318 319 jsi::Function MethodMetadata::createPromiseBody( 320 jsi::Runtime &runtime, 321 JSIInteropModuleRegistry *moduleRegistry, 322 jobjectArray globalArgs 323 ) { 324 return jsi::Function::createFromHostFunction( 325 runtime, 326 moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"), 327 2, 328 [this, globalArgs, moduleRegistry]( 329 jsi::Runtime &rt, 330 const jsi::Value &thisVal, 331 const jsi::Value *promiseConstructorArgs, 332 size_t promiseConstructorArgCount 333 ) { 334 if (promiseConstructorArgCount != 2) { 335 throw std::invalid_argument("Promise fn arg count must be 2"); 336 } 337 338 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 339 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 340 341 auto &runtimeHolder = moduleRegistry->runtimeHolder; 342 jobject resolve = createJavaCallbackFromJSIFunction( 343 std::move(resolveJSIFn), 344 rt, 345 runtimeHolder->jsInvoker 346 ).release(); 347 348 jobject reject = createJavaCallbackFromJSIFunction( 349 std::move(rejectJSIFn), 350 rt, 351 runtimeHolder->jsInvoker 352 ).release(); 353 354 JNIEnv *env = jni::Environment::current(); 355 356 auto &jPromise = JavaReferencesCache::instance()->getJClass( 357 "expo/modules/kotlin/jni/PromiseImpl"); 358 jmethodID jPromiseConstructor = jPromise.getMethod( 359 "<init>", 360 "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V" 361 ); 362 363 // Creates a promise object 364 jobject promise = env->NewObject( 365 jPromise.clazz, 366 jPromiseConstructor, 367 resolve, 368 reject 369 ); 370 371 // Cast in this place is safe, cause we know that this function expects promise. 372 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 373 asyncFunction->invoke( 374 globalArgs, 375 promise 376 ); 377 378 // We have to remove the local reference to the promise object. 379 // It doesn't mean that the promise will be deallocated, but rather that we move 380 // the ownership to the `JNIAsyncFunctionBody`. 381 env->DeleteLocalRef(promise); 382 env->DeleteGlobalRef(globalArgs); 383 384 return jsi::Value::undefined(); 385 } 386 ); 387 } 388 } // namespace expo 389