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