1a416e6dbSŁukasz Kosmaty // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2a416e6dbSŁukasz Kosmaty 
3a416e6dbSŁukasz Kosmaty #pragma once
4a416e6dbSŁukasz Kosmaty 
514c0f05dSŁukasz Kosmaty #include "JSIObjectWrapper.h"
614c0f05dSŁukasz Kosmaty #include "JSITypeConverter.h"
714c0f05dSŁukasz Kosmaty #include "JavaScriptRuntime.h"
8256b5942SŁukasz Kosmaty #include "WeakRuntimeHolder.h"
97cfebc52SŁukasz Kosmaty #include "JNIFunctionBody.h"
1029e8b6f8SŁukasz Kosmaty #include "JNIDeallocator.h"
11*e1f25825SŁukasz Kosmaty #include "JSIUtils.h"
1214c0f05dSŁukasz Kosmaty 
13a416e6dbSŁukasz Kosmaty #include <fbjni/fbjni.h>
14a416e6dbSŁukasz Kosmaty #include <jsi/jsi.h>
15a416e6dbSŁukasz Kosmaty 
16a416e6dbSŁukasz Kosmaty #include <memory>
17a416e6dbSŁukasz Kosmaty 
18a416e6dbSŁukasz Kosmaty namespace jni = facebook::jni;
19a416e6dbSŁukasz Kosmaty namespace jsi = facebook::jsi;
20a416e6dbSŁukasz Kosmaty 
21a416e6dbSŁukasz Kosmaty namespace expo {
22a416e6dbSŁukasz Kosmaty class JavaScriptValue;
23a416e6dbSŁukasz Kosmaty 
24879827bbSŁukasz Kosmaty class JavaScriptFunction;
25879827bbSŁukasz Kosmaty 
26a416e6dbSŁukasz Kosmaty /**
27a416e6dbSŁukasz Kosmaty  * Represents any JavaScript object. Its purpose is to exposes `jsi::Object` API back to Kotlin.
28a416e6dbSŁukasz Kosmaty  */
2929e8b6f8SŁukasz Kosmaty class JavaScriptObject : public jni::HybridClass<JavaScriptObject, Destructible>, JSIObjectWrapper {
30a416e6dbSŁukasz Kosmaty public:
31a416e6dbSŁukasz Kosmaty   static auto constexpr
32a416e6dbSŁukasz Kosmaty     kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptObject;";
33a416e6dbSŁukasz Kosmaty   static auto constexpr TAG = "JavaScriptObject";
34a416e6dbSŁukasz Kosmaty 
35a416e6dbSŁukasz Kosmaty   static void registerNatives();
36a416e6dbSŁukasz Kosmaty 
3729e8b6f8SŁukasz Kosmaty   static jni::local_ref<JavaScriptObject::javaobject> newInstance(
3829e8b6f8SŁukasz Kosmaty     JSIInteropModuleRegistry *jsiInteropModuleRegistry,
3929e8b6f8SŁukasz Kosmaty     std::weak_ptr<JavaScriptRuntime> runtime,
4029e8b6f8SŁukasz Kosmaty     std::shared_ptr<jsi::Object> jsObject
4129e8b6f8SŁukasz Kosmaty   );
4229e8b6f8SŁukasz Kosmaty 
43a416e6dbSŁukasz Kosmaty   JavaScriptObject(
44a416e6dbSŁukasz Kosmaty     std::weak_ptr<JavaScriptRuntime> runtime,
45a416e6dbSŁukasz Kosmaty     std::shared_ptr<jsi::Object> jsObject
46a416e6dbSŁukasz Kosmaty   );
47a416e6dbSŁukasz Kosmaty 
48256b5942SŁukasz Kosmaty   JavaScriptObject(
49256b5942SŁukasz Kosmaty     WeakRuntimeHolder runtime,
50256b5942SŁukasz Kosmaty     std::shared_ptr<jsi::Object> jsObject
51256b5942SŁukasz Kosmaty   );
52256b5942SŁukasz Kosmaty 
5314c0f05dSŁukasz Kosmaty   std::shared_ptr<jsi::Object> get() override;
5414c0f05dSŁukasz Kosmaty 
55a416e6dbSŁukasz Kosmaty   /**
56a416e6dbSŁukasz Kosmaty    * @return a bool whether the object has a property with the given name
57a416e6dbSŁukasz Kosmaty    */
58a416e6dbSŁukasz Kosmaty   bool hasProperty(const std::string &name);
59a416e6dbSŁukasz Kosmaty 
60a416e6dbSŁukasz Kosmaty   /**
61a416e6dbSŁukasz Kosmaty    * @return the property of the object with the given name.
62a416e6dbSŁukasz Kosmaty    * If the name isn't a property on the object, returns the `jsi::Value::undefined` value.
63a416e6dbSŁukasz Kosmaty    */
64a416e6dbSŁukasz Kosmaty   jsi::Value getProperty(const std::string &name);
65a416e6dbSŁukasz Kosmaty 
66a416e6dbSŁukasz Kosmaty   /**
67a416e6dbSŁukasz Kosmaty    * @return a vector consisting of all enumerable property names in the object and its prototype chain.
68a416e6dbSŁukasz Kosmaty    */
69a416e6dbSŁukasz Kosmaty   std::vector<std::string> getPropertyNames();
70a416e6dbSŁukasz Kosmaty 
7114c0f05dSŁukasz Kosmaty   void setProperty(const std::string &name, jsi::Value value);
7214c0f05dSŁukasz Kosmaty 
73dedc0ffdSŁukasz Kosmaty   static jsi::Object preparePropertyDescriptor(jsi::Runtime &jsRuntime, int options);
74dedc0ffdSŁukasz Kosmaty 
757cfebc52SŁukasz Kosmaty   void defineNativeDeallocator(
767cfebc52SŁukasz Kosmaty     jni::alias_ref<JNIFunctionBody::javaobject> deallocator
777cfebc52SŁukasz Kosmaty   );
787cfebc52SŁukasz Kosmaty 
7905c5e37dSŁukasz Kosmaty protected:
80256b5942SŁukasz Kosmaty   WeakRuntimeHolder runtimeHolder;
81a416e6dbSŁukasz Kosmaty   std::shared_ptr<jsi::Object> jsObject;
82a416e6dbSŁukasz Kosmaty 
8305c5e37dSŁukasz Kosmaty private:
8405c5e37dSŁukasz Kosmaty   friend HybridBase;
8505c5e37dSŁukasz Kosmaty 
86a416e6dbSŁukasz Kosmaty   bool jniHasProperty(jni::alias_ref<jstring> name);
87a416e6dbSŁukasz Kosmaty 
8829e8b6f8SŁukasz Kosmaty   jni::local_ref<jni::HybridClass<JavaScriptValue, Destructible>::javaobject> jniGetProperty(
89a416e6dbSŁukasz Kosmaty     jni::alias_ref<jstring> name
90a416e6dbSŁukasz Kosmaty   );
91a416e6dbSŁukasz Kosmaty 
92a416e6dbSŁukasz Kosmaty   jni::local_ref<jni::JArrayClass<jstring>> jniGetPropertyNames();
9314c0f05dSŁukasz Kosmaty 
9429e8b6f8SŁukasz Kosmaty   jni::local_ref<jni::HybridClass<JavaScriptFunction, Destructible>::javaobject> jniAsFunction();
95879827bbSŁukasz Kosmaty 
9614c0f05dSŁukasz Kosmaty   /**
9714c0f05dSŁukasz Kosmaty    * Unsets property with the given name.
9814c0f05dSŁukasz Kosmaty    */
9914c0f05dSŁukasz Kosmaty   void unsetProperty(jni::alias_ref<jstring> name);
10014c0f05dSŁukasz Kosmaty 
10114c0f05dSŁukasz Kosmaty   /**
10214c0f05dSŁukasz Kosmaty    * A template to generate different versions of the `setProperty` method based on the `jsi_type_converter` trait.
10314c0f05dSŁukasz Kosmaty    * Those generated methods will be exported and visible in the Kotlin codebase.
10414c0f05dSŁukasz Kosmaty    * On the other hand, we could just make one function that would take a generic Java Object,
10514c0f05dSŁukasz Kosmaty    * but then we would have to decide what to do with it and how to convert it to jsi::Value
10614c0f05dSŁukasz Kosmaty    * in cpp. That would be expensive. So it's easier to ensure that
10714c0f05dSŁukasz Kosmaty    * we call the correct version of `setProperty` in the Kotlin code.
10814c0f05dSŁukasz Kosmaty    *
10914c0f05dSŁukasz Kosmaty    * This template will work only if the jsi_type_converter exists for a given type.
11014c0f05dSŁukasz Kosmaty    */
11114c0f05dSŁukasz Kosmaty   template<
11214c0f05dSŁukasz Kosmaty     class T,
11314c0f05dSŁukasz Kosmaty     typename = std::enable_if_t<is_jsi_type_converter_defined<T>>
11414c0f05dSŁukasz Kosmaty   >
setProperty(jni::alias_ref<jstring> name,T value)11514c0f05dSŁukasz Kosmaty   void setProperty(jni::alias_ref<jstring> name, T value) {
116256b5942SŁukasz Kosmaty     jsi::Runtime &jsRuntime = runtimeHolder.getJSRuntime();
11714c0f05dSŁukasz Kosmaty 
118256b5942SŁukasz Kosmaty     auto cName = name->toStdString();
11914c0f05dSŁukasz Kosmaty     jsObject->setProperty(
120256b5942SŁukasz Kosmaty       jsRuntime,
12114c0f05dSŁukasz Kosmaty       cName.c_str(),
122256b5942SŁukasz Kosmaty       jsi_type_converter<T>::convert(jsRuntime, value)
12314c0f05dSŁukasz Kosmaty     );
12414c0f05dSŁukasz Kosmaty   }
12564f4fd05SŁukasz Kosmaty 
12664f4fd05SŁukasz Kosmaty   template<
12764f4fd05SŁukasz Kosmaty     class T,
12864f4fd05SŁukasz Kosmaty     typename = std::enable_if_t<is_jsi_type_converter_defined<T>>
12964f4fd05SŁukasz Kosmaty   >
defineProperty(jni::alias_ref<jstring> name,T value,int options)13064f4fd05SŁukasz Kosmaty   void defineProperty(jni::alias_ref<jstring> name, T value, int options) {
131256b5942SŁukasz Kosmaty     jsi::Runtime &jsRuntime = runtimeHolder.getJSRuntime();
13264f4fd05SŁukasz Kosmaty 
13364f4fd05SŁukasz Kosmaty     auto cName = name->toStdString();
13464f4fd05SŁukasz Kosmaty     jsi::Object descriptor = preparePropertyDescriptor(jsRuntime, options);
13564f4fd05SŁukasz Kosmaty     descriptor.setProperty(jsRuntime, "value", jsi_type_converter<T>::convert(jsRuntime, value));
136*e1f25825SŁukasz Kosmaty     common::definePropertyOnJSIObject(jsRuntime, jsObject.get(), cName.c_str(), std::move(descriptor));
13764f4fd05SŁukasz Kosmaty   }
138a416e6dbSŁukasz Kosmaty };
139a416e6dbSŁukasz Kosmaty } // namespace expo
140