first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

45
node_modules/expo-modules-core/ios/JSI/EXArrayBuffer.h generated vendored Normal file
View File

@@ -0,0 +1,45 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#import <jsi/jsi.h>
#import <ExpoModulesJSI/MemoryBuffer.h>
#endif
/**
* A reference-counting wrapper around ArrayBuffer memory to manage lifetime.
* Used to ensure ArrayBuffer memory remains valid when shared between
* Swift Data objects and the underlying buffer.
*/
@interface EXArrayBufferStrongRef : NSObject
#ifdef __cplusplus
- (nonnull instancetype)initWith:(std::shared_ptr<expo::MemoryBuffer>)ptr;
#endif
/**
* Releases the strong reference to the underlying memory buffer.
* After calling this method, the memory may be deallocated.
*/
- (void)reset;
@end
/**
* Protocol defining the interface for raw ArrayBuffer implementations.
* Provides access to the underlying memory and size information.
*/
NS_SWIFT_NAME(RawArrayBuffer)
@protocol EXArrayBuffer
- (size_t)getSize;
- (nonnull void *)getUnsafeMutableRawPointer;
/**
* Returns a strong reference to the underlying memory buffer, or nil if not applicable.
* Used to prevent deallocation when the memory is shared with other objects.
*/
- (EXArrayBufferStrongRef *_Nullable)memoryStrongRef;
@end

View File

@@ -0,0 +1,23 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXArrayBuffer.h>
#import <ExpoModulesJSI/MemoryBuffer.h>
@implementation EXArrayBufferStrongRef {
std::shared_ptr<expo::MemoryBuffer> _ptr;
}
- (nonnull instancetype)initWith:(std::shared_ptr<expo::MemoryBuffer>)ptr
{
if (self = [super init]) {
_ptr = ptr;
}
return self;
}
- (void)reset
{
_ptr.reset();
}
@end

View File

@@ -0,0 +1,53 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#import <Foundation/Foundation.h>
#import <jsi/jsi.h>
#import <React/RCTBridgeModule.h>
#import <ReactCommon/CallInvoker.h>
using namespace facebook;
using namespace react;
@class EXJavaScriptValue;
@class EXJavaScriptRuntime;
namespace expo
{
jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value);
jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value);
jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value);
jsi::String convertNSURLToJSIString(jsi::Runtime &runtime, NSURL *value);
jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value);
jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value);
std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value);
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value);
NSArray *convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr<CallInvoker> jsInvoker);
NSArray<EXJavaScriptValue *> *convertJSIValuesToNSArray(EXJavaScriptRuntime *runtime, const jsi::Value *values, size_t count);
NSDictionary *convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr<CallInvoker> jsInvoker);
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker);
id convertJSIValueToObjCObjectAsDictValue(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker);
RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr<CallInvoker> jsInvoker);
} // namespace expo
#endif

View File

@@ -0,0 +1,261 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <react/bridging/CallbackWrapper.h>
#import <ExpoModulesJSI/EXJavaScriptValue.h>
#import <ExpoModulesJSI/EXJavaScriptObject.h>
#import <ExpoModulesJSI/EXJavaScriptWeakObject.h>
#import <ExpoModulesJSI/EXJSIConversions.h>
#import <ExpoModulesJSI/EXJavaScriptValue.h>
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
#import <ExpoModulesJSI/EXStringUtils.h>
#import <ExpoModulesJSI/EXJavaScriptObjectBinding.h>
#import <ExpoModulesJSI/EXNativeArrayBuffer.h>
namespace expo {
/**
* All static helper functions are ObjC++ specific.
*/
jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value((bool)[value boolValue]);
}
jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value([value doubleValue]);
}
jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
{
const uint8_t *utf8 = (const uint8_t *)[value UTF8String];
const size_t length = [value length];
if (utf8 != nullptr && expo::isAllASCIIAndNotNull(utf8, utf8 + length)) {
return jsi::String::createFromAscii(runtime, (const char *)utf8, length);
}
// Using cStringUsingEncoding should be fine as long as we provide the length.
return jsi::String::createFromUtf16(runtime, (const char16_t *)[value cStringUsingEncoding:NSUTF16StringEncoding], length);
}
jsi::String convertNSURLToJSIString(jsi::Runtime &runtime, NSURL *value)
{
NSString *stringValue = [value absoluteString];
return convertNSStringToJSIString(runtime, stringValue);
}
jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
{
jsi::Object result = jsi::Object(runtime);
for (NSString *k in value) {
result.setProperty(runtime, [k UTF8String], convertObjCObjectToJSIValue(runtime, value[k]));
}
return result;
}
jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value)
{
jsi::Array result = jsi::Array(runtime, value.count);
for (size_t i = 0; i < value.count; i++) {
result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
}
return result;
}
std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value)
{
std::vector<jsi::Value> result;
for (size_t i = 0; i < value.count; i++) {
result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i]));
}
return result;
}
jsi::Value createUint8Array(jsi::Runtime &runtime, NSData *data) {
auto global = runtime.global();
auto arrayBufferCtor = global.getPropertyAsFunction(runtime, "ArrayBuffer");
auto arrayBufferObject = arrayBufferCtor.callAsConstructor(runtime, static_cast<int>(data.length)).getObject(runtime);
auto arrayBuffer = arrayBufferObject.getArrayBuffer(runtime);
memcpy(arrayBuffer.data(runtime), data.bytes, data.length);
auto uint8ArrayCtor = global.getPropertyAsFunction(runtime, "Uint8Array");
auto uint8Array = uint8ArrayCtor.callAsConstructor(runtime, arrayBufferObject).getObject(runtime);
return uint8Array;
}
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
{
if ([value isKindOfClass:[EXJavaScriptValue class]]) {
return [(EXJavaScriptValue *)value get];
}
if ([value isKindOfClass:[EXJavaScriptObject class]]) {
return jsi::Value(runtime, *[(EXJavaScriptObject *)value get]);
}
if ([value isKindOfClass:[EXJavaScriptWeakObject class]]) {
return jsi::Value(runtime, *[[(EXJavaScriptWeakObject *)value lock] get]);
}
if ([value isKindOfClass:[EXJavaScriptObjectBinding class]]) {
return jsi::Value(runtime, *[[(EXJavaScriptObjectBinding *)value get] get]);
}
if ([value isKindOfClass:[EXNativeArrayBuffer class]]) {
auto memoryBuffer = [(EXNativeArrayBuffer *)value jsiBuffer];
return jsi::ArrayBuffer(runtime, memoryBuffer);
}
if ([value isKindOfClass:[NSString class]]) {
return convertNSStringToJSIString(runtime, (NSString *)value);
} else if ([value isKindOfClass:[NSNumber class]]) {
if ([value isKindOfClass:[@YES class]]) {
return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value);
}
return convertNSNumberToJSINumber(runtime, (NSNumber *)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value);
} else if ([value isKindOfClass:[NSArray class]]) {
return convertNSArrayToJSIArray(runtime, (NSArray *)value);
} else if ([value isKindOfClass:[NSData class]]) {
return createUint8Array(runtime, (NSData *)value);
} else if ([value isKindOfClass:[NSURL class]]) {
return convertNSURLToJSIString(runtime, (NSURL *)value);
} else if (value == (id)kCFNull) {
return jsi::Value::null();
}
return jsi::Value::undefined();
}
NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value)
{
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
}
NSArray *convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr<CallInvoker> jsInvoker)
{
size_t size = value.size(runtime);
NSMutableArray *result = [NSMutableArray new];
for (size_t i = 0; i < size; i++) {
// Insert kCFNull when it's `undefined` value to preserve the indices.
[result
addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker) ?: (id)kCFNull];
}
return [result copy];
}
NSArray<EXJavaScriptValue *> *convertJSIValuesToNSArray(EXJavaScriptRuntime *runtime, const jsi::Value *values, size_t count)
{
NSMutableArray<EXJavaScriptValue *> *array = [NSMutableArray arrayWithCapacity:count];
jsi::Runtime *jsiRuntime = [runtime get];
for (int i = 0; i < count; i++) {
array[i] = [[EXJavaScriptValue alloc] initWithRuntime:runtime
value:jsi::Value(*jsiRuntime, values[i])];
}
return array;
}
NSDictionary *convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr<CallInvoker> jsInvoker)
{
jsi::Array propertyNames = value.getPropertyNames(runtime);
size_t size = propertyNames.size(runtime);
NSMutableDictionary *result = [NSMutableDictionary new];
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
NSString *k = convertJSIStringToNSString(runtime, name);
jsi::Value jsValue = value.getProperty(runtime, name);
id v = convertJSIValueToObjCObjectAsDictValue(runtime, std::move(jsValue), jsInvoker);
if (v) {
result[k] = v;
}
}
return [result copy];
}
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker)
{
if (value.isUndefined() || value.isNull()) {
return nil;
}
if (value.isBool()) {
return @(value.getBool());
}
if (value.isNumber()) {
return @(value.getNumber());
}
if (value.isString()) {
return convertJSIStringToNSString(runtime, value.getString(runtime));
}
if (value.isObject()) {
jsi::Object o = value.getObject(runtime);
if (o.isArray(runtime)) {
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
}
if (o.isFunction(runtime)) {
return convertJSIFunctionToCallback(runtime, std::move(o.getFunction(runtime)), jsInvoker);
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
}
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
}
id convertJSIValueToObjCObjectAsDictValue(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker)
{
if (value.isUndefined()) {
return nil;
}
if (value.isNull()) {
return [NSNull null];
}
if (value.isBool()) {
return @(value.getBool());
}
if (value.isNumber()) {
return @(value.getNumber());
}
if (value.isString()) {
return convertJSIStringToNSString(runtime, value.getString(runtime));
}
if (value.isObject()) {
jsi::Object o = value.getObject(runtime);
if (o.isArray(runtime)) {
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
}
if (o.isFunction(runtime)) {
return convertJSIFunctionToCallback(runtime, std::move(o.getFunction(runtime)), jsInvoker);
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
}
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
}
RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr<CallInvoker> jsInvoker)
{
auto weakWrapper = CallbackWrapper::createWeak(value.getFunction(runtime), runtime, jsInvoker);
BOOL __block wrapperWasCalled = NO;
RCTResponseSenderBlock callback = ^(NSArray *responses) {
if (wrapperWasCalled) {
throw std::runtime_error("callback arg cannot be called more than once");
}
auto strongWrapper = weakWrapper.lock();
if (!strongWrapper) {
return;
}
strongWrapper->jsInvoker().invokeAsync([weakWrapper, responses]() {
auto strongWrapper2 = weakWrapper.lock();
if (!strongWrapper2) {
return;
}
std::vector<jsi::Value> args = convertNSArrayToStdVector(strongWrapper2->runtime(), responses);
strongWrapper2->callback().call(strongWrapper2->runtime(), (const jsi::Value *)args.data(), args.size());
strongWrapper2->destroy();
});
wrapperWasCalled = YES;
};
return callback;
}
} // namespace expo

