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