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