58
node_modules/expo-modules-core/ios/JSI/EXJSIUtils.h generated vendored Normal file
View File

@@ -0,0 +1,58 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#import <functional>
#import <jsi/jsi.h>
#import <React/RCTBridgeModule.h>
#import <ReactCommon/TurboModuleUtils.h>
#import <ReactCommon/CallInvoker.h>
#import <react/bridging/CallbackWrapper.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo
{
#pragma mark - Promises
using PromiseInvocationBlock = void (^)(RCTPromiseResolveBlock resolveWrapper, RCTPromiseRejectBlock rejectWrapper);
void callPromiseSetupWithBlock(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> jsInvoker, std::shared_ptr<react::Promise> promise, PromiseInvocationBlock setupBlock);
#pragma mark - Weak objects
/**
Checks whether the `WeakRef` class is available in the given runtime.
According to the docs, it is unimplemented in JSC prior to iOS 14.5.
As of the time of writing this comment it's also unimplemented in Hermes
where you should use `jsi::WeakObject` instead.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef
*/
bool isWeakRefSupported(jsi::Runtime &runtime);
/**
Creates the `WeakRef` with given JSI object. You should first use `isWeakRefSupported`
to check whether this feature is supported by the runtime.
*/
std::shared_ptr<jsi::Object> createWeakRef(jsi::Runtime &runtime, std::shared_ptr<jsi::Object> object);
/**
Returns the `WeakRef` object's target object, or an empty pointer if the target object has been reclaimed.
*/
std::shared_ptr<jsi::Object> derefWeakRef(jsi::Runtime &runtime, std::shared_ptr<jsi::Object> object);
#pragma mark - Errors
jsi::Value makeCodedError(jsi::Runtime &runtime, NSString *code, NSString *message);
#pragma mark - RuntimeScheduler
std::shared_ptr<react::RuntimeScheduler> runtimeSchedulerFromRuntime(jsi::Runtime &runtime);
} // namespace expo
#endif

137
node_modules/expo-modules-core/ios/JSI/EXJSIUtils.mm generated vendored Normal file
View File

@@ -0,0 +1,137 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#import <ExpoModulesJSI/EXJSIConversions.h>
#import <ExpoModulesJSI/EXJSIUtils.h>
#import <ExpoModulesJSI/JSIUtils.h>
namespace expo {
void callPromiseSetupWithBlock(jsi::Runtime &runtime, std::shared_ptr<CallInvoker> jsInvoker, std::shared_ptr<Promise> promise, PromiseInvocationBlock setupBlock)
{
auto weakResolveWrapper = react::CallbackWrapper::createWeak(promise->resolve_.getFunction(runtime), runtime, jsInvoker);
auto weakRejectWrapper = react::CallbackWrapper::createWeak(promise->reject_.getFunction(runtime), runtime, jsInvoker);
__block BOOL isSettled = NO;
RCTPromiseResolveBlock resolveBlock = ^(id result) {
if (isSettled) {
// The promise is already either resolved or rejected.
return;
}
auto strongResolveWrapper = weakResolveWrapper.lock();
auto strongRejectWrapper = weakRejectWrapper.lock();
if (!strongResolveWrapper || !strongRejectWrapper) {
return;
}
strongResolveWrapper->jsInvoker().invokeAsync([weakResolveWrapper, weakRejectWrapper, result]() {
auto strongResolveWrapper2 = weakResolveWrapper.lock();
auto strongRejectWrapper2 = weakRejectWrapper.lock();
if (!strongResolveWrapper2 || !strongRejectWrapper2) {
return;
}
jsi::Runtime &rt = strongResolveWrapper2->runtime();
jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
strongResolveWrapper2->callback().call(rt, arg);
strongResolveWrapper2->destroy();
strongRejectWrapper2->destroy();
});
isSettled = YES;
};
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
if (isSettled) {
// The promise is already either resolved or rejected.
return;
}
auto strongResolveWrapper = weakResolveWrapper.lock();
auto strongRejectWrapper = weakRejectWrapper.lock();
if (!strongResolveWrapper || !strongRejectWrapper) {
return;
}
strongRejectWrapper->jsInvoker().invokeAsync([weakResolveWrapper, weakRejectWrapper, code, message]() {
auto strongResolveWrapper2 = weakResolveWrapper.lock();
auto strongRejectWrapper2 = weakRejectWrapper.lock();
if (!strongResolveWrapper2 || !strongRejectWrapper2) {
return;
}
jsi::Runtime &rt = strongRejectWrapper2->runtime();
jsi::Value jsError = makeCodedError(rt, code, message);
strongRejectWrapper2->callback().call(rt, jsError);
strongResolveWrapper2->destroy();
strongRejectWrapper2->destroy();
});
isSettled = YES;
};
setupBlock(resolveBlock, rejectBlock);
}
#pragma mark - Weak objects
bool isWeakRefSupported(jsi::Runtime &runtime) {
return runtime.global().hasProperty(runtime, "WeakRef");
}
std::shared_ptr<jsi::Object> createWeakRef(jsi::Runtime &runtime, std::shared_ptr<jsi::Object> object) {
jsi::Object weakRef = runtime
.global()
.getProperty(runtime, "WeakRef")
.asObject(runtime)
.asFunction(runtime)
.callAsConstructor(runtime, jsi::Value(runtime, *object))
.asObject(runtime);
return std::make_shared<jsi::Object>(std::move(weakRef));
}
std::shared_ptr<jsi::Object> derefWeakRef(jsi::Runtime &runtime, std::shared_ptr<jsi::Object> object) {
jsi::Value ref = object->getProperty(runtime, "deref")
.asObject(runtime)
.asFunction(runtime)
.callWithThis(runtime, *object);
if (ref.isUndefined()) {
return nullptr;
}
return std::make_shared<jsi::Object>(ref.asObject(runtime));
}
#pragma mark - Errors
jsi::Value makeCodedError(jsi::Runtime &runtime, NSString *code, NSString *message) {
jsi::String jsCode = convertNSStringToJSIString(runtime, code);
jsi::String jsMessage = convertNSStringToJSIString(runtime, message);
return runtime
.global()
.getProperty(runtime, "ExpoModulesCore_CodedError")
.asObject(runtime)
.asFunction(runtime)
.callAsConstructor(runtime, {
jsi::Value(runtime, jsCode),
jsi::Value(runtime, jsMessage)
});
}
#pragma mark - RuntimeScheduler
std::shared_ptr<react::RuntimeScheduler> runtimeSchedulerFromRuntime(jsi::Runtime &runtime) {
if (auto binding = react::RuntimeSchedulerBinding::getBinding(runtime)) {
return binding->getRuntimeScheduler();
}
return nullptr;
}
} // namespace expo

