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