Initial commit

This commit is contained in:
marianesaldana
2026-05-23 08:59:34 -06:00
commit 80dbd947e5
36446 changed files with 3729147 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/RuntimeExecutor.h>
namespace expo {
class BridgelessJSCallInvoker : public react::CallInvoker {
public:
explicit BridgelessJSCallInvoker(react::RuntimeExecutor runtimeExecutor) : runtimeExecutor_(std::move(runtimeExecutor)) {}
#if REACT_NATIVE_TARGET_VERSION >= 75
void invokeAsync(react::CallFunc &&func) noexcept override {
runtimeExecutor_([func = std::move(func)](jsi::Runtime &runtime) { func(runtime); });
}
void invokeSync(react::CallFunc &&func) override {
throw std::runtime_error("Synchronous native -> JS calls are currently not supported.");
}
#else
void invokeAsync(std::function<void()> &&func) noexcept override {
runtimeExecutor_([func = std::move(func)](jsi::Runtime &runtime) { func(); });
}
void invokeSync(std::function<void()> &&func) override {
throw std::runtime_error("Synchronous native -> JS calls are currently not supported.");
}
#endif
private:
react::RuntimeExecutor runtimeExecutor_;
}; // class BridgelessJSCallInvoker
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,299 @@
#include "JSIUtils.h"
#include "EventEmitter.h"
#include "LazyObject.h"
namespace expo::EventEmitter {
#pragma mark - Listeners
void Listeners::add(jsi::Runtime &runtime, std::string eventName, const jsi::Function &listener) noexcept {
listenersMap[eventName].emplace_back(runtime, listener);
}
void Listeners::remove(jsi::Runtime &runtime, std::string eventName, const jsi::Function &listener) noexcept {
if (!listenersMap.contains(eventName)) {
return;
}
jsi::Value listenerValue(runtime, listener);
listenersMap[eventName].remove_if([&](const jsi::Value &item) {
return jsi::Value::strictEquals(runtime, listenerValue, item);
});
}
void Listeners::removeAll(std::string eventName) noexcept {
if (listenersMap.contains(eventName)) {
listenersMap[eventName].clear();
}
}
void Listeners::clear() noexcept {
listenersMap.clear();
}
size_t Listeners::listenersCount(std::string eventName) noexcept {
if (!listenersMap.contains(eventName)) {
return 0;
}
return listenersMap[eventName].size();
}
void Listeners::call(jsi::Runtime &runtime, std::string eventName, const jsi::Object &thisObject, const jsi::Value *args, size_t count) noexcept {
if (!listenersMap.contains(eventName)) {
return;
}
ListenersList &listenersList = listenersMap[eventName];
size_t listSize = listenersList.size();
if (listSize == 0) {
// Nothing to call.
return;
}
if (listSize == 1) {
// The most common scenario just call the only listener.
listenersList
.front()
.asObject(runtime)
.asFunction(runtime)
.callWithThis(runtime, thisObject, args, count);
return;
}
// When there are more than one listener, we copy the list to a vector as the list may be modified during the loop.
std::vector<jsi::Function> listenersVector;
listenersVector.reserve(listSize);
// Copy listeners to vector already as jsi::Function so we don't additionally copy jsi::Value
for (const jsi::Value &listener : listenersList) {
listenersVector.push_back(listener.asObject(runtime).asFunction(runtime));
}
// Call listeners from the vector. The list can be modified by the listeners but it will not affect this loop,
// i.e. newly added listeners will not be called and removed listeners will be called one last time.
// This is compliant with the EventEmitter in Node.js
for (const jsi::Function &listener : listenersVector) {
listener.callWithThis(runtime, thisObject, args, count);
}
}
#pragma mark - NativeState
NativeState::NativeState() : jsi::NativeState() {}
NativeState::~NativeState() {
listeners.clear();
}
NativeState::Shared NativeState::get(jsi::Runtime &runtime, const jsi::Object &object, bool createIfMissing) {
if (object.hasNativeState<NativeState>(runtime)) {
return object.getNativeState<NativeState>(runtime);
}
if (createIfMissing) {
NativeState::Shared state = std::make_shared<NativeState>();
object.setNativeState(runtime, state);
return state;
}
return nullptr;
}
#pragma mark - Utils
void callObservingFunction(jsi::Runtime &runtime, const jsi::Object &object, const char* functionName, std::string eventName) {
jsi::Value fnValue = object.getProperty(runtime, functionName);
if (!fnValue.isObject()) {
// Skip it if there is no observing function.
return;
}
fnValue
.getObject(runtime)
.asFunction(runtime)
.callWithThis(runtime, object, {
jsi::Value(runtime, jsi::String::createFromUtf8(runtime, eventName))
});
}
void addListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, true)) {
state->listeners.add(runtime, eventName, listener);
if (state->listeners.listenersCount(eventName) == 1) {
callObservingFunction(runtime, emitter, "startObserving", eventName);
}
}
}
void removeListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
size_t listenersCountBefore = state->listeners.listenersCount(eventName);
state->listeners.remove(runtime, eventName, listener);
if (listenersCountBefore >= 1 && state->listeners.listenersCount(eventName) == 0) {
callObservingFunction(runtime, emitter, "stopObserving", eventName);
}
}
}
void removeAllListeners(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
size_t listenersCountBefore = state->listeners.listenersCount(eventName);
state->listeners.removeAll(eventName);
if (listenersCountBefore >= 1) {
callObservingFunction(runtime, emitter, "stopObserving", eventName);
}
}
}
void emitEvent(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Value *args, size_t count) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
state->listeners.call(runtime, eventName, emitter, args, count);
}
}
size_t getListenerCount(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
return state->listeners.listenersCount(eventName);
}
return 0;
}
jsi::Value createEventSubscription(jsi::Runtime &runtime, const std::string &eventName, const jsi::Object &emitter, const jsi::Function &listener) {
jsi::Object subscription(runtime);
jsi::PropNameID removeProp = jsi::PropNameID::forAscii(runtime, "remove", 6);
std::shared_ptr<jsi::Value> emitterValue = std::make_shared<jsi::Value>(runtime, emitter);
std::shared_ptr<jsi::Value> listenerValue = std::make_shared<jsi::Value>(runtime, listener);
jsi::HostFunctionType removeSubscription = [eventName, emitterValue, listenerValue](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
jsi::Object emitter = emitterValue->getObject(runtime);
jsi::Function listener = listenerValue->getObject(runtime).getFunction(runtime);
removeListener(runtime, emitter, eventName, listener);
return jsi::Value::undefined();
};
subscription.setProperty(runtime, removeProp, jsi::Function::createFromHostFunction(runtime, removeProp, 0, removeSubscription));
return jsi::Value(runtime, subscription);
}
#pragma mark - Public API
void emitEvent(jsi::Runtime &runtime, jsi::Object &emitter, const std::string &eventName, const std::vector<jsi::Value> &arguments) {
emitEvent(runtime, emitter, eventName, arguments.data(), arguments.size());
}
jsi::Function getClass(jsi::Runtime &runtime) {
return common::getCoreObject(runtime)
.getPropertyAsFunction(runtime, "EventEmitter");
}
void installClass(jsi::Runtime &runtime) {
jsi::Function eventEmitterClass = common::createClass(runtime, "EventEmitter", [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
// To provide backwards compatibility with the old EventEmitter where the native module object was passed as an argument.
// We're checking if the argument is already an instance of the new emitter and if so, just return it without unnecessarily wrapping it.
if (count > 0) {
jsi::Object firstArg = args[0].asObject(runtime);
jsi::Function constructor = thisValue.asObject(runtime).getPropertyAsFunction(runtime, "constructor");
if (firstArg.instanceOf(runtime, constructor)) {
return jsi::Value(runtime, args[0]);
}
}
return jsi::Value(runtime, thisValue);
});
jsi::Object prototype = eventEmitterClass.getPropertyAsObject(runtime, "prototype");
jsi::HostFunctionType addListenerHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Function listener = args[1].asObject(runtime).asFunction(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// `this` might be an object that is representing a host object, in which case it's not possible to get the native state.
// For native modules we need to unwrap it to get the object used under the hood by `LazyObject` host object.
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
addListener(runtime, emitter, eventName, listener);
return createEventSubscription(runtime, eventName, emitter, listener);
};
jsi::HostFunctionType removeListenerHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Function listener = args[1].asObject(runtime).asFunction(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
removeListener(runtime, emitter, eventName, listener);
return jsi::Value::undefined();
};
jsi::HostFunctionType removeAllListenersHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
removeAllListeners(runtime, emitter, eventName);
return jsi::Value::undefined();
};
jsi::HostFunctionType emit = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
// Make a new pointer that skips the first argument which is the event name.
const jsi::Value *eventArgs = count > 1 ? &args[1] : nullptr;
emitEvent(runtime, emitter, eventName, eventArgs, count - 1);
return jsi::Value::undefined();
};
jsi::HostFunctionType listenerCountHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
return jsi::Value((int)getListenerCount(runtime, emitter, eventName));
};
// Added for compatibility with the old EventEmitter API.
jsi::HostFunctionType removeSubscriptionHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
jsi::Object subscription = args[0].asObject(runtime);
subscription.getProperty(runtime, "remove")
.asObject(runtime)
.asFunction(runtime)
.callWithThis(runtime, subscription, {});
return jsi::Value::undefined();
};
jsi::PropNameID addListenerProp = jsi::PropNameID::forAscii(runtime, "addListener", 11);
jsi::PropNameID removeListenerProp = jsi::PropNameID::forAscii(runtime, "removeListener", 14);
jsi::PropNameID removeAllListenersProp = jsi::PropNameID::forAscii(runtime, "removeAllListeners", 18);
jsi::PropNameID emitProp = jsi::PropNameID::forAscii(runtime, "emit", 4);
jsi::PropNameID listenerCountProp = jsi::PropNameID::forAscii(runtime, "listenerCount", 13);
jsi::PropNameID removeSubscriptionProp = jsi::PropNameID::forAscii(runtime, "removeSubscription", 18);
prototype.setProperty(runtime, addListenerProp, jsi::Function::createFromHostFunction(runtime, addListenerProp, 2, addListenerHost));
prototype.setProperty(runtime, removeListenerProp, jsi::Function::createFromHostFunction(runtime, removeListenerProp, 2, removeListenerHost));
prototype.setProperty(runtime, removeAllListenersProp, jsi::Function::createFromHostFunction(runtime, removeAllListenersProp, 1, removeAllListenersHost));
prototype.setProperty(runtime, emitProp, jsi::Function::createFromHostFunction(runtime, emitProp, 2, emit));
prototype.setProperty(runtime, listenerCountProp, jsi::Function::createFromHostFunction(runtime, listenerCountProp, 1, listenerCountHost));
prototype.setProperty(runtime, removeSubscriptionProp, jsi::Function::createFromHostFunction(runtime, removeSubscriptionProp, 1, removeSubscriptionHost));
common::getCoreObject(runtime)
.setProperty(runtime, "EventEmitter", eventEmitterClass);
}
} // namespace expo::EventEmitter

