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(jni::alias_ref<react::NativeMap::javaobject>
53   constants) {
54   auto dynamic = constants->cthis()->consume();
55   assert(dynamic.isObject());
56 
57   for (const auto &[key, value]: dynamic.items()) {
58     this->constants[key.asString()] = value;
59   }
60 }
61 
62 void JavaScriptModuleObject::registerSyncFunction(
63   jni::alias_ref<jstring> name,
64   jint args,
65   jni::alias_ref<JNIFunctionBody::javaobject> body
66 ) {
67   auto cName = name->toStdString();
68   methodsMetadata.try_emplace(cName, cName, args, false, jni::make_global(body));
69 }
70 
71 void JavaScriptModuleObject::registerAsyncFunction(
72   jni::alias_ref<jstring> name,
73   jint args,
74   jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
75 ) {
76   auto cName = name->toStdString();
77   methodsMetadata.try_emplace(cName, cName, args, true, jni::make_global(body));
78 }
79 
80 JavaScriptModuleObject::HostObject::HostObject(
81   JavaScriptModuleObject *jsModule) : jsModule(jsModule) {}
82 
83 jsi::Value JavaScriptModuleObject::HostObject::get(jsi::Runtime &runtime,
84                                                    const jsi::PropNameID &name) {
85   auto cName = name.utf8(runtime);
86 
87   auto constantsRecord = jsModule->constants.find(cName);
88   if (constantsRecord != jsModule->constants.end()) {
89     auto dynamic = constantsRecord->second;
90     return jsi::valueFromDynamic(runtime, dynamic);
91   }
92 
93   auto metadataRecord = jsModule->methodsMetadata.find(cName);
94   if (metadataRecord == jsModule->methodsMetadata.end()) {
95     return jsi::Value::undefined();
96   }
97   auto &metadata = metadataRecord->second;
98   return jsi::Value(runtime, *metadata.toJSFunction(runtime, jsModule->jsiInteropModuleRegistry));
99 }
100 
101 void JavaScriptModuleObject::HostObject::set(
102   jsi::Runtime &runtime,
103   const jsi::PropNameID &name,
104   const jsi::Value &value
105 ) {
106   throw jsi::JSError(
107     runtime,
108     "RuntimeError: Cannot override the host object for expo module '" + name.utf8(runtime) + "'"
109   );
110 }
111 
112 std::vector<jsi::PropNameID> JavaScriptModuleObject::HostObject::getPropertyNames(
113   jsi::Runtime &rt
114 ) {
115   auto &metadata = jsModule->methodsMetadata;
116   std::vector<jsi::PropNameID> result;
117   std::transform(
118     metadata.begin(),
119     metadata.end(),
120     std::back_inserter(result),
121     [&rt](const auto &kv) {
122       return jsi::PropNameID::forUtf8(rt, kv.first);
123     }
124   );
125 
126   auto &constants = jsModule->constants;
127   std::transform(
128     constants.begin(),
129     constants.end(),
130     std::back_inserter(result),
131     [&rt](const auto &kv) {
132       return jsi::PropNameID::forUtf8(rt, kv.first);
133     }
134   );
135 
136   return result;
137 }
138 } // namespace expo
139