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