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("registerSyncFunction",
35                                     JavaScriptModuleObject::registerSyncFunction),
36                    makeNativeMethod("registerAsyncFunction",
37                                     JavaScriptModuleObject::registerAsyncFunction),
38                  });
39 }
40 
41 std::shared_ptr<jsi::Object> JavaScriptModuleObject::getJSIObject(jsi::Runtime &runtime) {
42   if (jsiObject == nullptr) {
43     auto hostObject = std::make_shared<JavaScriptModuleObject::HostObject>(this);
44     jsiObject = std::make_shared<jsi::Object>(
45       jsi::Object::createFromHostObject(runtime, hostObject));
46   }
47 
48   return jsiObject;
49 }
50 
51 void JavaScriptModuleObject::registerSyncFunction(
52   jni::alias_ref<jstring> name,
53   jint args,
54   jni::alias_ref<JNIFunctionBody::javaobject> body
55 ) {
56   auto cName = name->toStdString();
57   methodsMetadata.try_emplace(cName, cName, args, false, jni::make_global(body));
58 }
59 
60 void JavaScriptModuleObject::registerAsyncFunction(
61   jni::alias_ref<jstring> name,
62   jint args,
63   jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
64 ) {
65   auto cName = name->toStdString();
66   methodsMetadata.try_emplace(cName, cName, args, true, jni::make_global(body));
67 }
68 
69 JavaScriptModuleObject::HostObject::HostObject(
70   JavaScriptModuleObject *jsModule) : jsModule(jsModule) {}
71 
72 jsi::Value JavaScriptModuleObject::HostObject::get(jsi::Runtime &runtime,
73                                                    const jsi::PropNameID &name) {
74   auto cName = name.utf8(runtime);
75   auto metadataRecord = jsModule->methodsMetadata.find(cName);
76   if (metadataRecord == jsModule->methodsMetadata.end()) {
77     return jsi::Value::undefined();
78   }
79   auto &metadata = metadataRecord->second;
80   return jsi::Value(runtime, *metadata.toJSFunction(runtime, jsModule->jsiInteropModuleRegistry));
81 }
82 
83 void JavaScriptModuleObject::HostObject::set(
84   jsi::Runtime &runtime,
85   const jsi::PropNameID &name,
86   const jsi::Value &value
87 ) {
88   throw jsi::JSError(
89     runtime,
90     "RuntimeError: Cannot override the host object for expo module '" + name.utf8(runtime) + "'"
91   );
92 }
93 
94 std::vector<jsi::PropNameID> JavaScriptModuleObject::HostObject::getPropertyNames(
95   jsi::Runtime &rt
96 ) {
97   auto &metadata = jsModule->methodsMetadata;
98   std::vector<jsi::PropNameID> result;
99   std::transform(
100     metadata.begin(),
101     metadata.end(),
102     std::back_inserter(result),
103     [&rt](const auto &kv) {
104       return jsi::PropNameID::forUtf8(rt, kv.first);
105     }
106   );
107 
108   return result;
109 }
110 } // namespace expo
111