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