View File

@@ -0,0 +1,102 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#ifdef __cplusplus
#import <jsi/jsi.h>
namespace jsi = facebook::jsi;
#endif // __cplusplus
@class EXJavaScriptRuntime;
@class EXJavaScriptValue;
@class EXJavaScriptWeakObject;
/**
The property descriptor options for the property being defined or modified.
*/
typedef NS_OPTIONS(NSInteger, EXJavaScriptObjectPropertyDescriptor) {
/**
If set, the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.
*/
EXJavaScriptObjectPropertyDescriptorConfigurable = 1 << 0,
/**
If set, the property shows up during enumeration of the properties on the corresponding object.
*/
EXJavaScriptObjectPropertyDescriptorEnumerable = 1 << 1,
/**
If set, the value associated with the property may be changed with an assignment operator.
*/
EXJavaScriptObjectPropertyDescriptorWritable = 1 << 2,
} NS_SWIFT_NAME(JavaScriptObjectPropertyDescriptor);
NS_SWIFT_NAME(JavaScriptObject)
@interface EXJavaScriptObject : NSObject
// Some parts of the interface must be hidden for Swift it can't import any C++ code.
#ifdef __cplusplus
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObjectPtr
runtime:(nonnull EXJavaScriptRuntime *)runtime;
/**
Returns the pointer to the underlying object.
*/
- (nonnull jsi::Object *)get;
/**
Returns the shared pointer to the underlying object.
*/
- (std::shared_ptr<jsi::Object>)getShared;
#endif // __cplusplus
#pragma mark - Accessing object properties
/**
\return a bool whether the object has a property with the given name.
*/
- (BOOL)hasProperty:(nonnull NSString *)name;
/**
\return the property of the object with the given name.
If the name isn't a property on the object, returns the `undefined` value.
*/
- (nonnull EXJavaScriptValue *)getProperty:(nonnull NSString *)name;
/**
\return an array consisting of all enumerable property names in the object and its prototype chain.
*/
- (nonnull NSArray<NSString *> *)getPropertyNames;
#pragma mark - Modifying object properties
/**
Sets the value for the property with the given name.
*/
- (void)setProperty:(nonnull NSString *)name value:(nullable id)value;
/**
Defines a new property or modifies an existing property on the object using the property descriptor.
*/
- (void)defineProperty:(nonnull NSString *)name descriptor:(nonnull EXJavaScriptObject *)descriptor;
/**
Defines a new property or modifies an existing property on the object. Calls `Object.defineProperty` under the hood.
*/
- (void)defineProperty:(nonnull NSString *)name value:(nullable id)value options:(EXJavaScriptObjectPropertyDescriptor)options;
#pragma mark - WeakObject
- (nonnull EXJavaScriptWeakObject *)createWeak;
#pragma mark - Deallocator
- (void)setObjectDeallocator:(void (^ _Nonnull)(void))deallocatorBlock;
#pragma mark - Memory pressure
/**
Sets the memory pressure to inform the GC about how much external memory is associated with that specific JS object.
*/
- (void)setExternalMemoryPressure:(size_t)size;
@end

View File

@@ -0,0 +1,140 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJSIConversions.h>
#import <ExpoModulesJSI/EXJavaScriptValue.h>
#import <ExpoModulesJSI/EXJavaScriptObject.h>
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
#import <ExpoModulesJSI/EXJavaScriptWeakObject.h>
#import <ExpoModulesJSI/EXJSIUtils.h>
#import <ExpoModulesJSI/JSIUtils.h>
#import <ExpoModulesJSI/ObjectDeallocator.h>
@implementation EXJavaScriptObject {
/**
Pointer to the `EXJavaScriptRuntime` wrapper.
\note It must be weak because only then the original runtime can be safely deallocated
when the JS engine wants to without unsetting it on each created object.
*/
__weak EXJavaScriptRuntime *_runtime;
/**
Shared pointer to the original JSI object that is being wrapped by `EXJavaScriptObject` class.
*/
std::shared_ptr<jsi::Object> _jsObjectPtr;
}
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObjectPtr
runtime:(nonnull EXJavaScriptRuntime *)runtime
{
if (self = [super init]) {
_runtime = runtime;
_jsObjectPtr = jsObjectPtr;
}
return self;
}
- (nonnull jsi::Object *)get
{
return _jsObjectPtr.get();
}
- (std::shared_ptr<jsi::Object>)getShared
{
return _jsObjectPtr;
}
#pragma mark - Accessing object properties
- (BOOL)hasProperty:(nonnull NSString *)name
{
return _jsObjectPtr->hasProperty(*[_runtime get], [name UTF8String]);
}
- (nonnull EXJavaScriptValue *)getProperty:(nonnull NSString *)name
{
return [[EXJavaScriptValue alloc] initWithRuntime:_runtime
value:_jsObjectPtr->getProperty(*[_runtime get], [name UTF8String])];
}
- (nonnull NSArray<NSString *> *)getPropertyNames
{
jsi::Runtime *runtime = [_runtime get];
jsi::Array propertyNamesArray = _jsObjectPtr->getPropertyNames(*[_runtime get]);
return expo::convertJSIArrayToNSArray(*runtime, propertyNamesArray, nullptr);
}
#pragma mark - Modifying object properties
- (void)setProperty:(nonnull NSString *)name value:(nullable id)value
{
jsi::Value jsiValue = expo::convertObjCObjectToJSIValue(*[_runtime get], value);
_jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], jsiValue);
}
- (void)defineProperty:(nonnull NSString *)name descriptor:(nonnull EXJavaScriptObject *)descriptor
{
jsi::Runtime *runtime = [_runtime get];
jsi::Object *jsThis = _jsObjectPtr.get();
expo::common::defineProperty(*runtime, jsThis, [name UTF8String], std::move(*[descriptor get]));
}
- (void)defineProperty:(nonnull NSString *)name value:(nullable id)value options:(EXJavaScriptObjectPropertyDescriptor)options
{
jsi::Runtime *runtime = [_runtime get];
jsi::Object *jsThis = _jsObjectPtr.get();
jsi::Object descriptor = [self preparePropertyDescriptorWithOptions:options];
descriptor.setProperty(*runtime, "value", expo::convertObjCObjectToJSIValue(*runtime, value));
expo::common::defineProperty(*runtime, jsThis, [name UTF8String], std::move(descriptor));
}
#pragma mark - WeakObject
- (nonnull EXJavaScriptWeakObject *)createWeak
{
return [[EXJavaScriptWeakObject alloc] initWith:_jsObjectPtr runtime:_runtime];
}
#pragma mark - Deallocator
- (void)setObjectDeallocator:(void (^)(void))deallocatorBlock
{
expo::common::setDeallocator(*[_runtime get], _jsObjectPtr, deallocatorBlock);
}
#pragma mark - Equality
- (BOOL)isEqual:(id)object
{
if ([object isKindOfClass:EXJavaScriptObject.class]) {
jsi::Runtime *runtime = [_runtime get];
jsi::Object *a = _jsObjectPtr.get();
jsi::Object *b = [(EXJavaScriptObject *)object get];
return jsi::Object::strictEquals(*runtime, *a, *b);
}
return false;
}
#pragma mark - Memory pressure
- (void)setExternalMemoryPressure:(size_t)size
{
_jsObjectPtr->setExternalMemoryPressure(*[_runtime get], size);
}
#pragma mark - Private helpers
- (jsi::Object)preparePropertyDescriptorWithOptions:(EXJavaScriptObjectPropertyDescriptor)options
{
jsi::Runtime *runtime = [_runtime get];
jsi::Object descriptor(*runtime);
descriptor.setProperty(*runtime, "configurable", (bool)(options & EXJavaScriptObjectPropertyDescriptorConfigurable));
descriptor.setProperty(*runtime, "enumerable", (bool)(options & EXJavaScriptObjectPropertyDescriptorEnumerable));
descriptor.setProperty(*runtime, "writable", (bool)(options & EXJavaScriptObjectPropertyDescriptorWritable));
return descriptor;
}
@end

