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>> expectedArgTypes, 69 jni::alias_ref<JNIFunctionBody::javaobject> body 70 ) { 71 std::string cName = name->toStdString(); 72 73 methodsMetadata.try_emplace( 74 cName, 75 longLivedObjectCollection_, 76 cName, 77 args, 78 false, 79 jni::make_local(expectedArgTypes), 80 jni::make_global(body) 81 ); 82 } 83 84 void JavaScriptModuleObject::registerAsyncFunction( 85 jni::alias_ref<jstring> name, 86 jint args, 87 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 88 jni::alias_ref<JNIAsyncFunctionBody::javaobject> body 89 ) { 90 std::string cName = name->toStdString(); 91 92 methodsMetadata.try_emplace( 93 cName, 94 longLivedObjectCollection_, 95 cName, 96 args, 97 true, 98 jni::make_local(expectedArgTypes), 99 jni::make_global(body) 100 ); 101 } 102 103 void JavaScriptModuleObject::registerProperty( 104 jni::alias_ref<jstring> name, 105 jni::alias_ref<ExpectedType> expectedArgType, 106 jni::alias_ref<JNIFunctionBody::javaobject> getter, 107 jni::alias_ref<JNIFunctionBody::javaobject> setter 108 ) { 109 auto cName = name->toStdString(); 110 111 auto getterMetadata = MethodMetadata( 112 longLivedObjectCollection_, 113 cName, 114 0, 115 false, 116 std::vector<std::unique_ptr<AnyType>>(), 117 jni::make_global(getter) 118 ); 119 120 auto types = std::vector<std::unique_ptr<AnyType>>(); 121 types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType))); 122 auto setterMetadata = MethodMetadata( 123 longLivedObjectCollection_, 124 cName, 125 1, 126 false, 127 std::move(types), 128 jni::make_global(setter) 129 ); 130 131 auto functions = std::make_pair( 132 std::move(getterMetadata), 133 std::move(setterMetadata) 134 ); 135 136 properties.insert({cName, std::move(functions)}); 137 } 138 139 JavaScriptModuleObject::HostObject::HostObject( 140 JavaScriptModuleObject *jsModule) : jsModule(jsModule) {} 141 142 /** 143 * Clears all the JSI references held by the `JavaScriptModuleObject`. 144 */ 145 JavaScriptModuleObject::HostObject::~HostObject() { 146 if (jsModule->jsiObject != nullptr) { 147 jsModule->jsiObject.reset(); 148 } 149 jsModule->methodsMetadata.clear(); 150 jsModule->constants.clear(); 151 jsModule->properties.clear(); 152 jsModule->longLivedObjectCollection_->clear(); 153 } 154 155 jsi::Value JavaScriptModuleObject::HostObject::get(jsi::Runtime &runtime, 156 const jsi::PropNameID &name) { 157 auto cName = name.utf8(runtime); 158 159 auto constantsRecord = jsModule->constants.find(cName); 160 if (constantsRecord != jsModule->constants.end()) { 161 auto dynamic = constantsRecord->second; 162 return jsi::valueFromDynamic(runtime, dynamic); 163 } 164 165 auto propertyRecord = jsModule->properties.find(cName); 166 if (propertyRecord != jsModule->properties.end()) { 167 auto&[getter, _] = propertyRecord->second; 168 return getter.callSync(runtime, jsModule->jsiInteropModuleRegistry, nullptr, 0); 169 } 170 171 auto metadataRecord = jsModule->methodsMetadata.find(cName); 172 if (metadataRecord == jsModule->methodsMetadata.end()) { 173 return jsi::Value::undefined(); 174 } 175 auto &metadata = metadataRecord->second; 176 return jsi::Value(runtime, *metadata.toJSFunction(runtime, jsModule->jsiInteropModuleRegistry)); 177 } 178 179 void JavaScriptModuleObject::HostObject::set( 180 jsi::Runtime &runtime, 181 const jsi::PropNameID &name, 182 const jsi::Value &value 183 ) { 184 auto cName = name.utf8(runtime); 185 auto propertyRecord = jsModule->properties.find(cName); 186 if (propertyRecord != jsModule->properties.end()) { 187 auto&[_, setter] = propertyRecord->second; 188 setter.callSync(runtime, jsModule->jsiInteropModuleRegistry, &value, 1); 189 return; 190 } 191 192 throw jsi::JSError( 193 runtime, 194 "RuntimeError: Cannot override the host object for expo module '" + name.utf8(runtime) + "'" 195 ); 196 } 197 198 std::vector<jsi::PropNameID> JavaScriptModuleObject::HostObject::getPropertyNames( 199 jsi::Runtime &rt 200 ) { 201 auto &metadata = jsModule->methodsMetadata; 202 std::vector<jsi::PropNameID> result; 203 std::transform( 204 metadata.begin(), 205 metadata.end(), 206 std::back_inserter(result), 207 [&rt](const auto &kv) { 208 return jsi::PropNameID::forUtf8(rt, kv.first); 209 } 210 ); 211 212 auto &constants = jsModule->constants; 213 std::transform( 214 constants.begin(), 215 constants.end(), 216 std::back_inserter(result), 217 [&rt](const auto &kv) { 218 return jsi::PropNameID::forUtf8(rt, kv.first); 219 } 220 ); 221 222 auto &properties = jsModule->properties; 223 std::transform( 224 properties.begin(), 225 properties.end(), 226 std::back_inserter(result), 227 [&rt](const auto &kv) { 228 return jsi::PropNameID::forUtf8(rt, kv.first); 229 } 230 ); 231 232 return result; 233 } 234 235 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis) 236 : javaPart_(jni::make_global(jThis)) { 237 longLivedObjectCollection_ = std::make_shared<react::LongLivedObjectCollection>(); 238 } 239 240 } // namespace expo 241