View File

@@ -0,0 +1,111 @@
#pragma once
#ifdef __cplusplus
#include <unordered_map>
#include <list>
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::EventEmitter {
/**
Class containing and managing listeners of the event emitter.
*/
class Listeners {
private:
friend class NativeState;
friend void addListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener);
friend void removeListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener);
friend void removeAllListeners(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName);
friend void emitEvent(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Value *args, size_t count);
friend size_t getListenerCount(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName);
/**
Type of the list containing listeners for the specific event name.
*/
using ListenersList = std::list<jsi::Value>;
/**
Type of the map where the keys are event names and the values are lists of listeners.
*/
using ListenersMap = std::unordered_map<std::string, ListenersList>;
/**
Map with the events and listeners.
*/
ListenersMap listenersMap;
/**
Adds a listener for the given event name.
*/
void add(jsi::Runtime &runtime, std::string eventName, const jsi::Function &listener) noexcept;
/**
Removes the listener for the given event name.
*/
void remove(jsi::Runtime &runtime, std::string eventName, const jsi::Function &listener) noexcept;
/**
Removes all listeners for the given event name.
*/
void removeAll(std::string eventName) noexcept;
/**
Clears the entire map of events and listeners.
*/
void clear() noexcept;
/**
Returns a number of listeners added for the given event name.
*/
size_t listenersCount(std::string eventName) noexcept;
/**
Calls listeners for the given event name, with the given `this` object and payload arguments.
*/
void call(jsi::Runtime &runtime, std::string eventName, const jsi::Object &thisObject, const jsi::Value *args, size_t count) noexcept;
};
/**
Class representing a native state of objects that emit events.
*/
class JSI_EXPORT NativeState : public jsi::NativeState {
public:
using Shared = std::shared_ptr<NativeState>;
NativeState();
virtual ~NativeState();
/**
A structure containing event listeners.
*/
Listeners listeners;
/**
Gets event emitter's native state from the given object.
If `createIfMissing` is set to `true`, the state will be automatically created.
*/
static Shared get(jsi::Runtime &runtime, const jsi::Object &object, bool createIfMissing = false);
};
/**
Emits an event with the given name and arguments to the emitter object.
Does nothing if the given object is not an instance of the EventEmitter class.
*/
void emitEvent(jsi::Runtime &runtime, jsi::Object &emitter, const std::string &eventName, const std::vector<jsi::Value> &arguments);
/**
Gets `expo.EventEmitter` class from the given runtime.
*/
jsi::Function getClass(jsi::Runtime &runtime);
/**
Installs `expo.EventEmitter` class in the given runtime.
*/
void installClass(jsi::Runtime &runtime);
} // namespace expo::EventEmitter
#endif // __cplusplus