View File

@@ -0,0 +1,25 @@
// Copyright 2023-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesJSI/EXJavaScriptObject.h>
NS_ASSUME_NONNULL_BEGIN
typedef EXJavaScriptObject * _Nonnull (^EXJavaScriptObjectBindingGetter)(void);
NS_SWIFT_NAME(JavaScriptObjectBinding)
@interface EXJavaScriptObjectBinding : NSObject
@property (nonatomic, copy) EXJavaScriptObjectBindingGetter getter;
- (instancetype)initWithGetter:(EXJavaScriptObjectBindingGetter)getter
NS_DESIGNATED_INITIALIZER
NS_SWIFT_NAME(init(getter:));
- (instancetype)init NS_UNAVAILABLE;
- (EXJavaScriptObject *)get;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,24 @@
// Copyright 2025-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJavaScriptObjectBinding.h>
#import <ExpoModulesJSI/EXJSIConversions.h>
/**
A wrapper around `EXJavaScriptObject`'s getter. The getter is a Swift lambda that creates the JS object.
Needed to make sure the object creation happens on the correct thread when called from inside `EXJSIConversions`.
*/
@implementation EXJavaScriptObjectBinding
- (nonnull instancetype)initWithGetter:(EXJavaScriptObjectBindingGetter)getter
{
self.getter = getter;
return self;
}
- (EXJavaScriptObject *)get
{
auto obj = self.getter();
return obj;
}
@end

View File

@@ -0,0 +1,125 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <React/RCTBridgeModule.h>
#import <React/RCTCallInvoker.h>
#import <Foundation/Foundation.h>
#import <ExpoModulesJSI/EXJavaScriptValue.h>
#import <ExpoModulesJSI/EXJavaScriptObject.h>
#ifdef __cplusplus
#import <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
namespace facebook::react {
class RuntimeScheduler;
}
namespace jsi = facebook::jsi;
namespace react = facebook::react;
#endif // __cplusplus
@class EXJavaScriptValue;
@class EXJavaScriptObject;
typedef void (^JSRuntimeExecutionBlock)(void);
typedef void (^JSAsyncFunctionBlock)(EXJavaScriptValue *_Nonnull thisValue,
NSArray<EXJavaScriptValue *> *_Nonnull arguments,
NS_SWIFT_SENDABLE RCTPromiseResolveBlock _Nonnull resolve,
NS_SWIFT_SENDABLE RCTPromiseRejectBlock _Nonnull reject);
typedef EXJavaScriptValue *_Nullable (^JSSyncFunctionBlock)(EXJavaScriptValue *_Nonnull thisValue,
NSArray<EXJavaScriptValue *> *_Nonnull arguments,
NSError *_Nullable __autoreleasing *_Nullable error);
#ifdef __cplusplus
typedef jsi::Value (^JSHostFunctionBlock)(jsi::Runtime &runtime,
std::shared_ptr<react::CallInvoker> callInvoker,
EXJavaScriptValue *_Nonnull thisValue,
NSArray<EXJavaScriptValue *> *_Nonnull arguments);
#endif // __cplusplus
NS_SWIFT_SENDABLE
NS_SWIFT_NAME(JavaScriptRuntime)
@interface EXJavaScriptRuntime : NSObject
/**
Creates a new JavaScript runtime.
*/
- (nonnull instancetype)init;
#ifdef __cplusplus
- (nonnull instancetype)initWithRuntime:(jsi::Runtime &)runtime;
- (nonnull instancetype)initWithRuntime:(jsi::Runtime &)runtime callInvoker:(std::shared_ptr<react::CallInvoker>)callInvoker;
/**
Returns the underlying runtime object.
*/
- (nonnull jsi::Runtime *)get;
/**
Returns the call invoker the runtime was initialized with.
*/
- (std::shared_ptr<react::CallInvoker>)callInvoker;
/**
Wraps given host object to `EXJavaScriptObject`.
*/
- (nonnull EXJavaScriptObject *)createHostObject:(std::shared_ptr<jsi::HostObject>)jsiHostObjectPtr;
#endif // __cplusplus
/**
Returns the runtime global object for use in Swift.
*/
- (nonnull EXJavaScriptObject *)global;
/**
Creates a new object for use in Swift.
*/
- (nonnull EXJavaScriptObject *)createObject;
/**
Creates a synchronous host function that runs given block when it's called.
The value returned by the block is synchronously returned to JS.
\return A JavaScript function represented as a `JavaScriptObject`.
*/
- (nonnull EXJavaScriptObject *)createSyncFunction:(nonnull NSString *)name
argsCount:(NSInteger)argsCount
block:(nonnull JSSyncFunctionBlock)block NS_REFINED_FOR_SWIFT;
/**
Creates an asynchronous host function that runs given block when it's called.
The block receives a resolver that you should call when the asynchronous operation
succeeds and a rejecter to call whenever it fails.
\return A JavaScript function represented as a `JavaScriptObject`.
*/
- (nonnull EXJavaScriptObject *)createAsyncFunction:(nonnull NSString *)name
argsCount:(NSInteger)argsCount
block:(nonnull JSAsyncFunctionBlock)block;
#pragma mark - Classes
/**
Creates a new object, using the provided object as the prototype.
*/
- (nullable EXJavaScriptObject *)createObjectWithPrototype:(nonnull EXJavaScriptObject *)prototype;
#pragma mark - Script evaluation
/**
Evaluates given JavaScript source code.
*/
- (nonnull EXJavaScriptValue *)evaluateScript:(nonnull NSString *)scriptSource NS_REFINED_FOR_SWIFT;
#pragma mark - Runtime execution
/**
Schedules a block to be executed with granted synchronized access to the JS runtime.
*/
- (void)schedule:(nonnull JSRuntimeExecutionBlock)block priority:(int)priority NS_REFINED_FOR_SWIFT;
@end

View File

