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