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 "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>> desiredTypes, 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>> desiredTypes, 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 jint desiredType, 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