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