@@ -0,0 +1,230 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
#import <ExpoModulesJSI/EXJSIUtils.h>
#import <ExpoModulesJSI/EXJSIConversions.h>
#import <ExpoModulesJSI/TestingJSCallInvoker.h>
#import <ExpoModulesJSI/JSIUtils.h>
#import <jsi/jsi.h>
#import <hermes/hermes.h>
#import <ReactCommon/SchedulerPriority.h>
@implementation EXJavaScriptRuntime {
std::shared_ptr<jsi::Runtime> _runtime;
std::shared_ptr<react::CallInvoker> _jsCallInvoker;
std::shared_ptr<facebook::react::RuntimeScheduler> _runtimeScheduler;
}
/**
Initializes a runtime that is independent from React Native and its runtime initialization.
This flow is mostly intended for tests.
*/
- (nonnull instancetype)init
{
if (self = [super init]) {
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
_runtime = facebook::hermes::makeHermesRuntime();
// This version of the Hermes uses a Promise implementation that is provided by the RN.
// The `setImmediate` function isn't defined, but is required by the Promise implementation.
// That's why we inject it here.
auto setImmediatePropName = jsi::PropNameID::forUtf8(*_runtime, "setImmediate");
_runtime->global().setProperty(
*_runtime, setImmediatePropName, jsi::Function::createFromHostFunction(*_runtime, setImmediatePropName, 1,
[](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
args[0].asObject(rt).asFunction(rt).call(rt);
return jsi::Value::undefined();
})
);
#else
_runtime = jsc::makeJSCRuntime();
#endif
_jsCallInvoker = std::make_shared<expo::TestingJSCallInvoker>(_runtime);
}
return self;
}
- (nonnull instancetype)initWithRuntime:(jsi::Runtime &)runtime
{
if (self = [super init]) {
// Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it.
// In this code flow, the runtime should be owned by something else like the RCTBridge.
// See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
_runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), &runtime);
_runtimeScheduler = expo::runtimeSchedulerFromRuntime(runtime);
_jsCallInvoker = std::make_shared<RuntimeSchedulerCallInvoker>(_runtimeScheduler);
}
return self;
}
- (nonnull instancetype)initWithRuntime:(jsi::Runtime &)runtime callInvoker:(std::shared_ptr<react::RuntimeSchedulerCallInvoker>)callInvoker {
if (self = [super init]) {
_runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), &runtime);
_runtimeScheduler = nullptr;
_jsCallInvoker = callInvoker;
}
return self;
}
- (nonnull jsi::Runtime *)get
{
return _runtime.get();
}
- (std::shared_ptr<react::CallInvoker>)callInvoker
{
return _jsCallInvoker;
}
- (nonnull EXJavaScriptObject *)createObject
{
auto jsObjectPtr = std::make_shared<jsi::Object>(*_runtime);
return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
}
- (nonnull EXJavaScriptObject *)createHostObject:(std::shared_ptr<jsi::HostObject>)jsiHostObjectPtr
{
auto jsObjectPtr = std::make_shared<jsi::Object>(jsi::Object::createFromHostObject(*_runtime, jsiHostObjectPtr));
return [[EXJavaScriptObject alloc] initWith:jsObjectPtr runtime:self];
}
- (nonnull EXJavaScriptObject *)global
{
auto jsGlobalPtr = std::make_shared<jsi::Object>(_runtime->global());
return [[EXJavaScriptObject alloc] initWith:jsGlobalPtr runtime:self];
}
- (nonnull EXJavaScriptObject *)createSyncFunction:(nonnull NSString *)name
argsCount:(NSInteger)argsCount
block:(nonnull JSSyncFunctionBlock)block
{
JSHostFunctionBlock hostFunctionBlock = ^jsi::Value(
jsi::Runtime &runtime,
std::shared_ptr<react::CallInvoker> callInvoker,
EXJavaScriptValue * _Nonnull thisValue,
NSArray<EXJavaScriptValue *> * _Nonnull arguments) {
NSError *error;
EXJavaScriptValue *result = block(thisValue, arguments, &error);
if (error == nil) {
return [result get];
} else {
// `expo::makeCodedError` doesn't work during unit tests, so we construct Error and add a code,
// instead of using the CodedError subclass.
jsi::String jsCode = expo::convertNSStringToJSIString(runtime, error.userInfo[@"code"]);
jsi::String jsMessage = expo::convertNSStringToJSIString(runtime, error.userInfo[@"message"]);
jsi::Value error = runtime
.global()
.getProperty(runtime, "Error")
.asObject(runtime)
.asFunction(runtime)
.callAsConstructor(runtime, {
jsi::Value(runtime, jsMessage)
});
error.asObject(runtime).setProperty(runtime, "code", jsi::Value(runtime, jsCode));
throw jsi::JSError(runtime, jsi::Value(runtime, error));
}
};
return [self createHostFunction:name argsCount:argsCount block:hostFunctionBlock];
}
- (nonnull EXJavaScriptObject *)createAsyncFunction:(nonnull NSString *)name
argsCount:(NSInteger)argsCount
block:(nonnull JSAsyncFunctionBlock)block
{
JSHostFunctionBlock hostFunctionBlock = ^jsi::Value(
jsi::Runtime &runtime,
std::shared_ptr<react::CallInvoker> callInvoker,
EXJavaScriptValue * _Nonnull thisValue,
NSArray<EXJavaScriptValue *> * _Nonnull arguments) {
if (!callInvoker) {
// In mocked environment the call invoker may be null so it's not supported to call async functions.
// Testing async functions is a bit more complicated anyway. See `init` description for more.
throw jsi::JSError(runtime, "Calling async functions is not supported when the call invoker is unavailable");
}
// The function that is invoked as a setup of the EXJavaScript `Promise`.
auto promiseSetup = [callInvoker, block, thisValue, arguments](jsi::Runtime &runtime, std::shared_ptr<Promise> promise) {
expo::callPromiseSetupWithBlock(runtime, callInvoker, promise, ^(RCTPromiseResolveBlock resolver, RCTPromiseRejectBlock rejecter) {
block(thisValue, arguments, resolver, rejecter);
});
};
return createPromiseAsJSIValue(runtime, promiseSetup);
};
return [self createHostFunction:name argsCount:argsCount block:hostFunctionBlock];
}
#pragma mark - Classes
- (nullable EXJavaScriptObject *)createObjectWithPrototype:(nonnull EXJavaScriptObject *)prototype
{
std::shared_ptr<jsi::Object> object = std::make_shared<jsi::Object>(expo::common::createObjectWithPrototype(*_runtime, [prototype getShared].get()));
return object ? [[EXJavaScriptObject alloc] initWith:object runtime:self] : nil;
}
#pragma mark - Script evaluation
- (nonnull EXJavaScriptValue *)evaluateScript:(nonnull NSString *)scriptSource
{
std::shared_ptr<jsi::StringBuffer> scriptBuffer = std::make_shared<jsi::StringBuffer>([scriptSource UTF8String]);
jsi::Value result;
try {
result = _runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>");
} catch (jsi::JSError &error) {
NSString *reason = [NSString stringWithUTF8String:error.getMessage().c_str()];
NSString *stack = [NSString stringWithUTF8String:error.getStack().c_str()];
@throw [NSException exceptionWithName:@"ScriptEvaluationException" reason:reason userInfo:@{
@"message": reason,
@"stack": stack,
}];
} catch (jsi::JSIException &error) {
NSString *reason = [NSString stringWithUTF8String:error.what()];
@throw [NSException exceptionWithName:@"ScriptEvaluationException" reason:reason userInfo:@{
@"message": reason
}];
}
return [[EXJavaScriptValue alloc] initWithRuntime:self value:std::move(result)];
}
#pragma mark - Runtime execution
- (void)schedule:(nonnull JSRuntimeExecutionBlock)block priority:(int)priority
{
if (_runtimeScheduler) {
auto schedulerPriority = static_cast<facebook::react::SchedulerPriority>(priority);
auto callback = [block](jsi::Runtime &) {
block();
};
_runtimeScheduler->scheduleTask(schedulerPriority, std::move(callback));
return;
}
_jsCallInvoker->invokeAsync(SchedulerPriority(priority), [block = std::move(block)](jsi::Runtime&) {
block();
});
}
#pragma mark - Private
- (nonnull EXJavaScriptObject *)createHostFunction:(nonnull NSString *)name
argsCount:(NSInteger)argsCount
block:(nonnull JSHostFunctionBlock)block
{
jsi::PropNameID propNameId = jsi::PropNameID::forAscii(*_runtime, [name UTF8String], [name length]);
std::weak_ptr<react::CallInvoker> weakCallInvoker = _jsCallInvoker;
jsi::HostFunctionType function = [weakCallInvoker, block, self](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
// Theoretically should check here whether the call invoker isn't null, but in mocked environment
// there is no need to care about that for synchronous calls, so it's ensured in `createAsyncFunction` instead.
auto callInvoker = weakCallInvoker.lock();
NSArray<EXJavaScriptValue *> *arguments = expo::convertJSIValuesToNSArray(self, args, count);
EXJavaScriptValue *thisValue = [[EXJavaScriptValue alloc] initWithRuntime:self value:jsi::Value(runtime, thisVal)];
return block(runtime, callInvoker, thisValue, arguments);
};
std::shared_ptr<jsi::Object> fnPtr = std::make_shared<jsi::Object>(jsi::Function::createFromHostFunction(*_runtime, propNameId, (unsigned int)argsCount, function));
return [[EXJavaScriptObject alloc] initWith:fnPtr runtime:self];
}
@end

