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 #include <sstream>
20 
21 namespace jni = facebook::jni;
22 namespace jsi = facebook::jsi;
23 namespace react = facebook::react;
24 
25 namespace expo {
26 
27 void decorateObjectWithFunctions(
28   jsi::Runtime &runtime,
29   JSIInteropModuleRegistry *jsiInteropModuleRegistry,
30   jsi::Object *jsObject,
31   JavaScriptModuleObject *objectData) {
32   for (auto &[name, method]: objectData->methodsMetadata) {
33     jsObject->setProperty(
34       runtime,
35       jsi::String::createFromUtf8(runtime, name),
36       jsi::Value(runtime, *method.toJSFunction(runtime, jsiInteropModuleRegistry))
37     );
38   }
39 }
40 
41 void decorateObjectWithProperties(
42   jsi::Runtime &runtime,
43   JSIInteropModuleRegistry *jsiInteropModuleRegistry,
44   jsi::Object *jsObject,
45   JavaScriptModuleObject *objectData) {
46   for (auto &[name, property]: objectData->properties) {
47     auto &[getter, setter] = property;
48 
49     auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime,
50                                                                   1 << 1 /* enumerable */);
51     descriptor.setProperty(
52       runtime,
53       "get",
54       jsi::Value(runtime, *getter.toJSFunction(runtime,
55                                                jsiInteropModuleRegistry))
56     );
57     descriptor.setProperty(
58       runtime,
59       "set",
60       jsi::Value(runtime, *setter.toJSFunction(runtime,
61                                                jsiInteropModuleRegistry))
62     );
63     JavaScriptObject::defineProperty(runtime, jsObject, name, std::move(descriptor));
64   }
65 }
66 
67 void decorateObjectWithConstants(
68   jsi::Runtime &runtime,
69   JSIInteropModuleRegistry *jsiInteropModuleRegistry,
70   jsi::Object *jsObject,
71   JavaScriptModuleObject *objectData) {
72   for (const auto &[name, value]: objectData->constants) {
73     jsObject->setProperty(
74       runtime,
75       jsi::String::createFromUtf8(runtime, name),
76       jsi::valueFromDynamic(runtime, value)
77     );
78   }
79 }
80 
81 jni::local_ref<jni::HybridClass<JavaScriptModuleObject>::jhybriddata>
82 JavaScriptModuleObject::initHybrid(jni::alias_ref<jhybridobject> jThis) {
83   return makeCxxInstance(jThis);
84 }
85 
86 void JavaScriptModuleObject::registerNatives() {
87   registerHybrid({
88                    makeNativeMethod("initHybrid", JavaScriptModuleObject::initHybrid),
89                    makeNativeMethod("exportConstants", JavaScriptModuleObject::exportConstants),
90                    makeNativeMethod("registerSyncFunction",
91                                     JavaScriptModuleObject::registerSyncFunction),
92                    makeNativeMethod("registerAsyncFunction",
93                                     JavaScriptModuleObject::registerAsyncFunction),
94                    makeNativeMethod("registerProperty",
95                                     JavaScriptModuleObject::registerProperty),
96                    makeNativeMethod("registerClass",
97                                     JavaScriptModuleObject::registerClass)
98                  });
99 }
100 
101 std::shared_ptr<jsi::Object> JavaScriptModuleObject::getJSIObject(jsi::Runtime &runtime) {
102   if (auto object = jsiObject.lock()) {
103     return object;
104   }
105 
106   auto moduleObject = std::make_shared<jsi::Object>(runtime);
107 
108   decorateObjectWithConstants(
109     runtime,
110     jsiInteropModuleRegistry,
111     moduleObject.get(),
112     this
113   );
114   decorateObjectWithProperties(
115     runtime,
116     jsiInteropModuleRegistry,
117     moduleObject.get(),
118     this
119   );
120   decorateObjectWithFunctions(
121     runtime,
122     jsiInteropModuleRegistry,
123     moduleObject.get(),
124     this
125   );
126 
127   for (auto &[name, classRef]: classes) {
128     auto classObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>(
129       jni::make_local(classRef))->cthis();
130 
131     std::string nativeConstructorKey("__native_constructor__");
132 
133     // Create a string buffer of the source code to evaluate.
134     std::stringstream source;
135     source << "(function " << name << "(...args) { this." << nativeConstructorKey
136            << "(...args); return this; })";
137     std::shared_ptr<jsi::StringBuffer> sourceBuffer = std::make_shared<jsi::StringBuffer>(
138       source.str());
139 
140     // Evaluate the code and obtain returned value (the constructor function).
141     jsi::Object klass = runtime.evaluateJavaScript(sourceBuffer, "").asObject(runtime);
142 
143     // Set the native constructor in the prototype.
144     jsi::Object prototype = klass.getPropertyAsObject(runtime, "prototype");
145     jsi::PropNameID nativeConstructorPropId = jsi::PropNameID::forAscii(runtime,
146                                                                         nativeConstructorKey);
147     jsi::Function nativeConstructor = jsi::Function::createFromHostFunction(
148       runtime,
149       nativeConstructorPropId,
150       // The paramCount is not obligatory to match, it only affects the `length` property of the function.
151       0,
152       [classObject, jsiInteropModuleRegistry = jsiInteropModuleRegistry](
153         jsi::Runtime &runtime,
154         const jsi::Value &thisValue,
155         const jsi::Value *args,
156         size_t count
157       ) -> jsi::Value {
158         auto thisObject = thisValue.asObject(runtime);
159         decorateObjectWithProperties(runtime, jsiInteropModuleRegistry, &thisObject, classObject);
160         // TODO(@lukmccall): call the constructor from the class definition
161         return jsi::Value::undefined();
162       });
163 
164     auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 0);
165     descriptor.setProperty(runtime, "value", jsi::Value(runtime, nativeConstructor));
166 
167     JavaScriptObject::defineProperty(runtime, &prototype, nativeConstructorKey,
168                                      std::move(descriptor));
169 
170     moduleObject->setProperty(
171       runtime,
172       jsi::String::createFromUtf8(runtime, name),
173       jsi::Value(runtime, klass.asFunction(runtime))
174     );
175 
176     decorateObjectWithFunctions(
177       runtime,
178       jsiInteropModuleRegistry,
179       &prototype,
180       classObject
181     );
182   }
183 
184   jsiObject = moduleObject;
185   return moduleObject;
186 }
187 
188 void JavaScriptModuleObject::exportConstants(
189   jni::alias_ref<react::NativeMap::javaobject> constants
190 ) {
191   auto dynamic = constants->cthis()->consume();
192   assert(dynamic.isObject());
193 
194   for (const auto &[key, value]: dynamic.items()) {
195     this->constants[key.asString()] = value;
196   }
197 }
198 
199 void JavaScriptModuleObject::registerSyncFunction(
200   jni::alias_ref<jstring> name,
201   jboolean takesOwner,
202   jint args,
203   jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
204   jni::alias_ref<JNIFunctionBody::javaobject> body
205 ) {
206   std::string cName = name->toStdString();
207 
208   methodsMetadata.try_emplace(
209     cName,
210     longLivedObjectCollection_,
211     cName,
212     takesOwner,
213     args,
214     false,
215     jni::make_local(expectedArgTypes),
216     jni::make_global(body)
217   );
218 }
219 
220 void JavaScriptModuleObject::registerAsyncFunction(
221   jni::alias_ref<jstring> name,
222   jboolean takesOwner,
223   jint args,
224   jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
225   jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
226 ) {
227   std::string cName = name->toStdString();
228 
229   methodsMetadata.try_emplace(
230     cName,
231     longLivedObjectCollection_,
232     cName,
233     takesOwner,
234     args,
235     true,
236     jni::make_local(expectedArgTypes),
237     jni::make_global(body)
238   );
239 }
240 
241 void JavaScriptModuleObject::registerClass(
242   jni::alias_ref<jstring> name,
243   jni::alias_ref<JavaScriptModuleObject::javaobject> classObject
244 ) {
245   std::string cName = name->toStdString();
246   classes[cName] = jni::make_global(classObject);
247 }
248 
249 void JavaScriptModuleObject::registerProperty(
250   jni::alias_ref<jstring> name,
251   jni::alias_ref<ExpectedType> expectedArgType,
252   jni::alias_ref<JNIFunctionBody::javaobject> getter,
253   jni::alias_ref<JNIFunctionBody::javaobject> setter
254 ) {
255   auto cName = name->toStdString();
256 
257   auto getterMetadata = MethodMetadata(
258     longLivedObjectCollection_,
259     cName,
260     false,
261     0,
262     false,
263     std::vector<std::unique_ptr<AnyType>>(),
264     jni::make_global(getter)
265   );
266 
267   auto types = std::vector<std::unique_ptr<AnyType>>();
268   types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType)));
269   auto setterMetadata = MethodMetadata(
270     longLivedObjectCollection_,
271     cName,
272     false,
273     1,
274     false,
275     std::move(types),
276     jni::make_global(setter)
277   );
278 
279   auto functions = std::make_pair(
280     std::move(getterMetadata),
281     std::move(setterMetadata)
282   );
283 
284   properties.insert({cName, std::move(functions)});
285 }
286 
287 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis)
288   : javaPart_(jni::make_global(jThis)) {
289   longLivedObjectCollection_ = std::make_shared<react::LongLivedObjectCollection>();
290 }
291 } // namespace expo
292