View File

@@ -0,0 +1,146 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include <sstream>
#include "JSIUtils.h"
namespace expo::common {
jsi::Function createClass(jsi::Runtime &runtime, const char *name, ClassConstructor constructor) {
std::string nativeConstructorKey("__native_constructor__");
// Create a string buffer of the source code to evaluate.
std::stringstream source;
source << "(function " << name << "(...args) { return this." << nativeConstructorKey << "(...args); })";
std::shared_ptr<jsi::StringBuffer> sourceBuffer = std::make_shared<jsi::StringBuffer>(source.str());
// Evaluate the code and obtain returned value (the constructor function).
jsi::Object klass = runtime.evaluateJavaScript(sourceBuffer, "").asObject(runtime);
// Set the native constructor in the prototype.
jsi::Object prototype = klass.getPropertyAsObject(runtime, "prototype");
jsi::PropNameID nativeConstructorPropId = jsi::PropNameID::forAscii(runtime, nativeConstructorKey);
jsi::Function nativeConstructor = jsi::Function::createFromHostFunction(
runtime,
nativeConstructorPropId,
// The paramCount is not obligatory to match, it only affects the `length` property of the function.
0,
[constructor](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
if (constructor) {
return constructor(runtime, thisValue, args, count);
}
return jsi::Value(runtime, thisValue);
});
jsi::Object descriptor(runtime);
descriptor.setProperty(runtime, "value", jsi::Value(runtime, nativeConstructor));
defineProperty(runtime, &prototype, nativeConstructorKey.c_str(), std::move(descriptor));
return klass.asFunction(runtime);
}
jsi::Function createInheritingClass(jsi::Runtime &runtime, const char *className, jsi::Function &baseClass, ClassConstructor constructor) {
jsi::PropNameID prototypePropNameId = jsi::PropNameID::forAscii(runtime, "prototype", 9);
jsi::Object baseClassPrototype = baseClass
.getProperty(runtime, prototypePropNameId)
.asObject(runtime);
jsi::Function klass = createClass(runtime, className, constructor);
jsi::Object klassPrototype = klass.getProperty(runtime, prototypePropNameId).asObject(runtime);
klassPrototype.setProperty(runtime, "__proto__", baseClassPrototype);
return klass;
}
jsi::Object createObjectWithPrototype(jsi::Runtime &runtime, jsi::Object *prototype) {
// Get the "Object" class.
jsi::Object objectClass = runtime
.global()
.getPropertyAsObject(runtime, "Object");
// Call "Object.create(prototype)" to create an object with the given prototype without calling the constructor.
jsi::Object object = objectClass
.getPropertyAsFunction(runtime, "create")
.callWithThis(runtime, objectClass, {
jsi::Value(runtime, *prototype)
})
.asObject(runtime);
return object;
}
std::vector<jsi::PropNameID> jsiArrayToPropNameIdsVector(jsi::Runtime &runtime, const jsi::Array &array) {
size_t size = array.size(runtime);
std::vector<jsi::PropNameID> vector;
vector.reserve(size);
for (size_t i = 0; i < size; i++) {
jsi::String name = array.getValueAtIndex(runtime, i).getString(runtime);
vector.push_back(jsi::PropNameID::forString(runtime, name));
}
return vector;
}
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, const PropertyDescriptor descriptor) {
jsi::Object jsDescriptor(runtime);
// These three flags are all `false` by default, so set the property only when `true`.
if (descriptor.configurable) {
jsDescriptor.setProperty(runtime, "configurable", jsi::Value(true));
}
if (descriptor.enumerable) {
jsDescriptor.setProperty(runtime, "enumerable", jsi::Value(true));
}
if (descriptor.writable) {
jsDescriptor.setProperty(runtime, "writable", jsi::Value(true));
}
if (descriptor.get) {
jsi::PropNameID getPropName = jsi::PropNameID::forAscii(runtime, "get", 3);
jsi::Function get = jsi::Function::createFromHostFunction(
runtime,
getPropName,
0,
[getter = descriptor.get](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
return getter(runtime, thisValue.asObject(runtime));
});
jsDescriptor.setProperty(runtime, getPropName, get);
}
if (descriptor.set) {
jsi::PropNameID setPropName = jsi::PropNameID::forAscii(runtime, "set", 3);
jsi::Function set = jsi::Function::createFromHostFunction(
runtime,
setPropName,
1,
[setter = descriptor.set](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
setter(runtime, thisValue.asObject(runtime), jsi::Value(runtime, args[0]));
return jsi::Value::undefined();
});
jsDescriptor.setProperty(runtime, setPropName, set);
}
if (!descriptor.value.isUndefined()) {
jsi::PropNameID valuePropName = jsi::PropNameID::forAscii(runtime, "value", 5);
jsDescriptor.setProperty(runtime, valuePropName, descriptor.value);
}
defineProperty(runtime, object, name, std::move(jsDescriptor));
}
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, jsi::Object descriptor) {
jsi::Object global = runtime.global();
jsi::Object objectClass = global.getPropertyAsObject(runtime, "Object");
jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(runtime, "defineProperty");
// This call is basically the same as `Object.defineProperty(object, name, descriptor)` in JS
definePropertyFunction.callWithThis(runtime, objectClass, {
jsi::Value(runtime, *object),
jsi::String::createFromUtf8(runtime, name),
std::move(descriptor),
});
}
} // namespace expo::common

