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 moduleObject = std::make_shared<jsi::Object>(runtime);
50 
51   for (const auto &[name, value]: constants) {
52     moduleObject->setProperty(
53       runtime,
54       jsi::String::createFromUtf8(runtime, name),
55       jsi::valueFromDynamic(runtime, value)
56     );
57   }
58 
59   for (auto &[name, property]: properties) {
60     auto &[getter, setter] = property;
61 
62     auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 1 << 1 /* enumerable */);
63     descriptor.setProperty(runtime, "get", jsi::Value(runtime, *getter.toJSFunction(runtime,
64                                                                                     jsiInteropModuleRegistry)));
65     descriptor.setProperty(runtime, "set", jsi::Value(runtime, *setter.toJSFunction(runtime,
66                                                                                     jsiInteropModuleRegistry)));
67     JavaScriptObject::defineProperty(runtime, moduleObject, name, std::move(descriptor));
68   }
69 
70   for (auto &[name, method]: methodsMetadata) {
71     moduleObject->setProperty(
72       runtime,
73       jsi::String::createFromUtf8(runtime, name),
74       jsi::Value(runtime, *method.toJSFunction(runtime, jsiInteropModuleRegistry))
75     );
76   }
77 
78   jsiObject = moduleObject;
79   return moduleObject;
80 }
81 
82 void JavaScriptModuleObject::exportConstants(
83   jni::alias_ref<react::NativeMap::javaobject> constants
84 ) {
85   auto dynamic = constants->cthis()->consume();
86   assert(dynamic.isObject());
87 
88   for (const auto &[key, value]: dynamic.items()) {
89     this->constants[key.asString()] = value;
90   }
91 }
92 
93 void JavaScriptModuleObject::registerSyncFunction(
94   jni::alias_ref<jstring> name,
95   jint args,
96   jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
97   jni::alias_ref<JNIFunctionBody::javaobject> body
98 ) {
99   std::string cName = name->toStdString();
100 
101   methodsMetadata.try_emplace(
102     cName,
103     longLivedObjectCollection_,
104     cName,
105     args,
106     false,
107     jni::make_local(expectedArgTypes),
108     jni::make_global(body)
109   );
110 }
111 
112 void JavaScriptModuleObject::registerAsyncFunction(
113   jni::alias_ref<jstring> name,
114   jint args,
115   jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
116   jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
117 ) {
118   std::string cName = name->toStdString();
119 
120   methodsMetadata.try_emplace(
121     cName,
122     longLivedObjectCollection_,
123     cName,
124     args,
125     true,
126     jni::make_local(expectedArgTypes),
127     jni::make_global(body)
128   );
129 }
130 
131 void JavaScriptModuleObject::registerProperty(
132   jni::alias_ref<jstring> name,
133   jni::alias_ref<ExpectedType> expectedArgType,
134   jni::alias_ref<JNIFunctionBody::javaobject> getter,
135   jni::alias_ref<JNIFunctionBody::javaobject> setter
136 ) {
137   auto cName = name->toStdString();
138 
139   auto getterMetadata = MethodMetadata(
140     longLivedObjectCollection_,
141     cName,
142     0,
143     false,
144     std::vector<std::unique_ptr<AnyType>>(),
145     jni::make_global(getter)
146   );
147 
148   auto types = std::vector<std::unique_ptr<AnyType>>();
149   types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType)));
150   auto setterMetadata = MethodMetadata(
151     longLivedObjectCollection_,
152     cName,
153     1,
154     false,
155     std::move(types),
156     jni::make_global(setter)
157   );
158 
159   auto functions = std::make_pair(
160     std::move(getterMetadata),
161     std::move(setterMetadata)
162   );
163 
164   properties.insert({cName, std::move(functions)});
165 }
166 
167 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis)
168   : javaPart_(jni::make_global(jThis)) {
169   longLivedObjectCollection_ = std::make_shared<react::LongLivedObjectCollection>();
170 }
171 } // namespace expo
172