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