View File

@@ -0,0 +1,30 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesJSI/EXJavaScriptObject.h>
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
// We need to redefine the C++ enum (see TypedArray.h) in an Objective-C way to expose it to Swift.
// Please keep them in-sync!
typedef NS_ENUM(NSInteger, EXTypedArrayKind) {
Int8Array = 1,
Int16Array = 2,
Int32Array = 3,
Uint8Array = 4,
Uint8ClampedArray = 5,
Uint16Array = 6,
Uint32Array = 7,
Float32Array = 8,
Float64Array = 9,
BigInt64Array = 10,
BigUint64Array = 11,
} NS_SWIFT_NAME(TypedArrayKind);
NS_SWIFT_NAME(JavaScriptTypedArray)
@interface EXJavaScriptTypedArray : EXJavaScriptObject
@property (nonatomic) EXTypedArrayKind kind;
- (nonnull void *)getUnsafeMutableRawPointer;
@end

View File

@@ -0,0 +1,29 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJavaScriptTypedArray.h>
#import <ExpoModulesJSI/TypedArray.h>
@implementation EXJavaScriptTypedArray {
__weak EXJavaScriptRuntime *_runtime;
std::shared_ptr<expo::TypedArray> _typedArrayPtr;
}
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObjectPtr
runtime:(EXJavaScriptRuntime *)runtime
{
if (self = [super initWith:jsObjectPtr runtime:runtime]) {
jsi::Runtime *rt = [runtime get];
_runtime = runtime;
_typedArrayPtr = std::make_shared<expo::TypedArray>(*rt, *jsObjectPtr.get());
_kind = (EXTypedArrayKind)_typedArrayPtr->getKind(*rt);
}
return self;
}
- (nonnull void *)getUnsafeMutableRawPointer
{
return _typedArrayPtr->getRawPointer(*[_runtime get]);
}
@end

View File

@@ -0,0 +1,75 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesJSI/EXJavaScriptObject.h>
#ifdef __cplusplus
#import <jsi/jsi.h>
namespace jsi = facebook::jsi;
#endif // __cplusplus
@class EXJavaScriptRuntime;
@class EXRawJavaScriptFunction;
@class EXJavaScriptTypedArray;
@class EXJavaScriptArrayBuffer;
/**
Represents any JavaScript value. Its purpose is to exposes `facebook::jsi::Value` API back to Swift.
*/
NS_SWIFT_NAME(JavaScriptValue)
@interface EXJavaScriptValue : NSObject
#ifdef __cplusplus
- (nonnull instancetype)initWithRuntime:(nullable EXJavaScriptRuntime *)runtime
value:(jsi::Value)value;
/**
Returns a copy of the underlying `jsi::Value`.
*/
- (jsi::Value)get;
#endif // __cplusplus
#pragma mark - Type checking
- (BOOL)isUndefined;
- (BOOL)isNull;
- (BOOL)isBool;
- (BOOL)isNumber;
- (BOOL)isString;
- (BOOL)isSymbol;
- (BOOL)isObject;
- (BOOL)isFunction;
- (BOOL)isArray;
- (BOOL)isTypedArray;
- (BOOL)isArrayBuffer;
#pragma mark - Type casting
- (nullable id)getRaw;
- (BOOL)getBool;
- (NSInteger)getInt;
- (double)getDouble;
- (nonnull NSString *)getString;
- (nonnull NSArray<EXJavaScriptValue *> *)getArray;
- (nonnull NSDictionary<NSString *, id> *)getDictionary;
- (nonnull EXJavaScriptObject *)getObject;
- (nonnull EXRawJavaScriptFunction *)getFunction;
- (nullable EXJavaScriptTypedArray *)getTypedArray;
- (nullable EXJavaScriptArrayBuffer *)getArrayBuffer;
#pragma mark - Helpers
- (nonnull NSString *)toString;
#pragma mark - Statics
@property (class, nonatomic, assign, readonly, nonnull) EXJavaScriptValue *undefined;
@property (class, nonatomic, assign, readonly, nonnull) EXJavaScriptValue *null;
+ (nonnull EXJavaScriptValue *)number:(double)value;
+ (nonnull EXJavaScriptValue *)string:(nonnull NSString *)value runtime:(nonnull EXJavaScriptRuntime *)runtime;
+ (nonnull EXJavaScriptValue *)from:(nullable id)value runtime:(nonnull EXJavaScriptRuntime *)runtime;
@end

View File