View File

@@ -0,0 +1,76 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::common {
#pragma mark - Helpers
/**
Gets the core Expo object, i.e. `global.expo`.
*/
inline jsi::Object getCoreObject(jsi::Runtime &runtime) {
return runtime.global().getPropertyAsObject(runtime, "expo");
}
#pragma mark - Classes
/**
Type of the native constructor of the JS classes.
*/
typedef std::function<jsi::Value(jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count)> ClassConstructor;
/**
Creates a class with the given name and native constructor.
*/
jsi::Function createClass(jsi::Runtime &runtime, const char *name, ClassConstructor constructor = nullptr);
/**
Creates a class (function) that inherits from the provided base class.
*/
jsi::Function createInheritingClass(jsi::Runtime &runtime, const char *className, jsi::Function &baseClass, ClassConstructor constructor = nullptr);
/**
Creates an object from the given prototype, without calling the constructor.
*/
jsi::Object createObjectWithPrototype(jsi::Runtime &runtime, jsi::Object *prototype);
#pragma mark - Conversions
/**
Converts `jsi::Array` to a vector with prop name ids (`std::vector<jsi::PropNameID>`).
*/
std::vector<jsi::PropNameID> jsiArrayToPropNameIdsVector(jsi::Runtime &runtime, const jsi::Array &array);
#pragma mark - Properties
/**
Represents a JS property descriptor used in the `Object.defineProperty` function.
*/
struct PropertyDescriptor {
const bool configurable = false;
const bool enumerable = false;
const bool writable = false;
const jsi::Value value = jsi::Value::undefined();
const std::function<jsi::Value(jsi::Runtime &runtime, jsi::Object thisObject)> get = 0;
const std::function<void(jsi::Runtime &runtime, jsi::Object thisObject, jsi::Value newValue)> set = 0;
}; // PropertyDescriptor
/**
Defines the property on the object with the provided descriptor options.
*/
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, const PropertyDescriptor descriptor);
/**
Calls `Object.defineProperty(object, name, descriptor)`.
*/
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, jsi::Object descriptor);
} // namespace expo::common
#endif // __cplusplus

