1// Copyright 2022-present 650 Industries. All rights reserved. 2 3#import <ExpoModulesCore/EXJSIConversions.h> 4#import <ExpoModulesCore/EXJavaScriptValue.h> 5#import <ExpoModulesCore/EXJavaScriptObject.h> 6#import <ExpoModulesCore/EXJavaScriptRuntime.h> 7#import <ExpoModulesCore/EXJavaScriptWeakObject.h> 8#import <ExpoModulesCore/EXJSIUtils.h> 9 10@implementation EXJavaScriptObject { 11 /** 12 Pointer to the `EXJavaScriptRuntime` wrapper. 13 14 \note It must be weak because only then the original runtime can be safely deallocated 15 when the JS engine wants to without unsetting it on each created object. 16 */ 17 __weak EXJavaScriptRuntime *_runtime; 18 19 /** 20 Shared pointer to the original JSI object that is being wrapped by `EXJavaScriptObject` class. 21 */ 22 std::shared_ptr<jsi::Object> _jsObjectPtr; 23} 24 25- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObjectPtr 26 runtime:(nonnull EXJavaScriptRuntime *)runtime 27{ 28 if (self = [super init]) { 29 _runtime = runtime; 30 _jsObjectPtr = jsObjectPtr; 31 } 32 return self; 33} 34 35- (nonnull jsi::Object *)get 36{ 37 return _jsObjectPtr.get(); 38} 39 40- (std::shared_ptr<jsi::Object>)getShared 41{ 42 return _jsObjectPtr; 43} 44 45#pragma mark - Accessing object properties 46 47- (BOOL)hasProperty:(nonnull NSString *)name 48{ 49 return _jsObjectPtr->hasProperty(*[_runtime get], [name UTF8String]); 50} 51 52- (nonnull EXJavaScriptValue *)getProperty:(nonnull NSString *)name 53{ 54 std::shared_ptr<jsi::Value> value = std::make_shared<jsi::Value>(_jsObjectPtr->getProperty(*[_runtime get], [name UTF8String])); 55 return [[EXJavaScriptValue alloc] initWithRuntime:_runtime value:value]; 56} 57 58- (nonnull NSArray<NSString *> *)getPropertyNames 59{ 60 jsi::Runtime *runtime = [_runtime get]; 61 jsi::Array propertyNamesArray = _jsObjectPtr->getPropertyNames(*[_runtime get]); 62 return expo::convertJSIArrayToNSArray(*runtime, propertyNamesArray, nullptr); 63} 64 65#pragma mark - Modifying object properties 66 67- (void)setProperty:(nonnull NSString *)name value:(nullable id)value 68{ 69 jsi::Value jsiValue = expo::convertObjCObjectToJSIValue(*[_runtime get], value); 70 _jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], jsiValue); 71} 72 73- (void)defineProperty:(nonnull NSString *)name descriptor:(nonnull EXJavaScriptObject *)descriptor 74{ 75 jsi::Runtime *runtime = [_runtime get]; 76 jsi::Object global = runtime->global(); 77 jsi::Object objectClass = global.getPropertyAsObject(*runtime, "Object"); 78 jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(*runtime, "defineProperty"); 79 80 // This call is basically the same as `Object.defineProperty(object, name, descriptor)` in JS 81 definePropertyFunction.callWithThis(*runtime, objectClass, { 82 jsi::Value(*runtime, *_jsObjectPtr.get()), 83 jsi::String::createFromUtf8(*runtime, [name UTF8String]), 84 std::move(*[descriptor get]), 85 }); 86} 87 88- (void)defineProperty:(nonnull NSString *)name value:(nullable id)value options:(EXJavaScriptObjectPropertyDescriptor)options 89{ 90 jsi::Runtime *runtime = [_runtime get]; 91 jsi::Object global = runtime->global(); 92 jsi::Object objectClass = global.getPropertyAsObject(*runtime, "Object"); 93 jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(*runtime, "defineProperty"); 94 jsi::Object descriptor = [self preparePropertyDescriptorWithOptions:options]; 95 96 descriptor.setProperty(*runtime, "value", expo::convertObjCObjectToJSIValue(*runtime, value)); 97 98 // This call is basically the same as `Object.defineProperty(object, name, descriptor)` in JS 99 definePropertyFunction.callWithThis(*runtime, objectClass, { 100 jsi::Value(*runtime, *_jsObjectPtr.get()), 101 jsi::String::createFromUtf8(*runtime, [name UTF8String]), 102 std::move(descriptor), 103 }); 104} 105 106#pragma mark - WeakObject 107 108- (nonnull EXJavaScriptWeakObject *)createWeak 109{ 110 return [[EXJavaScriptWeakObject alloc] initWith:_jsObjectPtr runtime:_runtime]; 111} 112 113#pragma mark - Deallocator 114 115- (void)setObjectDeallocator:(void (^)(void))deallocatorBlock 116{ 117 expo::setDeallocator(*[_runtime get], _jsObjectPtr, deallocatorBlock); 118} 119 120#pragma mark - Equality 121 122- (BOOL)isEqual:(id)object 123{ 124 if ([object isKindOfClass:EXJavaScriptObject.class]) { 125 jsi::Runtime *runtime = [_runtime get]; 126 jsi::Object *a = _jsObjectPtr.get(); 127 jsi::Object *b = [object get]; 128 return jsi::Object::strictEquals(*runtime, *a, *b); 129 } 130 return false; 131} 132 133#pragma mark - Private helpers 134 135- (jsi::Object)preparePropertyDescriptorWithOptions:(EXJavaScriptObjectPropertyDescriptor)options 136{ 137 jsi::Runtime *runtime = [_runtime get]; 138 jsi::Object descriptor(*runtime); 139 descriptor.setProperty(*runtime, "configurable", (bool)(options & EXJavaScriptObjectPropertyDescriptorConfigurable)); 140 descriptor.setProperty(*runtime, "enumerable", (bool)(options & EXJavaScriptObjectPropertyDescriptorEnumerable)); 141 descriptor.setProperty(*runtime, "writable", (bool)(options & EXJavaScriptObjectPropertyDescriptorWritable)); 142 return descriptor; 143} 144 145@end 146