1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo) 2 3 #include "JavaScriptModuleObject.h" 4 #include "JSIInteropModuleRegistry.h" 5 6 #include <folly/dynamic.h> 7 #include <jsi/JSIDynamic.h> 8 #include <react/jni/ReadableNativeArray.h> 9 #include <fbjni/detail/Hybrid.h> 10 #include <ReactCommon/TurboModuleUtils.h> 11 #include <jni/JCallback.h> 12 #include <jsi/JSIDynamic.h> 13 #include <fbjni/fbjni.h> 14 #include <jsi/jsi.h> 15 16 #include <utility> 17 #include <tuple> 18 #include <algorithm> 19 20 namespace jni = facebook::jni; 21 namespace jsi = facebook::jsi; 22 namespace react = facebook::react; 23 24 namespace expo { 25 26 jni::local_ref<jni::HybridClass<JavaScriptModuleObject>::jhybriddata> 27 JavaScriptModuleObject::initHybrid(jni::alias_ref<jhybridobject> jThis) { 28 return makeCxxInstance(jThis); 29 } 30 31 void JavaScriptModuleObject::registerNatives() { 32 registerHybrid({ 33 makeNativeMethod("initHybrid", JavaScriptModuleObject::initHybrid), 34 makeNativeMethod("exportConstants", JavaScriptModuleObject::exportConstants), 35 makeNativeMethod("registerSyncFunction", 36 JavaScriptModuleObject::registerSyncFunction), 37 makeNativeMethod("registerAsyncFunction", 38 JavaScriptModuleObject::registerAsyncFunction), 39 makeNativeMethod("registerProperty", 40 JavaScriptModuleObject::registerProperty), 41 }); 42 } 43 44 std::shared_ptr<jsi::Object> JavaScriptModuleObject::getJSIObject(jsi::Runtime &runtime) { 45 if (jsiObject == nullptr) { 46 auto hostObject = std::make_shared<JavaScriptModuleObject::HostObject>(this); 47 jsiObject = std::make_shared<jsi::Object>( 48 jsi::Object::createFromHostObject(runtime, hostObject)); 49 } 50 51 return jsiObject; 52 } 53 54 void JavaScriptModuleObject::exportConstants( 55 jni::alias_ref<react::NativeMap::javaobject> constants 56 ) { 57 auto dynamic = constants->cthis()->consume(); 58 assert(dynamic.isObject()); 59 60 for (const auto &[key, value]: dynamic.items()) { 61 this->constants[key.asString()] = value; 62 } 63 } 64 65 void JavaScriptModuleObject::registerSyncFunction( 66 jni::alias_ref<jstring> name, 67 jint args, 68 jni::alias_ref<jni::JArrayClass<ExpectedType>> desiredTypes, 69 jni::alias_ref<JNIFunctionBody::javaobject> body 70 ) { 71 std::string cName = name->toStdString(); 72 73 std::unique_ptr<int[]> types = std::make_unique<int[]>(args); 74 for (size_t i = 0; i < args; i++) { 75 auto expectedType = desiredTypes->getElement(i); 76 types[i] = expectedType->getCombinedTypes(); 77 } 78 79 methodsMetadata.try_emplace( 80 cName, 81 cName, 82 args, 83 false, 84 std::move(types), 85 jni::make_global(body) 86 ); 87 } 88 89 void JavaScriptModuleObject::registerAsyncFunction( 90 jni::alias_ref<jstring> name, 91 jint args, 92 jni::alias_ref<jni::JArrayClass<ExpectedType>> desiredTypes, 93 jni::alias_ref<JNIAsyncFunctionBody::javaobject> body 94 ) { 95 auto cName = name->toStdString(); 96 97 std::unique_ptr<int[]> types = std::make_unique<int[]>(args); 98 for (size_t i = 0; i < args; i++) { 99 auto expectedType = desiredTypes->getElement(i); 100 types[i] = expectedType->getCombinedTypes(); 101 } 102 103 methodsMetadata.try_emplace( 104 cName, 105 cName, 106 args, 107 true, 108 std::move(types), 109 jni::make_global(body) 110 ); 111 } 112 113 void JavaScriptModuleObject::registerProperty( 114 jni::alias_ref<jstring> name, 115 jint desiredType, 116 jni::alias_ref<JNIFunctionBody::javaobject> getter, 117 jni::alias_ref<JNIFunctionBody::javaobject> setter 118 ) { 119 auto cName = name->toStdString(); 120 std::unique_ptr<int[]> types = std::make_unique<int[]>(1); 121 types[0] = desiredType; 122 123 auto getterMetadata = MethodMetadata( 124 cName, 125 0, 126 false, 127 std::make_unique<int[]>(0), 128 jni::make_global(getter) 129 ); 130 131 auto setterMetadata = MethodMetadata( 132 cName, 133 1, 134 false, 135 std::move(types), 136 jni::make_global(setter) 137 ); 138 139 auto functions = std::make_pair( 140 std::move(getterMetadata), 141 std::move(setterMetadata) 142 ); 143 144 properties.insert({cName, std::move(functions)}); 145 } 146 147 JavaScriptModuleObject::HostObject::HostObject( 148 JavaScriptModuleObject *jsModule) : jsModule(jsModule) {} 149 150 jsi::Value JavaScriptModuleObject::HostObject::get(jsi::Runtime &runtime, 151 const jsi::PropNameID &name) { 152 auto cName = name.utf8(runtime); 153 154 auto constantsRecord = jsModule->constants.find(cName); 155 if (constantsRecord != jsModule->constants.end()) { 156 auto dynamic = constantsRecord->second; 157 return jsi::valueFromDynamic(runtime, dynamic); 158 } 159 160 auto propertyRecord = jsModule->properties.find(cName); 161 if (propertyRecord != jsModule->properties.end()) { 162 auto&[getter, _] = propertyRecord->second; 163 return getter.callSync(runtime, jsModule->jsiInteropModuleRegistry, nullptr, 0); 164 } 165 166 auto metadataRecord = jsModule->methodsMetadata.find(cName); 167 if (metadataRecord == jsModule->methodsMetadata.end()) { 168 return jsi::Value::undefined(); 169 } 170 auto &metadata = metadataRecord->second; 171 return jsi::Value(runtime, *metadata.toJSFunction(runtime, jsModule->jsiInteropModuleRegistry)); 172 } 173 174 void JavaScriptModuleObject::HostObject::set( 175 jsi::Runtime &runtime, 176 const jsi::PropNameID &name, 177 const jsi::Value &value 178 ) { 179 auto cName = name.utf8(runtime); 180 auto propertyRecord = jsModule->properties.find(cName); 181 if (propertyRecord != jsModule->properties.end()) { 182 auto&[_, setter] = propertyRecord->second; 183 setter.callSync(runtime, jsModule->jsiInteropModuleRegistry, &value, 1); 184 return; 185 } 186 187 throw jsi::JSError( 188 runtime, 189 "RuntimeError: Cannot override the host object for expo module '" + name.utf8(runtime) + "'" 190 ); 191 } 192 193 std::vector<jsi::PropNameID> JavaScriptModuleObject::HostObject::getPropertyNames( 194 jsi::Runtime &rt 195 ) { 196 auto &metadata = jsModule->methodsMetadata; 197 std::vector<jsi::PropNameID> result; 198 std::transform( 199 metadata.begin(), 200 metadata.end(), 201 std::back_inserter(result), 202 [&rt](const auto &kv) { 203 return jsi::PropNameID::forUtf8(rt, kv.first); 204 } 205 ); 206 207 auto &constants = jsModule->constants; 208 std::transform( 209 constants.begin(), 210 constants.end(), 211 std::back_inserter(result), 212 [&rt](const auto &kv) { 213 return jsi::PropNameID::forUtf8(rt, kv.first); 214 } 215 ); 216 217 auto &properties = jsModule->properties; 218 std::transform( 219 properties.begin(), 220 properties.end(), 221 std::back_inserter(result), 222 [&rt](const auto &kv) { 223 return jsi::PropNameID::forUtf8(rt, kv.first); 224 } 225 ); 226 227 return result; 228 } 229 } // namespace expo 230