View File

@@ -0,0 +1,57 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "JSIUtils.h"
#include "LazyObject.h"
namespace expo {
LazyObject::LazyObject(const LazyObjectInitializer initializer) : initializer(initializer) {}
LazyObject::~LazyObject() {
backedObject = nullptr;
}
jsi::Value LazyObject::get(jsi::Runtime &runtime, const jsi::PropNameID &name) {
if (!backedObject) {
if (name.utf8(runtime) == "$$typeof") {
// React Native asks for this property for some reason, we can just ignore it.
return jsi::Value::undefined();
}
initializeBackedObject(runtime);
}
return backedObject ? backedObject->getProperty(runtime, name) : jsi::Value::undefined();
}
void LazyObject::set(jsi::Runtime &runtime, const jsi::PropNameID &name, const jsi::Value &value) {
if (!backedObject) {
initializeBackedObject(runtime);
}
if (backedObject) {
backedObject->setProperty(runtime, name, value);
}
}
std::vector<jsi::PropNameID> LazyObject::getPropertyNames(jsi::Runtime &runtime) {
if (!backedObject) {
initializeBackedObject(runtime);
}
if (backedObject) {
jsi::Array propertyNames = backedObject->getPropertyNames(runtime);
return common::jsiArrayToPropNameIdsVector(runtime, propertyNames);
}
return {};
}
const jsi::Object &LazyObject::unwrapObjectIfNecessary(jsi::Runtime &runtime, const jsi::Object &object) {
if (object.isHostObject<LazyObject>(runtime)) {
LazyObject::Shared lazyObject = object.getHostObject<LazyObject>(runtime);
if (!lazyObject->backedObject) {
lazyObject->initializeBackedObject(runtime);
}
return *lazyObject->backedObject;
}
return object;
}
} // namespace expo

View File

@@ -0,0 +1,56 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
/**
A function that is responsible for initializing the backed object.
*/
typedef std::function<std::shared_ptr<jsi::Object>(jsi::Runtime &)> LazyObjectInitializer;
/**
A host object that defers the creating of the raw object until any property is accessed for the first time.
*/
class JSI_EXPORT LazyObject : public jsi::HostObject {
public:
using Shared = std::shared_ptr<LazyObject>;
LazyObject(const LazyObjectInitializer initializer);
virtual ~LazyObject();
jsi::Value get(jsi::Runtime &, const jsi::PropNameID &name) override;
void set(jsi::Runtime &, const jsi::PropNameID &name, const jsi::Value &value) override;
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;
/**
If the given object is a host object of type `LazyObject`, it returns its backed object.
Otherwise, the given object is returned back.
*/
static const jsi::Object &unwrapObjectIfNecessary(jsi::Runtime &runtime, const jsi::Object &object);
private:
const LazyObjectInitializer initializer;
std::shared_ptr<jsi::Object> backedObject;
/**
Initializes the backed object. It shouldn't be invoked more than once, so first make sure that `backedObject` is a null pointer.
*/
inline void initializeBackedObject(jsi::Runtime &runtime) {
backedObject = initializer(runtime);
}
}; // class LazyObject
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,16 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#include "JSIUtils.h"
#include "EventEmitter.h"
#include "NativeModule.h"
namespace expo::NativeModule {
void installClass(jsi::Runtime &runtime) {
jsi::Function eventEmitterClass = EventEmitter::getClass(runtime);
jsi::Function nativeModuleClass = common::createInheritingClass(runtime, "NativeModule", eventEmitterClass);
common::getCoreObject(runtime).setProperty(runtime, "NativeModule", nativeModuleClass);
}
} // namespace expo::NativeModule

