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