@@ -0,0 +1,229 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJSIConversions.h>
#import <ExpoModulesJSI/EXJavaScriptValue.h>
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
#import <ExpoModulesJSI/EXRawJavaScriptFunction.h>
#import <ExpoModulesJSI/EXJavaScriptTypedArray.h>
#import <ExpoModulesJSI/EXRawJavaScriptArrayBuffer.h>
#import <ExpoModulesJSI/TypedArray.h>
@implementation EXJavaScriptValue {
__weak EXJavaScriptRuntime *_runtime;
/**
The underlying JS value of which the `JavaScriptValue` is the only owner.
*/
jsi::Value _value;
}
- (nonnull instancetype)initWithRuntime:(nullable EXJavaScriptRuntime *)runtime
value:(jsi::Value)value
{
if (self = [super init]) {
_runtime = runtime;
_value = std::move(value);
}
return self;
}
- (jsi::Value)get
{
return jsi::Value(*[_runtime get], _value);
}
#pragma mark - Type checking
- (BOOL)isUndefined
{
return _value.isUndefined();
}
- (BOOL)isNull
{
return _value.isNull();
}
- (BOOL)isBool
{
return _value.isBool();
}
- (BOOL)isNumber
{
return _value.isNumber();
}
- (BOOL)isString
{
return _value.isString();
}
- (BOOL)isSymbol
{
return _value.isSymbol();
}
- (BOOL)isObject
{
return _value.isObject();
}
- (BOOL)isFunction
{
if (_value.isObject()) {
jsi::Runtime *runtime = [_runtime get];
return _value.getObject(*runtime).isFunction(*runtime);
}
return false;
}
- (BOOL)isArray
{
if (_value.isObject()) {
jsi::Runtime *runtime = [_runtime get];
return _value.getObject(*runtime).isArray(*runtime);
}
return false;
}
- (BOOL)isTypedArray
{
if (_value.isObject()) {
jsi::Runtime *runtime = [_runtime get];
return expo::isTypedArray(*runtime, _value.getObject(*runtime));
}
return false;
}
- (BOOL)isArrayBuffer
{
if (_value.isObject()) {
jsi::Runtime *runtime = [_runtime get];
return _value.getObject(*runtime).isArrayBuffer(*runtime);
}
return false;
}
#pragma mark - Type casting
- (nullable id)getRaw
{
return expo::convertJSIValueToObjCObject(*[_runtime get], _value, [_runtime callInvoker]);
}
- (BOOL)getBool
{
return _value.getBool();
}
- (NSInteger)getInt
{
return _value.getNumber();
}
- (double)getDouble
{
return _value.getNumber();
}
- (nonnull NSString *)getString
{
jsi::Runtime *runtime = [_runtime get];
return expo::convertJSIStringToNSString(*runtime, _value.getString(*runtime));
}
- (nonnull NSArray<EXJavaScriptValue *> *)getArray
{
jsi::Runtime *runtime = [_runtime get];
jsi::Array jsiArray = _value.getObject(*runtime).getArray(*runtime);
size_t arraySize = jsiArray.size(*runtime);
NSMutableArray *result = [NSMutableArray arrayWithCapacity:arraySize];
for (size_t i = 0; i < arraySize; i++) {
jsi::Value item = jsiArray.getValueAtIndex(*runtime, i);
[result addObject:[[EXJavaScriptValue alloc] initWithRuntime:_runtime value:std::move(item)]];
}
return result;
}
- (nonnull NSDictionary<NSString *, id> *)getDictionary
{
jsi::Runtime *runtime = [_runtime get];
return expo::convertJSIObjectToNSDictionary(*runtime, _value.getObject(*runtime), [_runtime callInvoker]);
}
- (nonnull EXJavaScriptObject *)getObject
{
jsi::Runtime *runtime = [_runtime get];
std::shared_ptr<jsi::Object> objectPtr = std::make_shared<jsi::Object>(_value.asObject(*runtime));
return [[EXJavaScriptObject alloc] initWith:objectPtr runtime:_runtime];
}
- (nonnull EXRawJavaScriptFunction *)getFunction
{
jsi::Runtime *runtime = [_runtime get];
std::shared_ptr<jsi::Function> functionPtr = std::make_shared<jsi::Function>(_value.asObject(*runtime).asFunction(*runtime));
return [[EXRawJavaScriptFunction alloc] initWith:functionPtr runtime:_runtime];
}
- (nullable EXJavaScriptTypedArray *)getTypedArray
{
if (![self isTypedArray]) {
return nil;
}
jsi::Runtime *runtime = [_runtime get];
std::shared_ptr<jsi::Object> objectPtr = std::make_shared<jsi::Object>(_value.asObject(*runtime));
return [[EXJavaScriptTypedArray alloc] initWith:objectPtr runtime:_runtime];
}
- (nullable EXJavaScriptArrayBuffer *)getArrayBuffer
{
if (![self isArrayBuffer]) {
return nil;
}
jsi::Runtime *runtime = [_runtime get];
std::shared_ptr<jsi::Object> objectPtr = std::make_shared<jsi::Object>(_value.asObject(*runtime));
return [[EXJavaScriptArrayBuffer alloc] initWith:objectPtr runtime:_runtime];
}
#pragma mark - Helpers
- (nonnull NSString *)toString
{
jsi::Runtime *runtime = [_runtime get];
return expo::convertJSIStringToNSString(*runtime, _value.toString(*runtime));
}
#pragma mark - Static properties
+ (nonnull EXJavaScriptValue *)undefined
{
return [[EXJavaScriptValue alloc] initWithRuntime:nil value:jsi::Value::undefined()];
}
+ (nonnull EXJavaScriptValue *)null
{
return [[EXJavaScriptValue alloc] initWithRuntime:nil value:jsi::Value::null()];
}
+ (nonnull EXJavaScriptValue *)number:(double)value
{
return [[EXJavaScriptValue alloc] initWithRuntime:nil value:jsi::Value(value)];
}
+ (nonnull EXJavaScriptValue *)string:(nonnull NSString *)value runtime:(nonnull EXJavaScriptRuntime *)runtime
{
jsi::Runtime *jsiRuntime = [runtime get];
return [[EXJavaScriptValue alloc] initWithRuntime:runtime
value:expo::convertNSStringToJSIString(*jsiRuntime, value)];
}
+ (nonnull EXJavaScriptValue *)from:(nullable id)value runtime:(nonnull EXJavaScriptRuntime *)runtime
{
jsi::Runtime *jsiRuntime = [runtime get];
return [[EXJavaScriptValue alloc] initWithRuntime:runtime
value:expo::convertObjCObjectToJSIValue(*jsiRuntime, value)];
}
@end

View File

@@ -0,0 +1,23 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesJSI/EXJavaScriptValue.h>
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
#ifdef __cplusplus
#import <jsi/jsi.h>
namespace jsi = facebook::jsi;
#endif // __cplusplus
NS_SWIFT_NAME(JavaScriptWeakObject)
@interface EXJavaScriptWeakObject : NSObject
#ifdef __cplusplus
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObject
runtime:(nonnull EXJavaScriptRuntime *)runtime;
#endif // __cplusplus
- (nullable EXJavaScriptObject *)lock;
@end

View File

@@ -0,0 +1,74 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJSIUtils.h>
#import <ExpoModulesJSI/EXJavaScriptWeakObject.h>
@implementation EXJavaScriptWeakObject {
/**
Pointer to the `EXJavaScriptRuntime` wrapper.
\note It must be weak because only then the original runtime can be safely deallocated
when the JS engine wants to without unsetting it on each created object.
*/
__weak EXJavaScriptRuntime *_runtime;
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
/**
A weak reference to a JS object. Available only on Hermes engine.
*/
std::shared_ptr<jsi::WeakObject> _weakObject;
#else
/**
Shared pointer to the `WeakRef` JS object. Available only on JSC engine.
*/
std::shared_ptr<jsi::Object> _weakObject;
#endif
}
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObject
runtime:(nonnull EXJavaScriptRuntime *)runtime
{
if (self = [super init]) {
_runtime = runtime;
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
_weakObject = std::make_shared<jsi::WeakObject>(*[runtime get], *jsObject);
#else
// Check whether the runtime supports `WeakRef` objects. If it does not,
// we consciously hold a strong reference to the object and cause memory leaks.
// This is the case on JSC prior to iOS 14.5.
if (expo::isWeakRefSupported(*[runtime get])) {
_weakObject = expo::createWeakRef(*[runtime get], jsObject);
} else {
_weakObject = jsObject;
}
#endif
}
return self;
}
- (nullable EXJavaScriptObject *)lock
{
jsi::Runtime *runtime = [_runtime get];
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
jsi::Value value = _weakObject->lock(*runtime);
// `lock` returns an undefined value if the underlying object no longer exists.
if (value.isUndefined()) {
return nil;
}
std::shared_ptr<jsi::Object> objectPtr = std::make_shared<jsi::Object>(value.asObject(*runtime));
#else
std::shared_ptr<jsi::Object> objectPtr = expo::isWeakRefSupported(*runtime)
? expo::derefWeakRef(*runtime, _weakObject)
: _weakObject;
#endif
if (!objectPtr) {
return nil;
}
return [[EXJavaScriptObject alloc] initWith:objectPtr runtime:_runtime];
}
@end

View File

@@ -0,0 +1,31 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#import <jsi/jsi.h>
namespace jsi = facebook::jsi;
#endif // __cplusplus
#import <ExpoModulesJSI/EXArrayBuffer.h>
/**
* Native ArrayBuffer implementation that manages its own memory allocation.
* This is used for ArrayBuffers created from native code that own their memory.
*/
NS_SWIFT_NAME(RawNativeArrayBuffer)
@interface EXNativeArrayBuffer : NSObject<EXArrayBuffer>
- (nonnull instancetype)initWithData:(uint8_t*_Nonnull)data
size:(size_t)size
cleanup:(void (^_Nonnull)(void))cleanup
NS_SWIFT_NAME(init(data:size:cleanup:));
#ifdef __cplusplus
/**
Returns a shared pointer to the underlying memory that can be used to create a JSI ArrayBuffer.
*/
- (std::shared_ptr<jsi::MutableBuffer>)jsiBuffer;
#endif
@end

View File

@@ -0,0 +1,41 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXNativeArrayBuffer.h>
#import <ExpoModulesJSI/MemoryBuffer.h>
@implementation EXNativeArrayBuffer {
std::shared_ptr<expo::MemoryBuffer> _buffer;
}
- (nonnull instancetype)initWithData:(uint8_t*)data
size:(size_t)size
cleanup:(void (^)(void))cleanup
{
if (self = [super init]) {
expo::CleanupFunc cleanupFn = [=]() { cleanup(); };
_buffer = std::make_shared<expo::MemoryBuffer>(data, size, std::move(cleanupFn));
}
return self;
}
- (std::shared_ptr<jsi::MutableBuffer>)jsiBuffer
{
return _buffer;
}
- (size_t)getSize
{
return _buffer->size();
}
- (nonnull void *)getUnsafeMutableRawPointer
{
return _buffer->data();
}
- (EXArrayBufferStrongRef * _Nullable)memoryStrongRef
{
return [[EXArrayBufferStrongRef alloc] initWith:_buffer];
}
@end