View File

@@ -0,0 +1,34 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::NativeModule {
/**
Gets `expo.NativeModule` class in the given runtime.
*/
inline jsi::Function getClass(jsi::Runtime &runtime) {
return common::getCoreObject(runtime).getPropertyAsFunction(runtime, "NativeModule");
}
/**
Installs `expo.NativeModule` class in the given runtime.
*/
void installClass(jsi::Runtime &runtime);
/**
Creates a new instance of the native module.
*/
inline jsi::Object createInstance(jsi::Runtime &runtime) {
return getClass(runtime).callAsConstructor(runtime).getObject(runtime);
}
} // namespace expo::NativeModule
#endif // __cplusplus

View File

@@ -0,0 +1,19 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ObjectDeallocator.h"
#include "JSIUtils.h"
namespace expo::common {
void setDeallocator(
jsi::Runtime &runtime,
const std::shared_ptr<jsi::Object> &jsThis,
ObjectDeallocator::Block deallocatorBlock
) {
std::shared_ptr<ObjectDeallocator> objectDeallocator = std::make_shared<ObjectDeallocator>(
deallocatorBlock
);
jsThis->setNativeState(runtime, objectDeallocator);
}
} // namespace expo::common

View File

@@ -0,0 +1,36 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#import <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::common {
class JSI_EXPORT ObjectDeallocator : public jsi::NativeState {
public:
typedef std::function<void()> Block;
ObjectDeallocator(const Block deallocator) : deallocator(deallocator) {};
virtual ~ObjectDeallocator() {
deallocator();
}
const Block deallocator;
}; // class ObjectDeallocator
/**
Sets the deallocator block on a given object, which is called when the object is being deallocated.
*/
void setDeallocator(
jsi::Runtime &runtime,
const std::shared_ptr<jsi::Object> &jsThis,
ObjectDeallocator::Block deallocatorBlock
);
} // namespace expo::common
#endif

View File

@@ -0,0 +1,69 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#include "SharedObject.h"
#include "JSIUtils.h"
namespace expo::SharedObject {
#pragma mark - NativeState
NativeState::NativeState(const ObjectId objectId, const ObjectReleaser releaser)
: EventEmitter::NativeState(), objectId(objectId), releaser(releaser) {}
NativeState::~NativeState() {
releaser(objectId);
}
#pragma mark - Utils
void installBaseClass(jsi::Runtime &runtime, const ObjectReleaser releaser) {
jsi::Function baseClass = EventEmitter::getClass(runtime);
jsi::Function klass = expo::common::createInheritingClass(runtime, "SharedObject", baseClass);
jsi::Object prototype = klass.getPropertyAsObject(runtime, "prototype");
jsi::Function releaseFunction = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "release"),
1,
[releaser](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
jsi::Object thisObject = thisValue.getObject(runtime);
if (thisObject.hasNativeState<NativeState>(runtime)) {
auto nativeState = thisObject.getNativeState<NativeState>(runtime);
releaser(nativeState->objectId);
// Should we reset the native state?
thisObject.setNativeState(runtime, nullptr);
}
return jsi::Value::undefined();
});
prototype.setProperty(runtime, "release", releaseFunction);
// This property should be deprecated, but it's still used when passing as a view prop.
defineProperty(runtime, &prototype, "__expo_shared_object_id__", common::PropertyDescriptor {
.get = [](jsi::Runtime &runtime, jsi::Object thisObject) {
if (thisObject.hasNativeState<NativeState>(runtime)) {
auto nativeState = thisObject.getNativeState<NativeState>(runtime);
return jsi::Value((int)nativeState->objectId);
}
return jsi::Value(0);
}
});
common::getCoreObject(runtime)
.setProperty(runtime, "SharedObject", klass);
}
jsi::Function getBaseClass(jsi::Runtime &runtime) {
return common::getCoreObject(runtime)
.getPropertyAsFunction(runtime, "SharedObject");
}
jsi::Function createClass(jsi::Runtime &runtime, const char *className, common::ClassConstructor constructor) {
jsi::Function baseSharedObjectClass = getBaseClass(runtime);
return common::createInheritingClass(runtime, className, baseSharedObjectClass, constructor);
}
} // namespace expo::SharedObject

View File

@@ -0,0 +1,59 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <jsi/jsi.h>
#include "JSIUtils.h"
#include "ObjectDeallocator.h"
#include "EventEmitter.h"
namespace jsi = facebook::jsi;
namespace expo::SharedObject {
/**
Type of the shared object IDs.
*/
typedef long ObjectId;
/**
Defines an object releaser block of the shared object.
*/
typedef std::function<void(const ObjectId)> ObjectReleaser;
/**
Installs a base JavaScript class for all shared object with a shared release block.
*/
void installBaseClass(jsi::Runtime &runtime, const ObjectReleaser releaser);
/**
Returns the base JavaScript class for all shared objects, i.e. `global.expo.SharedObject`.
*/
jsi::Function getBaseClass(jsi::Runtime &runtime);
/**
Creates a concrete shared object class with the given name and constructor.
*/
jsi::Function createClass(jsi::Runtime &runtime, const char *className, common::ClassConstructor constructor);
/**
Class representing a native state of the shared object.
*/
class JSI_EXPORT NativeState : public EventEmitter::NativeState {
public:
const ObjectId objectId = 0;
const ObjectReleaser releaser;
/**
The default constructor that initializes a native state for the shared object with given ID.
*/
NativeState(const ObjectId objectId, const ObjectReleaser releaser);
virtual ~NativeState();
}; // class NativeState
} // namespace expo::SharedObject
#endif // __cplusplus

