1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2 
3 #pragma once
4 
5 #include <fbjni/fbjni.h>
6 #include <jsi/jsi.h>
7 #include <react/jni/ReadableNativeArray.h>
8 #include <jni/JCallback.h>
9 
10 #include <unordered_map>
11 
12 #include "MethodMetadata.h"
13 #include "JNIFunctionBody.h"
14 
15 namespace jni = facebook::jni;
16 namespace jsi = facebook::jsi;
17 namespace react = facebook::react;
18 
19 namespace expo {
20 class JSIInteropModuleRegistry;
21 
22 /**
23  * A CPP part of the module.
24  *
25  * Right now objects of this class are stored by the ModuleHolder to ensure they will live
26  * as long as the RN context.
27  */
28 class JavaScriptModuleObject : public jni::HybridClass<JavaScriptModuleObject> {
29 public:
30   static auto constexpr
31     kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptModuleObject;";
32   static auto constexpr TAG = "JavaScriptModuleObject";
33 
34   static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
35 
36   static void registerNatives();
37 
38   /**
39    * Pointer to the module registry interop.
40    */
41   JSIInteropModuleRegistry *jsiInteropModuleRegistry;
42 
43   /**
44    * Returns a cached instance of jsi::Object representing this module.
45    * @param runtime
46    * @return Wrapped instance of JavaScriptModuleObject::HostObject
47    */
48   std::shared_ptr<jsi::Object> getJSIObject(jsi::Runtime &runtime);
49 
50   /**
51    * Exports constants that will be assigned to the underlying HostObject.
52    */
53   void exportConstants(jni::alias_ref<react::NativeMap::javaobject> constants);
54 
55   /**
56    * Registers a sync function.
57    * That function can be called via the `JavaScriptModuleObject.callSyncMethod` method.
58    */
59   void registerSyncFunction(
60     jni::alias_ref<jstring> name,
61     jint args,
62     jni::alias_ref<jni::JArrayInt> desiredTypes,
63     jni::alias_ref<JNIFunctionBody::javaobject> body
64   );
65 
66   /**
67    * Registers a async function.
68    * That function can be called via the `JavaScriptModuleObject.callAsyncMethod` method.
69    */
70   void registerAsyncFunction(
71     jni::alias_ref<jstring> name,
72     jint args,
73     jni::alias_ref<jni::JArrayInt> desiredTypes,
74     jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
75   );
76 
77   /**
78    * Registers a property
79    * @param name of the property
80    * @param desiredType of the setter argument
81    * @param getter body for the get method - can be nullptr
82    * @param setter body for the set method - can be nullptr
83    */
84   void registerProperty(
85     jni::alias_ref<jstring> name,
86     jint desiredType,
87     jni::alias_ref<JNIFunctionBody::javaobject> getter,
88     jni::alias_ref<JNIFunctionBody::javaobject> setter
89   );
90 
91   /**
92    * An inner class of the `JavaScriptModuleObject` that is exported to the JS.
93    * It's an additional communication layer between JS and Kotlin.
94    * So the high-level view on accessing the exported function will look like this:
95    * `JS` --get function--> `JavaScriptModuleObject::HostObject` --access module metadata--> `JavaScriptModuleObject`
96    *  --create JSI function--> `MethodMetadata`
97    *
98    * This abstraction wasn't necessary. However, it makes the management of ownership much easier -
99    * `JavaScriptModuleObject` is held by the ModuleHolder and `JavaScriptModuleObject::HostObject` is stored in the JS runtime.
100    * Without this distinction the `JavaScriptModuleObject` would have to turn into `HostObject` and `HybridObject` at the same time.
101    */
102   class HostObject : public jsi::HostObject {
103   public:
104     HostObject(JavaScriptModuleObject *);
105 
106     jsi::Value get(jsi::Runtime &, const jsi::PropNameID &name) override;
107 
108     void set(jsi::Runtime &, const jsi::PropNameID &name, const jsi::Value &value) override;
109 
110     std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;
111 
112   private:
113     JavaScriptModuleObject *jsModule;
114   };
115 
116 private:
117   friend HybridBase;
118   /**
119    * A reference to the `JavaScriptModuleObject::HostObject`.
120    * Simple we cached that value to return the same object each time.
121    */
122   std::shared_ptr<jsi::Object> jsiObject = nullptr;
123   jni::global_ref<JavaScriptModuleObject::javaobject> javaPart_;
124 
125   /**
126    * Metadata map that stores information about all available methods on this module.
127    */
128   std::unordered_map<std::string, MethodMetadata> methodsMetadata;
129 
130   /**
131    * A constants map.
132    */
133   std::unordered_map<std::string, folly::dynamic> constants;
134 
135   /**
136    * A registry of properties
137    * The first MethodMetadata points to the getter and the second one to the setter.
138    */
139   std::map<std::string, std::pair<MethodMetadata, MethodMetadata>> properties;
140 
141   explicit JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis)
142     : javaPart_(jni::make_global(jThis)) {}
143 };
144 } // namespace expo
145