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                  });
40 }
41 
42 std::shared_ptr<jsi::Object> JavaScriptModuleObject::getJSIObject(jsi::Runtime &runtime) {
43   if (jsiObject == nullptr) {
44     auto hostObject = std::make_shared<JavaScriptModuleObject::HostObject>(this);
45     jsiObject = std::make_shared<jsi::Object>(
46       jsi::Object::createFromHostObject(runtime, hostObject));
47   }
48 
49   return jsiObject;
50 }
51 
52 void JavaScriptModuleObject::exportConstants(
53   jni::alias_ref<react::NativeMap::javaobject> constants
54 ) {
55   auto dynamic = constants->cthis()->consume();
56   assert(dynamic.isObject());
57 
58   for (const auto &[key, value]: dynamic.items()) {
59     this->constants[key.asString()] = value;
60   }
61 }
62 
63 void JavaScriptModuleObject::registerSyncFunction(
64   jni::alias_ref<jstring> name,
65   jint args,
66   jni::alias_ref<jni::JArrayInt> desiredTypes,
67   jni::alias_ref<JNIFunctionBody::javaobject> body
68 ) {
69   std::string cName = name->toStdString();
70   std::unique_ptr<int[]> types = desiredTypes->getRegion(0, args);
71 
72   methodsMetadata.try_emplace(
73     cName,
74     cName,
75     args,
76     false,
77     std::move(types),
78     jni::make_global(body)
79   );
80 }
81 
82 void JavaScriptModuleObject::registerAsyncFunction(
83   jni::alias_ref<jstring> name,
84   jint args,
85   jni::alias_ref<jni::JArrayInt> desiredTypes,
86   jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
87 ) {
88   auto cName = name->toStdString();
89   std::unique_ptr<int[]> types = desiredTypes->getRegion(0, args);
90 
91   methodsMetadata.try_emplace(
92     cName,
93     cName,
94     args,
95     true,
96     std::move(types),
97     jni::make_global(body)
98   );
99 }
100 
101 JavaScriptModuleObject::HostObject::HostObject(
102   JavaScriptModuleObject *jsModule) : jsModule(jsModule) {}
103 
104 jsi::Value JavaScriptModuleObject::HostObject::get(jsi::Runtime &runtime,
105                                                    const jsi::PropNameID &name) {
106   auto cName = name.utf8(runtime);
107 
108   auto constantsRecord = jsModule->constants.find(cName);
109   if (constantsRecord != jsModule->constants.end()) {
110     auto dynamic = constantsRecord->second;
111     return jsi::valueFromDynamic(runtime, dynamic);
112   }
113 
114   auto metadataRecord = jsModule->methodsMetadata.find(cName);
115   if (metadataRecord == jsModule->methodsMetadata.end()) {
116     return jsi::Value::undefined();
117   }
118   auto &metadata = metadataRecord->second;
119   return jsi::Value(runtime, *metadata.toJSFunction(runtime, jsModule->jsiInteropModuleRegistry));
120 }
121 
122 void JavaScriptModuleObject::HostObject::set(
123   jsi::Runtime &runtime,
124   const jsi::PropNameID &name,
125   const jsi::Value &value
126 ) {
127   throw jsi::JSError(
128     runtime,
129     "RuntimeError: Cannot override the host object for expo module '" + name.utf8(runtime) + "'"
130   );
131 }
132 
133 std::vector<jsi::PropNameID> JavaScriptModuleObject::HostObject::getPropertyNames(
134   jsi::Runtime &rt
135 ) {
136   auto &metadata = jsModule->methodsMetadata;
137   std::vector<jsi::PropNameID> result;
138   std::transform(
139     metadata.begin(),
140     metadata.end(),
141     std::back_inserter(result),
142     [&rt](const auto &kv) {
143       return jsi::PropNameID::forUtf8(rt, kv.first);
144     }
145   );
146 
147   auto &constants = jsModule->constants;
148   std::transform(
149     constants.begin(),
150     constants.end(),
151     std::back_inserter(result),
152     [&rt](const auto &kv) {
153       return jsi::PropNameID::forUtf8(rt, kv.first);
154     }
155   );
156 
157   return result;
158 }
159 } // namespace expo
160