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