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 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::newObjectCxxArgs(std::move(fn)); 106 } 107 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 std::vector<jobject> result(count); 138 139 const auto getCurrentArg = [&thisValue, args, takesOwner = takesOwner]( 140 size_t index 141 ) -> const jsi::Value & { 142 if (!takesOwner) { 143 return args[index]; 144 } else { 145 if (index != 0) { 146 return args[index - 1]; 147 } 148 return thisValue; 149 } 150 }; 151 152 for (size_t argIndex = 0; argIndex < count; argIndex++) { 153 const jsi::Value &arg = getCurrentArg(argIndex); 154 auto &type = argTypes[argIndex]; 155 if (arg.isNull() || arg.isUndefined()) { 156 // If value is null or undefined, we just passes a null 157 // Kotlin code will check if expected type is nullable. 158 result[argIndex] = nullptr; 159 } else { 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 174 return argumentArray; 175 } 176 177 MethodMetadata::MethodMetadata( 178 std::string name, 179 bool takesOwner, 180 int args, 181 bool isAsync, 182 jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 183 jni::global_ref<jobject> &&jBodyReference 184 ) : name(std::move(name)), 185 takesOwner(takesOwner), 186 args(args), 187 isAsync(isAsync), 188 jBodyReference(std::move(jBodyReference)) { 189 argTypes.reserve(args); 190 for (size_t i = 0; i < args; i++) { 191 auto expectedType = expectedArgTypes->getElement(i); 192 argTypes.push_back( 193 std::make_unique<AnyType>(std::move(expectedType)) 194 ); 195 } 196 } 197 198 MethodMetadata::MethodMetadata( 199 std::string name, 200 bool takesOwner, 201 int args, 202 bool isAsync, 203 std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes, 204 jni::global_ref<jobject> &&jBodyReference 205 ) : name(std::move(name)), 206 takesOwner(takesOwner), 207 args(args), 208 isAsync(isAsync), 209 argTypes(std::move(expectedArgTypes)), 210 jBodyReference(std::move(jBodyReference)) { 211 } 212 213 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 214 jsi::Runtime &runtime, 215 JSIInteropModuleRegistry *moduleRegistry 216 ) { 217 if (body == nullptr) { 218 if (isAsync) { 219 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 220 } else { 221 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 222 } 223 } 224 225 return body; 226 } 227 228 jsi::Function MethodMetadata::toSyncFunction( 229 jsi::Runtime &runtime, 230 JSIInteropModuleRegistry *moduleRegistry 231 ) { 232 return jsi::Function::createFromHostFunction( 233 runtime, 234 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 235 args, 236 [this, moduleRegistry]( 237 jsi::Runtime &rt, 238 const jsi::Value &thisValue, 239 const jsi::Value *args, 240 size_t count 241 ) -> jsi::Value { 242 try { 243 return this->callSync( 244 rt, 245 moduleRegistry, 246 thisValue, 247 args, 248 count 249 ); 250 } catch (jni::JniException &jniException) { 251 rethrowAsCodedError(rt, jniException); 252 } 253 }); 254 } 255 256 jni::local_ref<jobject> MethodMetadata::callJNISync( 257 JNIEnv *env, 258 jsi::Runtime &rt, 259 JSIInteropModuleRegistry *moduleRegistry, 260 const jsi::Value &thisValue, 261 const jsi::Value *args, 262 size_t count 263 ) { 264 if (this->jBodyReference == nullptr) { 265 return nullptr; 266 } 267 268 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count); 269 270 // Cast in this place is safe, cause we know that this function is promise-less. 271 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 272 auto result = syncFunction->invoke( 273 convertedArgs 274 ); 275 276 env->DeleteLocalRef(convertedArgs); 277 return result; 278 } 279 280 jsi::Value MethodMetadata::callSync( 281 jsi::Runtime &rt, 282 JSIInteropModuleRegistry *moduleRegistry, 283 const jsi::Value &thisValue, 284 const jsi::Value *args, 285 size_t count 286 ) { 287 JNIEnv *env = jni::Environment::current(); 288 /** 289 * This will push a new JNI stack frame for the LocalReferences in this 290 * function call. When the stack frame for this lambda is popped, 291 * all LocalReferences are deleted. 292 */ 293 jni::JniLocalScope scope(env, (int) count); 294 295 auto result = this->callJNISync(env, rt, moduleRegistry, thisValue, args, count); 296 return convert(moduleRegistry, env, rt, std::move(result)); 297 } 298 299 jsi::Function MethodMetadata::toAsyncFunction( 300 jsi::Runtime &runtime, 301 JSIInteropModuleRegistry *moduleRegistry 302 ) { 303 return jsi::Function::createFromHostFunction( 304 runtime, 305 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 306 args, 307 [this, moduleRegistry]( 308 jsi::Runtime &rt, 309 const jsi::Value &thisValue, 310 const jsi::Value *args, 311 size_t count 312 ) -> jsi::Value { 313 JNIEnv *env = jni::Environment::current(); 314 315 /** 316 * This will push a new JNI stack frame for the LocalReferences in this 317 * function call. When the stack frame for this lambda is popped, 318 * all LocalReferences are deleted. 319 */ 320 jni::JniLocalScope scope(env, (int) count); 321 322 auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>( 323 JSReferencesCache::JSKeys::PROMISE 324 ); 325 326 try { 327 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count); 328 auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs); 329 env->DeleteLocalRef(convertedArgs); 330 331 // Creates a JSI promise 332 jsi::Value promise = Promise.callAsConstructor( 333 rt, 334 createPromiseBody(rt, moduleRegistry, globalConvertedArgs) 335 ); 336 return promise; 337 } catch (jni::JniException &jniException) { 338 jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable(); 339 if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) { 340 unboxedThrowable = UnexpectedException::create(jniException.what()); 341 } 342 343 auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable); 344 auto code = codedException->getCode(); 345 auto message = codedException->getLocalizedMessage().value_or(""); 346 347 jsi::Value promise = Promise.callAsConstructor( 348 rt, 349 jsi::Function::createFromHostFunction( 350 rt, 351 moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"), 352 2, 353 [code, message]( 354 jsi::Runtime &rt, 355 const jsi::Value &thisVal, 356 const jsi::Value *promiseConstructorArgs, 357 size_t promiseConstructorArgCount 358 ) { 359 if (promiseConstructorArgCount != 2) { 360 throw std::invalid_argument("Promise fn arg count must be 2"); 361 } 362 363 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 364 rejectJSIFn.call( 365 rt, 366 makeCodedError( 367 rt, 368 jsi::String::createFromUtf8(rt, code), 369 jsi::String::createFromUtf8(rt, message) 370 ) 371 ); 372 return jsi::Value::undefined(); 373 } 374 ) 375 ); 376 377 return promise; 378 } 379 } 380 ); 381 } 382 383 jsi::Function MethodMetadata::createPromiseBody( 384 jsi::Runtime &runtime, 385 JSIInteropModuleRegistry *moduleRegistry, 386 jobjectArray globalArgs 387 ) { 388 return jsi::Function::createFromHostFunction( 389 runtime, 390 moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"), 391 2, 392 [this, globalArgs, moduleRegistry]( 393 jsi::Runtime &rt, 394 const jsi::Value &thisVal, 395 const jsi::Value *promiseConstructorArgs, 396 size_t promiseConstructorArgCount 397 ) { 398 if (promiseConstructorArgCount != 2) { 399 throw std::invalid_argument("Promise fn arg count must be 2"); 400 } 401 402 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 403 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 404 405 jobject resolve = createJavaCallbackFromJSIFunction( 406 std::move(resolveJSIFn), 407 rt, 408 moduleRegistry 409 ).release(); 410 411 jobject reject = createJavaCallbackFromJSIFunction( 412 std::move(rejectJSIFn), 413 rt, 414 moduleRegistry, 415 true 416 ).release(); 417 418 JNIEnv *env = jni::Environment::current(); 419 420 auto &jPromise = JavaReferencesCache::instance()->getJClass( 421 "expo/modules/kotlin/jni/PromiseImpl"); 422 jmethodID jPromiseConstructor = jPromise.getMethod( 423 "<init>", 424 "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V" 425 ); 426 427 // Creates a promise object 428 jobject promise = env->NewObject( 429 jPromise.clazz, 430 jPromiseConstructor, 431 resolve, 432 reject 433 ); 434 435 // Cast in this place is safe, cause we know that this function expects promise. 436 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 437 asyncFunction->invoke( 438 globalArgs, 439 promise 440 ); 441 442 // We have to remove the local reference to the promise object. 443 // It doesn't mean that the promise will be deallocated, but rather that we move 444 // the ownership to the `JNIAsyncFunctionBody`. 445 env->DeleteLocalRef(promise); 446 env->DeleteGlobalRef(globalArgs); 447 448 return jsi::Value::undefined(); 449 } 450 ); 451 } 452 } // namespace expo 453