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#pragma mark - Accessing object properties 41 42- (BOOL)hasProperty:(nonnull NSString *)name 43{ 44 return _jsObjectPtr->hasProperty(*[_runtime get], [name UTF8String]); 45} 46 47- (nonnull EXJavaScriptValue *)getProperty:(nonnull NSString *)name 48{ 49 std::shared_ptr<jsi::Value> value = std::make_shared<jsi::Value>(_jsObjectPtr->getProperty(*[_runtime get], [name UTF8String])); 50 return [[EXJavaScriptValue alloc] initWithRuntime:_runtime value:value]; 51} 52 53- (nonnull NSArray<NSString *> *)getPropertyNames 54{ 55 jsi::Runtime *runtime = [_runtime get]; 56 jsi::Array propertyNamesArray = _jsObjectPtr->getPropertyNames(*[_runtime get]); 57 return expo::convertJSIArrayToNSArray(*runtime, propertyNamesArray, nullptr); 58} 59 60#pragma mark - Modifying object properties 61 62- (void)setProperty:(nonnull NSString *)name value:(nullable id)value 63{ 64 jsi::Value jsiValue = expo::convertObjCObjectToJSIValue(*[_runtime get], value); 65 _jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], jsiValue); 66} 67 68- (void)defineProperty:(nonnull NSString *)name descriptor:(nonnull EXJavaScriptObject *)descriptor 69{ 70 jsi::Runtime *runtime = [_runtime get]; 71 jsi::Object global = runtime->global(); 72 jsi::Object objectClass = global.getPropertyAsObject(*runtime, "Object"); 73 jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(*runtime, "defineProperty"); 74 75 // This call is basically the same as `Object.defineProperty(object, name, descriptor)` in JS 76 definePropertyFunction.callWithThis(*runtime, objectClass, { 77 jsi::Value(*runtime, *_jsObjectPtr.get()), 78 jsi::String::createFromUtf8(*runtime, [name UTF8String]), 79 std::move(*[descriptor get]), 80 }); 81} 82 83- (void)defineProperty:(nonnull NSString *)name value:(nullable id)value options:(EXJavaScriptObjectPropertyDescriptor)options 84{ 85 jsi::Runtime *runtime = [_runtime get]; 86 jsi::Object global = runtime->global(); 87 jsi::Object objectClass = global.getPropertyAsObject(*runtime, "Object"); 88 jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(*runtime, "defineProperty"); 89 jsi::Object descriptor = [self preparePropertyDescriptorWithOptions:options]; 90 91 descriptor.setProperty(*runtime, "value", expo::convertObjCObjectToJSIValue(*runtime, value)); 92 93 // This call is basically the same as `Object.defineProperty(object, name, descriptor)` in JS 94 definePropertyFunction.callWithThis(*runtime, objectClass, { 95 jsi::Value(*runtime, *_jsObjectPtr.get()), 96 jsi::String::createFromUtf8(*runtime, [name UTF8String]), 97 std::move(descriptor), 98 }); 99} 100 101#pragma mark - WeakObject 102 103- (nonnull EXJavaScriptWeakObject *)createWeak 104{ 105 return [[EXJavaScriptWeakObject alloc] initWith:_jsObjectPtr runtime:_runtime]; 106} 107 108#pragma mark - Deallocator 109 110- (void)setObjectDeallocator:(void (^)(void))deallocatorBlock 111{ 112 expo::setDeallocator(*[_runtime get], _jsObjectPtr, deallocatorBlock); 113} 114 115#pragma mark - Equality 116 117- (BOOL)isEqual:(id)object 118{ 119 if ([object isKindOfClass:EXJavaScriptObject.class]) { 120 jsi::Runtime *runtime = [_runtime get]; 121 jsi::Object *a = _jsObjectPtr.get(); 122 jsi::Object *b = [object get]; 123 return jsi::Object::strictEquals(*runtime, *a, *b); 124 } 125 return false; 126} 127 128#pragma mark - Private helpers 129 130- (jsi::Object)preparePropertyDescriptorWithOptions:(EXJavaScriptObjectPropertyDescriptor)options 131{ 132 jsi::Runtime *runtime = [_runtime get]; 133 jsi::Object descriptor(*runtime); 134 descriptor.setProperty(*runtime, "configurable", (bool)(options & EXJavaScriptObjectPropertyDescriptorConfigurable)); 135 descriptor.setProperty(*runtime, "enumerable", (bool)(options & EXJavaScriptObjectPropertyDescriptorEnumerable)); 136 descriptor.setProperty(*runtime, "writable", (bool)(options & EXJavaScriptObjectPropertyDescriptorWritable)); 137 return descriptor; 138} 139 140@end 141