View File

@@ -0,0 +1,15 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesJSI/EXArrayBuffer.h>
#import <ExpoModulesJSI/EXJavaScriptObject.h>
/**
* JavaScript ArrayBuffer implementation that wraps a JSI ArrayBuffer.
* This provides access to ArrayBuffers created in JavaScript from native code.
*/
NS_SWIFT_NAME(RawJavaScriptArrayBuffer)
@interface EXJavaScriptArrayBuffer : EXJavaScriptObject<EXArrayBuffer>
@end

View File

@@ -0,0 +1,44 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
#import <ExpoModulesJSI/EXRawJavaScriptArrayBuffer.h>
@implementation EXJavaScriptArrayBuffer {
__weak EXJavaScriptRuntime *_runtime;
std::shared_ptr<jsi::ArrayBuffer> _jsiBuffer;
}
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObjectPtr
runtime:(EXJavaScriptRuntime *)runtime
{
jsi::Runtime *rt = [runtime get];
if (!jsObjectPtr.get()->isArrayBuffer(*rt)) {
throw std::runtime_error("Object is not an ArrayBuffer");
}
if (self = [super initWith:jsObjectPtr runtime:runtime]) {
_runtime = runtime;
_jsiBuffer = std::make_shared<jsi::ArrayBuffer>(jsObjectPtr.get()->getArrayBuffer(*rt));
}
return self;
}
- (size_t)getSize
{
return _jsiBuffer->size(*[_runtime get]);
}
- (nonnull void *)getUnsafeMutableRawPointer
{
return _jsiBuffer->data(*[_runtime get]);
}
- (EXArrayBufferStrongRef * _Nullable)memoryStrongRef
{
// JavaScript ArrayBuffers don't provide direct strong references to the underlying
// memory since the JS runtime owns the memory and manages its lifetime.
return nullptr;
}
@end

View File

@@ -0,0 +1,25 @@
// Copyright 2023-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesJSI/EXJavaScriptRuntime.h>
#ifdef __cplusplus
#import <jsi/jsi.h>
namespace jsi = facebook::jsi;
#endif // __cplusplus
NS_SWIFT_SENDABLE
NS_SWIFT_NAME(RawJavaScriptFunction)
@interface EXRawJavaScriptFunction : NSObject
#ifdef __cplusplus
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Function>)function
runtime:(nonnull EXJavaScriptRuntime *)runtime;
#endif // __cplusplus
- (nonnull EXJavaScriptValue *)callWithArguments:(nonnull NSArray<id> *)arguments
thisObject:(nullable EXJavaScriptObject *)thisObject
asConstructor:(BOOL)asConstructor;
@end

View File

@@ -0,0 +1,51 @@
// Copyright 2023-present 650 Industries. All rights reserved.
#import <ExpoModulesJSI/EXJSIConversions.h>
#import <ExpoModulesJSI/EXRawJavaScriptFunction.h>
@implementation EXRawJavaScriptFunction {
/**
Pointer to the `EXJavaScriptRuntime` wrapper.
\note It must be weak because only then the original runtime can be safely deallocated
when the JS engine wants to without unsetting it on each created object.
*/
__weak EXJavaScriptRuntime *_runtime;
/**
Shared pointer to the underlying JSI function.
*/
std::shared_ptr<jsi::Function> _function;
}
- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Function>)function
runtime:(nonnull EXJavaScriptRuntime *)runtime
{
if (self = [super init]) {
_runtime = runtime;
_function = function;
}
return self;
}
- (nonnull EXJavaScriptValue *)callWithArguments:(nonnull NSArray<id> *)arguments
thisObject:(nullable EXJavaScriptObject *)thisObject
asConstructor:(BOOL)asConstructor
{
jsi::Runtime *runtime = [_runtime get];
std::vector<jsi::Value> vector = expo::convertNSArrayToStdVector(*runtime, arguments);
const jsi::Value *data = vector.data();
jsi::Value result;
if (asConstructor) {
result = _function->callAsConstructor(*runtime, data, arguments.count);
} else if (thisObject) {
result = _function->callWithThis(*runtime, *[thisObject get], data, arguments.count);
} else {
result = _function->call(*runtime, data, arguments.count);
}
return [[EXJavaScriptValue alloc] initWithRuntime:_runtime value:std::move(result)];
}
@end

View File

@@ -0,0 +1,22 @@
// Copyright 2025-present 650 Industries. All rights reserved.
#import <cstdint>
#import <ExpoModulesJSI/EXStringUtils.h>
namespace expo {
bool isASCIIAndNotNull(const uint8_t c) {
constexpr uint32_t asciiMask = 0x7f;
return ((c & static_cast<uint8_t>(~asciiMask)) == 0 && c != 0);
}
bool isAllASCIIAndNotNull(const uint8_t * _Nonnull begin, const uint8_t * _Nonnull end) {
while (begin < end) {
if (!isASCIIAndNotNull(*begin))
return false;
++begin;
}
return true;
}
} // namespace expo

13
node_modules/expo-modules-core/ios/JSI/EXStringUtils.h generated vendored Normal file
View File

@@ -0,0 +1,13 @@
// Copyright 2025-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
namespace expo {
bool isASCIIAndNotNull(const uint8_t c);
bool isAllASCIIAndNotNull(const uint8_t * _Nonnull begin, const uint8_t * _Nonnull end);
} // namespace expo
#endif

View File

@@ -0,0 +1,14 @@
// Copyright 2023-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <functional>
class MainThreadInvoker {
public:
static void invokeOnMainThread(const std::function<void()> task);
};
#endif // __cplusplus

View File

@@ -0,0 +1,19 @@
// Copyright 2023-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#import <ExpoModulesJSI/MainThreadInvoker.h>
#import <Foundation/Foundation.h>
void MainThreadInvoker::invokeOnMainThread(const std::function<void()> task) {
dispatch_block_t block = [task]() {
task();
};
if ([NSThread isMainThread]) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
#endif // __cplusplus

View File

@@ -0,0 +1,46 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <ReactCommon/CallInvoker.h>
#include <ExpoModulesJSI/MainThreadInvoker.h>
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
/**
* Dummy CallInvoker.
* Async functions are invoked on the main thread on iOS.
* Used in the test environment to check the async flow.
*/
class TestingJSCallInvoker : public react::CallInvoker {
public:
explicit TestingJSCallInvoker(const std::shared_ptr<jsi::Runtime>& runtime) : runtime(runtime) {}
void invokeAsync(react::CallFunc &&func) noexcept override {
auto weakRuntime = runtime;
std::function<void()> mainThreadFunc = [weakRuntime, func]() {
auto strongRuntime = weakRuntime.lock();
func(*strongRuntime);
};
MainThreadInvoker::invokeOnMainThread(mainThreadFunc);
}
void invokeSync(react::CallFunc &&func) override {
func(*runtime.lock());
}
~TestingJSCallInvoker() override = default;
std::weak_ptr<jsi::Runtime> runtime;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,17 @@
// Copyright 2025-present 650 Industries. All rights reserved.
import Testing
@testable import ExpoModulesJSI
@Suite
struct JavaScriptRuntimeTests {
@Test
func `initializes`() {
// Just check it does not crash
let runtime = JavaScriptRuntime()
runtime.global()
}
// TODO: Move tests from ExpoModulesCore/JavaScriptRuntimeSpec and add more
}

View File

@@ -0,0 +1,23 @@
// Copyright 2025-present 650 Industries. All rights reserved.
import Testing
@testable import ExpoModulesJSI
@Suite
struct JavaScriptValueTests {
@Test
func `static undefined`() {
#expect(JavaScriptValue.undefined.isUndefined() == true)
#expect(JavaScriptValue.undefined.isNull() == false)
#expect(JavaScriptValue.undefined.isString() == false)
}
@Test
func `static null`() {
#expect(JavaScriptValue.null.isUndefined() == false)
#expect(JavaScriptValue.null.isNull() == true)
#expect(JavaScriptValue.null.isString() == false)
}
// TODO: Move tests from ExpoModulesCore/JavaScriptValueSpec and add more
}