View File

@@ -0,0 +1,44 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <ReactCommon/CallInvoker.h>
namespace expo {
/**
* Dummy CallInvoker that invokes everything immediately.
* Used in the test environment to check the async flow.
*/
class TestingSyncJSCallInvoker : public facebook::react::CallInvoker {
public:
TestingSyncJSCallInvoker(std::shared_ptr<jsi::Runtime> runtime) : runtime(runtime) {}
#if REACT_NATIVE_TARGET_VERSION >= 75
void invokeAsync(react::CallFunc &&func) noexcept override {
func(*runtime);
}
void invokeSync(react::CallFunc &&func) override {
func(*runtime);
}
#else
void invokeAsync(std::function<void()> &&func) noexcept override {
func();
}
void invokeSync(std::function<void()> &&func) override {
func();
}
#endif
~TestingSyncJSCallInvoker() override = default;
std::shared_ptr<jsi::Runtime> runtime;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,70 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include <unordered_map>
#include "TypedArray.h"
namespace expo {
std::unordered_map<std::string, TypedArrayKind> nameToKindMap = {
{"Int8Array", TypedArrayKind::Int8Array},
{"Int16Array", TypedArrayKind::Int16Array},
{"Int32Array", TypedArrayKind::Int32Array},
{"Uint8Array", TypedArrayKind::Uint8Array},
{"Uint8ClampedArray", TypedArrayKind::Uint8ClampedArray},
{"Uint16Array", TypedArrayKind::Uint16Array},
{"Uint32Array", TypedArrayKind::Uint32Array},
{"Float32Array", TypedArrayKind::Float32Array},
{"Float64Array", TypedArrayKind::Float64Array},
{"BigInt64Array", TypedArrayKind::BigInt64Array},
{"BigUint64Array", TypedArrayKind::BigUint64Array},
};
TypedArrayKind getTypedArrayKindForName(const std::string &name) {
return nameToKindMap.at(name);
}
TypedArray::TypedArray(jsi::Runtime &runtime, const jsi::Object &obj)
: jsi::Object(jsi::Value(runtime, obj).asObject(runtime)) {}
TypedArrayKind TypedArray::getKind(jsi::Runtime &runtime) const {
auto constructorName = this->getPropertyAsObject(runtime, "constructor")
.getProperty(runtime, "name")
.asString(runtime)
.utf8(runtime);
return getTypedArrayKindForName(constructorName);
};
size_t TypedArray::byteOffset(jsi::Runtime &runtime) const {
return getProperty(runtime, "byteOffset").asNumber();
}
size_t TypedArray::byteLength(jsi::Runtime &runtime) const {
return getProperty(runtime, "byteLength").asNumber();
}
jsi::ArrayBuffer TypedArray::getBuffer(jsi::Runtime &runtime) const {
auto buffer = getProperty(runtime, "buffer");
if (buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime)) {
return buffer.asObject(runtime).getArrayBuffer(runtime);
} else {
throw std::runtime_error("no ArrayBuffer attached");
}
}
void* TypedArray::getRawPointer(jsi::Runtime &runtime) {
return reinterpret_cast<void *>(getBuffer(runtime).data(runtime) + byteOffset(runtime));
}
bool isTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj) {
jsi::Object ArrayBuffer = runtime
.global()
.getPropertyAsObject(runtime, "ArrayBuffer");
jsi::Value isViewResult = ArrayBuffer
.getPropertyAsFunction(runtime, "isView")
.callWithThis(runtime, ArrayBuffer, {jsi::Value(runtime, jsObj)});
return isViewResult.getBool();
}
} // namespace expo

View File

