1 #include "MethodMetadata.h" 2 3 #include "JSIInteropModuleRegistry.h" 4 5 namespace jni = facebook::jni; 6 namespace jsi = facebook::jsi; 7 namespace react = facebook::react; 8 9 namespace expo { 10 11 // Modified version of the RN implementation 12 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57 13 jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction( 14 jsi::Function &&function, 15 jsi::Runtime &rt, 16 std::shared_ptr<react::CallInvoker> jsInvoker 17 ) { 18 auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt, 19 std::move(jsInvoker)); 20 21 // This needs to be a shared_ptr because: 22 // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is 23 // not. 24 // 2. It cannot be weak_ptr since we need this object to live on. 25 // 3. It cannot be a value, because that would be deleted as soon as this 26 // function returns. 27 auto callbackWrapperOwner = 28 std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper); 29 30 std::function<void(folly::dynamic)> fn = 31 [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false]( 32 folly::dynamic responses) mutable { 33 if (wrapperWasCalled) { 34 throw std::runtime_error( 35 "callback 2 arg cannot be called more than once"); 36 } 37 38 auto strongWrapper = weakWrapper.lock(); 39 if (!strongWrapper) { 40 return; 41 } 42 43 strongWrapper->jsInvoker().invokeAsync( 44 [weakWrapper, callbackWrapperOwner, responses]() mutable { 45 auto strongWrapper2 = weakWrapper.lock(); 46 if (!strongWrapper2) { 47 return; 48 } 49 50 jsi::Value args = 51 jsi::valueFromDynamic(strongWrapper2->runtime(), responses); 52 auto argsArray = args.getObject(strongWrapper2->runtime()) 53 .asArray(strongWrapper2->runtime()); 54 jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0); 55 56 strongWrapper2->callback().call( 57 strongWrapper2->runtime(), 58 (const jsi::Value *) &arg, 59 (size_t) 1 60 ); 61 62 callbackWrapperOwner.reset(); 63 }); 64 65 wrapperWasCalled = true; 66 }; 67 68 return react::JCxxCallbackImpl::newObjectCxxArgs(fn); 69 } 70 71 MethodMetadata::MethodMetadata( 72 std::string name, 73 int args, 74 bool isAsync, 75 jni::global_ref<jobject> &&jBodyReference 76 ) : name(name), 77 args(args), 78 isAsync(isAsync), 79 jBodyReference(jBodyReference) {} 80 81 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 82 jsi::Runtime &runtime, 83 JSIInteropModuleRegistry *moduleRegistry 84 ) { 85 if (body == nullptr) { 86 if (isAsync) { 87 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 88 } else { 89 body = std::make_shared<jsi::Function>(toSyncFunction(runtime)); 90 } 91 } 92 93 return body; 94 } 95 96 jsi::Function MethodMetadata::toSyncFunction(jsi::Runtime &runtime) { 97 return jsi::Function::createFromHostFunction( 98 runtime, 99 jsi::PropNameID::forAscii(runtime, name), 100 args, 101 [this]( 102 jsi::Runtime &rt, 103 const jsi::Value &thisValue, 104 const jsi::Value *args, 105 size_t count 106 ) -> jsi::Value { 107 auto dynamicArray = folly::dynamic::array(); 108 for (int i = 0; i < count; i++) { 109 auto &arg = args[i]; 110 dynamicArray.push_back(jsi::dynamicFromValue(rt, arg)); 111 } 112 113 // Cast in this place is safe, cause we know that this function is promise-less. 114 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 115 auto result = syncFunction->invoke( 116 react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamicArray)).get() 117 ); 118 119 if (result == nullptr) { 120 return jsi::Value::undefined(); 121 } 122 123 return jsi::valueFromDynamic(rt, result->cthis()->consume()) 124 .asObject(rt) 125 .asArray(rt) 126 .getValueAtIndex(rt, 0); 127 }); 128 } 129 130 jsi::Function MethodMetadata::toAsyncFunction( 131 jsi::Runtime &runtime, 132 JSIInteropModuleRegistry *moduleRegistry 133 ) { 134 return jsi::Function::createFromHostFunction( 135 runtime, 136 jsi::PropNameID::forAscii(runtime, name), 137 args, 138 [this, moduleRegistry]( 139 jsi::Runtime &rt, 140 const jsi::Value &thisValue, 141 const jsi::Value *args, 142 size_t count 143 ) -> jsi::Value { 144 auto dynamicArray = folly::dynamic::array(); 145 for (int i = 0; i < count; i++) { 146 auto &arg = args[i]; 147 dynamicArray.push_back(jsi::dynamicFromValue(rt, arg)); 148 } 149 150 auto Promise = rt.global().getPropertyAsFunction(rt, "Promise"); 151 // Creates a JSI promise 152 jsi::Value promise = Promise.callAsConstructor( 153 rt, 154 createPromiseBody(rt, moduleRegistry, std::move(dynamicArray)) 155 ); 156 return promise; 157 } 158 ); 159 } 160 161 jsi::Function MethodMetadata::createPromiseBody( 162 jsi::Runtime &runtime, 163 JSIInteropModuleRegistry *moduleRegistry, 164 folly::dynamic &&args 165 ) { 166 return jsi::Function::createFromHostFunction( 167 runtime, 168 jsi::PropNameID::forAscii(runtime, "promiseFn"), 169 2, 170 [this, args = std::move(args), moduleRegistry]( 171 jsi::Runtime &rt, 172 const jsi::Value &thisVal, 173 const jsi::Value *promiseConstructorArgs, 174 size_t promiseConstructorArgCount 175 ) { 176 if (promiseConstructorArgCount != 2) { 177 throw std::invalid_argument("Promise fn arg count must be 2"); 178 } 179 180 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 181 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 182 183 jobject resolve = createJavaCallbackFromJSIFunction( 184 std::move(resolveJSIFn), 185 rt, 186 moduleRegistry->jsInvoker 187 ).release(); 188 189 jobject reject = createJavaCallbackFromJSIFunction( 190 std::move(rejectJSIFn), 191 rt, 192 moduleRegistry->jsInvoker 193 ).release(); 194 195 JNIEnv *env = jni::Environment::current(); 196 197 jclass jPromiseImpl = 198 env->FindClass("com/facebook/react/bridge/PromiseImpl"); 199 jmethodID jPromiseImplConstructor = env->GetMethodID( 200 jPromiseImpl, 201 "<init>", 202 "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"); 203 204 // Creates a promise object 205 jobject promise = env->NewObject( 206 jPromiseImpl, 207 jPromiseImplConstructor, 208 resolve, 209 reject 210 ); 211 212 // Cast in this place is safe, cause we know that this function expects promise. 213 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 214 asyncFunction->invoke( 215 react::ReadableNativeArray::newObjectCxxArgs(args).get(), 216 promise 217 ); 218 219 // We have to remove the local reference to the promise object. 220 // It doesn't mean that the promise will be deallocated, but rather that we move 221 // the ownership to the `JNIAsyncFunctionBody`. 222 env->DeleteLocalRef(promise); 223 224 return jsi::Value::undefined(); 225 } 226 ); 227 } 228 229 } // namespace expo 230