// Copyright © 2021-present 650 Industries, Inc. (aka Expo) #pragma once #include "JSIObjectWrapper.h" #include "JSITypeConverter.h" #include "JavaScriptRuntime.h" #include "WeakRuntimeHolder.h" #include #include #include namespace jni = facebook::jni; namespace jsi = facebook::jsi; namespace expo { class JavaScriptValue; /** * Represents any JavaScript object. Its purpose is to exposes `jsi::Object` API back to Kotlin. */ class JavaScriptObject : public jni::HybridClass, JSIObjectWrapper { public: static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptObject;"; static auto constexpr TAG = "JavaScriptObject"; static void registerNatives(); JavaScriptObject( std::weak_ptr runtime, std::shared_ptr jsObject ); JavaScriptObject( WeakRuntimeHolder runtime, std::shared_ptr jsObject ); std::shared_ptr get() override; /** * @return a bool whether the object has a property with the given name */ bool hasProperty(const std::string &name); /** * @return the property of the object with the given name. * If the name isn't a property on the object, returns the `jsi::Value::undefined` value. */ jsi::Value getProperty(const std::string &name); /** * @return a vector consisting of all enumerable property names in the object and its prototype chain. */ std::vector getPropertyNames(); void setProperty(const std::string &name, jsi::Value value); protected: WeakRuntimeHolder runtimeHolder; std::shared_ptr jsObject; private: friend HybridBase; bool jniHasProperty(jni::alias_ref name); jni::local_ref::javaobject> jniGetProperty( jni::alias_ref name ); jni::local_ref> jniGetPropertyNames(); /** * Unsets property with the given name. */ void unsetProperty(jni::alias_ref name); /** * A template to generate different versions of the `setProperty` method based on the `jsi_type_converter` trait. * Those generated methods will be exported and visible in the Kotlin codebase. * On the other hand, we could just make one function that would take a generic Java Object, * but then we would have to decide what to do with it and how to convert it to jsi::Value * in cpp. That would be expensive. So it's easier to ensure that * we call the correct version of `setProperty` in the Kotlin code. * * This template will work only if the jsi_type_converter exists for a given type. */ template< class T, typename = std::enable_if_t> > void setProperty(jni::alias_ref name, T value) { jsi::Runtime &jsRuntime = runtimeHolder.getJSRuntime(); auto cName = name->toStdString(); jsObject->setProperty( jsRuntime, cName.c_str(), jsi_type_converter::convert(jsRuntime, value) ); } template< class T, typename = std::enable_if_t> > void defineProperty(jni::alias_ref name, T value, int options) { jsi::Runtime &jsRuntime = runtimeHolder.getJSRuntime(); auto cName = name->toStdString(); jsi::Object global = jsRuntime.global(); jsi::Object objectClass = global.getPropertyAsObject(jsRuntime, "Object"); jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction( jsRuntime, "defineProperty" ); jsi::Object descriptor = preparePropertyDescriptor(jsRuntime, options); descriptor.setProperty(jsRuntime, "value", jsi_type_converter::convert(jsRuntime, value)); definePropertyFunction.callWithThis(jsRuntime, objectClass, { jsi::Value(jsRuntime, *jsObject), jsi::String::createFromUtf8(jsRuntime, cName), std::move(descriptor) }); } static jsi::Object preparePropertyDescriptor(jsi::Runtime &jsRuntime, int options); }; } // namespace expo