@@ -0,0 +1,50 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#pragma once
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
// Please keep it in-sync with the `EXTypedArrayKind` in Objective-C.
// We need to maintain two implementations to expose this enum to Swift.
enum class TypedArrayKind {
Int8Array = 1,
Int16Array = 2,
Int32Array = 3,
Uint8Array = 4,
Uint8ClampedArray = 5,
Uint16Array = 6,
Uint32Array = 7,
Float32Array = 8,
Float64Array = 9,
BigInt64Array = 10,
BigUint64Array = 11,
};
class TypedArray : public jsi::Object {
public:
TypedArray(jsi::Runtime &, const jsi::Object &);
TypedArray(TypedArray &&) = default;
TypedArray &operator=(TypedArray &&) = default;
TypedArrayKind getKind(jsi::Runtime &runtime) const;
size_t byteOffset(jsi::Runtime &runtime) const;
size_t byteLength(jsi::Runtime &runtime) const;
jsi::ArrayBuffer getBuffer(jsi::Runtime &runtime) const;
void* getRawPointer(jsi::Runtime &runtime);
};
bool isTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj);
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,19 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewComponentDescriptor.h"
namespace expo {
ExpoViewComponentDescriptor::ExpoViewComponentDescriptor(facebook::react::ComponentDescriptorParameters const &parameters)
: facebook::react::ConcreteComponentDescriptor<ExpoViewShadowNode>(parameters) {
}
facebook::react::ComponentHandle ExpoViewComponentDescriptor::getComponentHandle() const {
return reinterpret_cast<facebook::react::ComponentHandle>(getComponentName());
}
facebook::react::ComponentName ExpoViewComponentDescriptor::getComponentName() const {
return std::static_pointer_cast<std::string const>(this->flavor_)->c_str();
}
} // namespace expo

View File

@@ -0,0 +1,25 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include "ExpoViewShadowNode.h"
namespace expo {
class ExpoViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor<ExpoViewShadowNode> {
public:
using Flavor = std::shared_ptr<std::string const>;
ExpoViewComponentDescriptor(facebook::react::ComponentDescriptorParameters const &parameters);
facebook::react::ComponentHandle getComponentHandle() const override;
facebook::react::ComponentName getComponentName() const override;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,13 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewEventEmitter.h"
using namespace facebook;
namespace expo {
void ExpoViewEventEmitter::dispatch(std::string eventName, react::ValueFactory payloadFactory) const {
dispatchEvent(eventName, payloadFactory);
}
} // namespace expo

View File

@@ -0,0 +1,28 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <jsi/jsi.h>
namespace react = facebook::react;
namespace expo {
class ExpoViewEventEmitter : public facebook::react::ViewEventEmitter {
public:
using facebook::react::ViewEventEmitter::ViewEventEmitter;
using Shared = std::shared_ptr<const ExpoViewEventEmitter>;
/**
Dispatches an event to send from the native view to JavaScript.
This is basically exposing `dispatchEvent` from `facebook::react::EventEmitter` for public use.
*/
void dispatch(std::string eventName, react::ValueFactory payloadFactory) const;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,34 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewProps.h"
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/components/view/ViewProps.h>
namespace react = facebook::react;
namespace expo {
/**
Borrows the props map from the source props and applies the update given in the raw props.
*/
std::unordered_map<std::string, folly::dynamic> propsMapFromProps(const ExpoViewProps &sourceProps, const react::RawProps &rawProps) {
// Move the contents of the source props map the source props instance will not be used anymore.
std::unordered_map<std::string, folly::dynamic> propsMap = std::move(sourceProps.propsMap);
// Iterate over values in the raw props object.
// Note that it contains only updated props.
rawProps.iterateOverValues([&propsMap](react::RawPropsPropNameHash hash, const char *name, const react::RawValue &value) {
std::string propName(name);
propsMap[propName] = (folly::dynamic)value;
});
return propsMap;
}
ExpoViewProps::ExpoViewProps(const react::PropsParserContext &context,
const ExpoViewProps &sourceProps,
const react::RawProps &rawProps)
: react::ViewProps(context, sourceProps, rawProps),
propsMap(propsMapFromProps(sourceProps, rawProps)) {}
} // namespace expo

View File

@@ -0,0 +1,30 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <folly/dynamic.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
namespace expo {
class ExpoViewProps final : public facebook::react::ViewProps {
public:
ExpoViewProps() = default;
ExpoViewProps(const facebook::react::PropsParserContext &context,
const ExpoViewProps &sourceProps,
const facebook::react::RawProps &rawProps);
#pragma mark - Props
/**
A map with props stored as `folly::dynamic` objects.
*/
std::unordered_map<std::string, folly::dynamic> propsMap;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,9 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewShadowNode.h"
namespace expo {
extern const char ExpoViewComponentName[] = "ExpoFabricView";
} // namespace expo

View File

@@ -0,0 +1,24 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include "ExpoViewProps.h"
#include "ExpoViewEventEmitter.h"
#include "ExpoViewState.h"
namespace expo {
extern const char ExpoViewComponentName[];
class ExpoViewShadowNode final : public facebook::react::ConcreteViewShadowNode<ExpoViewComponentName, ExpoViewProps, ExpoViewEventEmitter, ExpoViewState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,35 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace expo {
class ExpoViewState final {
public:
ExpoViewState() {};
#ifdef ANDROID
ExpoViewState(ExpoViewState const &previousState, folly::dynamic data) {};
folly::dynamic getDynamic() const {
return {};
};
facebook::react::MapBuffer getMapBuffer() const {
return facebook::react::MapBufferBuilder::EMPTY();
};
#endif
};
} // namespace expo
#endif // __cplusplus