1// Copyright 2022-present 650 Industries. All rights reserved. 2 3#import <ExpoModulesCore/EXJSIConversions.h> 4#import <ExpoModulesCore/EXJavaScriptObject.h> 5#import <ExpoModulesCore/EXJavaScriptRuntime.h> 6 7@implementation EXJavaScriptObject { 8 /** 9 Pointer to the `EXJavaScriptRuntime` wrapper. 10 11 \note It must be weak because only then the original runtime can be safely deallocated 12 when the JS engine wants to without unsetting it on each created object. 13 */ 14 __weak EXJavaScriptRuntime *_runtime; 15 16 /** 17 Shared pointer to the original JSI object that is being wrapped by `EXJavaScriptObject` class. 18 */ 19 std::shared_ptr<jsi::Object> _jsObjectPtr; 20} 21 22- (nonnull instancetype)initWith:(std::shared_ptr<jsi::Object>)jsObjectPtr 23 runtime:(nonnull EXJavaScriptRuntime *)runtime 24{ 25 if (self = [super init]) { 26 _runtime = runtime; 27 _jsObjectPtr = jsObjectPtr; 28 } 29 return self; 30} 31 32- (nonnull jsi::Object *)get 33{ 34 return _jsObjectPtr.get(); 35} 36 37#pragma mark - Accessing object properties 38 39- (BOOL)hasProperty:(nonnull NSString *)name 40{ 41 return _jsObjectPtr->hasProperty(*[_runtime get], [name UTF8String]); 42} 43 44- (nonnull EXJavaScriptValue *)getProperty:(nonnull NSString *)name 45{ 46 std::shared_ptr<jsi::Value> value = std::make_shared<jsi::Value>(_jsObjectPtr->getProperty(*[_runtime get], [name UTF8String])); 47 return [[EXJavaScriptValue alloc] initWithRuntime:_runtime value:value]; 48} 49 50- (nonnull NSArray<NSString *> *)getPropertyNames 51{ 52 jsi::Runtime *runtime = [_runtime get]; 53 jsi::Array propertyNamesArray = _jsObjectPtr->getPropertyNames(*[_runtime get]); 54 return expo::convertJSIArrayToNSArray(*runtime, propertyNamesArray, nullptr); 55} 56 57#pragma mark - Modifying object properties 58 59- (void)setProperty:(nonnull NSString *)name value:(nullable id)value 60{ 61 jsi::Value jsiValue = expo::convertObjCObjectToJSIValue(*[_runtime get], value); 62 _jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], jsiValue); 63} 64 65- (void)defineProperty:(nonnull NSString *)name value:(nullable id)value options:(EXJavaScriptObjectPropertyDescriptor)options 66{ 67 jsi::Runtime *runtime = [_runtime get]; 68 jsi::Object global = runtime->global(); 69 jsi::Object objectClass = global.getPropertyAsObject(*runtime, "Object"); 70 jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(*runtime, "defineProperty"); 71 jsi::Object descriptor = [self preparePropertyDescriptorWithOptions:options]; 72 73 descriptor.setProperty(*runtime, "value", expo::convertObjCObjectToJSIValue(*runtime, value)); 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), 80 }); 81} 82 83#pragma mark - Functions 84 85- (void)setAsyncFunction:(nonnull NSString *)name 86 argsCount:(NSInteger)argsCount 87 block:(nonnull JSAsyncFunctionBlock)block 88{ 89 if (!_runtime) { 90 NSLog(@"Cannot set '%@' async function when the EXJavaScript runtime is no longer available.", name); 91 return; 92 } 93 jsi::Function function = [_runtime createAsyncFunction:name argsCount:argsCount block:block]; 94 _jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], function); 95} 96 97- (void)setSyncFunction:(nonnull NSString *)name 98 argsCount:(NSInteger)argsCount 99 block:(nonnull JSSyncFunctionBlock)block 100{ 101 if (!_runtime) { 102 NSLog(@"Cannot set '%@' sync function when the EXJavaScript runtime is no longer available.", name); 103 return; 104 } 105 jsi::Function function = [_runtime createSyncFunction:name argsCount:argsCount block:block]; 106 _jsObjectPtr->setProperty(*[_runtime get], [name UTF8String], function); 107} 108 109#pragma mark - Private helpers 110 111- (jsi::Object)preparePropertyDescriptorWithOptions:(EXJavaScriptObjectPropertyDescriptor)options 112{ 113 jsi::Runtime *runtime = [_runtime get]; 114 jsi::Object descriptor(*runtime); 115 descriptor.setProperty(*runtime, "configurable", (bool)(options & EXJavaScriptObjectPropertyDescriptorConfigurable)); 116 descriptor.setProperty(*runtime, "enumerable", (bool)(options & EXJavaScriptObjectPropertyDescriptorEnumerable)); 117 descriptor.setProperty(*runtime, "writable", (bool)(options & EXJavaScriptObjectPropertyDescriptorWritable)); 118 return descriptor; 119} 120 121@end 122