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