objc-js 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,82 @@
1
+ #ifndef PROTOCOL_STORAGE_H
2
+ #define PROTOCOL_STORAGE_H
3
+
4
+ #include <condition_variable>
5
+ #include <mutex>
6
+ #include <napi.h>
7
+ #include <pthread.h>
8
+ #include <string>
9
+ #include <unordered_map>
10
+
11
+ // Forward declarations for Objective-C types
12
+ #ifdef __OBJC__
13
+ @class NSInvocation;
14
+ #else
15
+ typedef struct NSInvocation NSInvocation;
16
+ #endif
17
+
18
+ // MARK: - Data Structures
19
+
20
+ // Data passed from native thread to JS thread for invocation handling
21
+ struct InvocationData {
22
+ NSInvocation *invocation;
23
+ std::string selectorName;
24
+ std::string typeEncoding;
25
+ // Synchronization: we use NonBlockingCall + runloop pumping to avoid
26
+ // deadlocks in Electron while still getting return values
27
+ std::mutex *completionMutex;
28
+ std::condition_variable *completionCv;
29
+ bool *isComplete;
30
+ };
31
+
32
+ // Stores information about a protocol implementation instance
33
+ struct ProtocolImplementation {
34
+ // ThreadSafeFunction for each selector - allows calling JS from any thread
35
+ std::unordered_map<std::string, Napi::ThreadSafeFunction> callbacks;
36
+ // Original JS functions for direct calls (kept alive by persistent refs)
37
+ std::unordered_map<std::string, Napi::FunctionReference> jsCallbacks;
38
+ // Type encodings for each selector
39
+ std::unordered_map<std::string, std::string> typeEncodings;
40
+ // Dynamically generated class name
41
+ std::string className;
42
+ // Store the environment for direct calls
43
+ napi_env env;
44
+ // Store the JS thread ID for thread detection
45
+ pthread_t js_thread;
46
+ // Flag to indicate if running in Electron (requires TSFN path always)
47
+ bool isElectron;
48
+ };
49
+
50
+ // MARK: - Global Storage
51
+
52
+ // Global map: instance pointer -> implementation details
53
+ // This keeps JavaScript callbacks alive for the lifetime of the Objective-C
54
+ // object
55
+ extern std::unordered_map<void *, ProtocolImplementation> g_implementations;
56
+ extern std::mutex g_implementations_mutex;
57
+
58
+ // MARK: - Storage Access Helpers
59
+
60
+ // Helper to signal completion of an invocation
61
+ inline void SignalInvocationComplete(InvocationData *data) {
62
+ if (data->completionMutex && data->completionCv && data->isComplete) {
63
+ std::lock_guard<std::mutex> lock(*data->completionMutex);
64
+ *data->isComplete = true;
65
+ data->completionCv->notify_one();
66
+ }
67
+ }
68
+
69
+ // Look up implementation for an instance pointer
70
+ // Returns nullptr if not found
71
+ // Caller must hold g_implementations_mutex
72
+ inline ProtocolImplementation *
73
+ FindImplementation(void *instancePtr) {
74
+ auto it = g_implementations.find(instancePtr);
75
+ if (it != g_implementations.end()) {
76
+ return &it->second;
77
+ }
78
+ return nullptr;
79
+ }
80
+
81
+ #endif // PROTOCOL_STORAGE_H
82
+
@@ -0,0 +1,612 @@
1
+ #ifndef TYPE_CONVERSION_H
2
+ #define TYPE_CONVERSION_H
3
+
4
+ #include "ObjcObject.h"
5
+ #include <Foundation/Foundation.h>
6
+ #include <napi.h>
7
+ #include <objc/runtime.h>
8
+ #include <string>
9
+
10
+ // MARK: - Type Encoding Utilities
11
+
12
+ // Helper class to manage the lifetime of simplified type encodings
13
+ class SimplifiedTypeEncoding {
14
+ private:
15
+ std::string simplified;
16
+
17
+ public:
18
+ SimplifiedTypeEncoding(const char *typeEncoding) : simplified(typeEncoding) {
19
+ // Remove any leading qualifiers (r=const, n=in, N=inout, o=out, O=bycopy,
20
+ // R=byref, V=oneway)
21
+ while (!simplified.empty() &&
22
+ (simplified[0] == 'r' || simplified[0] == 'n' ||
23
+ simplified[0] == 'N' || simplified[0] == 'o' ||
24
+ simplified[0] == 'O' || simplified[0] == 'R' ||
25
+ simplified[0] == 'V')) {
26
+ simplified.erase(0, 1);
27
+ }
28
+ }
29
+
30
+ const char *c_str() const { return simplified.c_str(); }
31
+ char operator[](size_t index) const { return simplified[index]; }
32
+ operator const char *() const { return simplified.c_str(); }
33
+ };
34
+
35
+ // Legacy function for compatibility - returns pointer to internal string
36
+ // WARNING: The returned pointer is only valid as long as the typeEncoding
37
+ // parameter is valid
38
+ inline const char *SimplifyTypeEncoding(const char *typeEncoding) {
39
+ // For simple cases where there are no qualifiers, return the original pointer
40
+ if (typeEncoding && typeEncoding[0] != 'r' && typeEncoding[0] != 'n' &&
41
+ typeEncoding[0] != 'N' && typeEncoding[0] != 'o' &&
42
+ typeEncoding[0] != 'O' && typeEncoding[0] != 'R' &&
43
+ typeEncoding[0] != 'V') {
44
+ return typeEncoding;
45
+ }
46
+
47
+ // For complex cases, we need to skip qualifiers
48
+ // This is a temporary fix - callers should use SimplifiedTypeEncoding class
49
+ static thread_local std::string buffer;
50
+ buffer = typeEncoding;
51
+ while (!buffer.empty() &&
52
+ (buffer[0] == 'r' || buffer[0] == 'n' || buffer[0] == 'N' ||
53
+ buffer[0] == 'o' || buffer[0] == 'O' || buffer[0] == 'R' ||
54
+ buffer[0] == 'V')) {
55
+ buffer.erase(0, 1);
56
+ }
57
+ return buffer.c_str();
58
+ }
59
+
60
+ // MARK: - ObjC to JS Conversion
61
+
62
+ // Convert an Objective-C value (from a pointer) to a JavaScript value
63
+ inline Napi::Value ObjCToJS(Napi::Env env, void *valuePtr, char typeCode) {
64
+ switch (typeCode) {
65
+ case 'c': {
66
+ char value = *(char *)valuePtr;
67
+ return Napi::Number::New(env, value);
68
+ }
69
+ case 'i': {
70
+ int value = *(int *)valuePtr;
71
+ return Napi::Number::New(env, value);
72
+ }
73
+ case 's': {
74
+ short value = *(short *)valuePtr;
75
+ return Napi::Number::New(env, value);
76
+ }
77
+ case 'l': {
78
+ long value = *(long *)valuePtr;
79
+ return Napi::Number::New(env, value);
80
+ }
81
+ case 'q': {
82
+ long long value = *(long long *)valuePtr;
83
+ return Napi::Number::New(env, value);
84
+ }
85
+ case 'C': {
86
+ unsigned char value = *(unsigned char *)valuePtr;
87
+ return Napi::Number::New(env, value);
88
+ }
89
+ case 'I': {
90
+ unsigned int value = *(unsigned int *)valuePtr;
91
+ return Napi::Number::New(env, value);
92
+ }
93
+ case 'S': {
94
+ unsigned short value = *(unsigned short *)valuePtr;
95
+ return Napi::Number::New(env, value);
96
+ }
97
+ case 'L': {
98
+ unsigned long value = *(unsigned long *)valuePtr;
99
+ return Napi::Number::New(env, value);
100
+ }
101
+ case 'Q': {
102
+ unsigned long long value = *(unsigned long long *)valuePtr;
103
+ return Napi::Number::New(env, value);
104
+ }
105
+ case 'f': {
106
+ float value = *(float *)valuePtr;
107
+ return Napi::Number::New(env, value);
108
+ }
109
+ case 'd': {
110
+ double value = *(double *)valuePtr;
111
+ return Napi::Number::New(env, value);
112
+ }
113
+ case 'B': {
114
+ bool value = *(bool *)valuePtr;
115
+ return Napi::Boolean::New(env, value);
116
+ }
117
+ case '*': {
118
+ char *value = *(char **)valuePtr;
119
+ if (value == nullptr) {
120
+ return env.Null();
121
+ }
122
+ return Napi::String::New(env, value);
123
+ }
124
+ case '@': {
125
+ id value = *(__strong id *)valuePtr;
126
+ if (value == nil) {
127
+ return env.Null();
128
+ }
129
+ return ObjcObject::NewInstance(env, value);
130
+ }
131
+ case '#': {
132
+ Class value = *(Class *)valuePtr;
133
+ if (value == nil) {
134
+ return env.Null();
135
+ }
136
+ return ObjcObject::NewInstance(env, value);
137
+ }
138
+ case ':': {
139
+ SEL value = *(SEL *)valuePtr;
140
+ if (value == nullptr) {
141
+ return env.Null();
142
+ }
143
+ NSString *selectorString = NSStringFromSelector(value);
144
+ if (selectorString == nil) {
145
+ return env.Null();
146
+ }
147
+ return Napi::String::New(env, [selectorString UTF8String]);
148
+ }
149
+ case 'v':
150
+ return env.Undefined();
151
+ default:
152
+ return env.Undefined();
153
+ }
154
+ }
155
+
156
+ // Extract an argument from NSInvocation and convert to JS value
157
+ inline Napi::Value ExtractInvocationArgumentToJS(Napi::Env env,
158
+ NSInvocation *invocation,
159
+ NSUInteger index,
160
+ char typeCode) {
161
+ switch (typeCode) {
162
+ case 'c': {
163
+ char value;
164
+ [invocation getArgument:&value atIndex:index];
165
+ return Napi::Number::New(env, value);
166
+ }
167
+ case 'i': {
168
+ int value;
169
+ [invocation getArgument:&value atIndex:index];
170
+ return Napi::Number::New(env, value);
171
+ }
172
+ case 's': {
173
+ short value;
174
+ [invocation getArgument:&value atIndex:index];
175
+ return Napi::Number::New(env, value);
176
+ }
177
+ case 'l': {
178
+ long value;
179
+ [invocation getArgument:&value atIndex:index];
180
+ return Napi::Number::New(env, value);
181
+ }
182
+ case 'q': {
183
+ long long value;
184
+ [invocation getArgument:&value atIndex:index];
185
+ return Napi::Number::New(env, value);
186
+ }
187
+ case 'C': {
188
+ unsigned char value;
189
+ [invocation getArgument:&value atIndex:index];
190
+ return Napi::Number::New(env, value);
191
+ }
192
+ case 'I': {
193
+ unsigned int value;
194
+ [invocation getArgument:&value atIndex:index];
195
+ return Napi::Number::New(env, value);
196
+ }
197
+ case 'S': {
198
+ unsigned short value;
199
+ [invocation getArgument:&value atIndex:index];
200
+ return Napi::Number::New(env, value);
201
+ }
202
+ case 'L': {
203
+ unsigned long value;
204
+ [invocation getArgument:&value atIndex:index];
205
+ return Napi::Number::New(env, value);
206
+ }
207
+ case 'Q': {
208
+ unsigned long long value;
209
+ [invocation getArgument:&value atIndex:index];
210
+ return Napi::Number::New(env, value);
211
+ }
212
+ case 'f': {
213
+ float value;
214
+ [invocation getArgument:&value atIndex:index];
215
+ return Napi::Number::New(env, value);
216
+ }
217
+ case 'd': {
218
+ double value;
219
+ [invocation getArgument:&value atIndex:index];
220
+ return Napi::Number::New(env, value);
221
+ }
222
+ case 'B': {
223
+ bool value;
224
+ [invocation getArgument:&value atIndex:index];
225
+ return Napi::Boolean::New(env, value);
226
+ }
227
+ case '*': {
228
+ char *value;
229
+ [invocation getArgument:&value atIndex:index];
230
+ if (value == nullptr) {
231
+ return env.Null();
232
+ }
233
+ return Napi::String::New(env, value);
234
+ }
235
+ case '@': {
236
+ __unsafe_unretained id value;
237
+ [invocation getArgument:&value atIndex:index];
238
+ if (value == nil) {
239
+ return env.Null();
240
+ }
241
+ return ObjcObject::NewInstance(env, value);
242
+ }
243
+ case '#': {
244
+ Class value;
245
+ [invocation getArgument:&value atIndex:index];
246
+ if (value == nil) {
247
+ return env.Null();
248
+ }
249
+ return ObjcObject::NewInstance(env, value);
250
+ }
251
+ case ':': {
252
+ SEL value;
253
+ [invocation getArgument:&value atIndex:index];
254
+ if (value == nullptr) {
255
+ return env.Null();
256
+ }
257
+ NSString *selString = NSStringFromSelector(value);
258
+ if (selString == nil) {
259
+ return env.Null();
260
+ }
261
+ return Napi::String::New(env, [selString UTF8String]);
262
+ }
263
+ case '^': {
264
+ void *value;
265
+ [invocation getArgument:&value atIndex:index];
266
+ if (value == nullptr) {
267
+ return env.Null();
268
+ }
269
+ return env.Undefined();
270
+ }
271
+ default:
272
+ return env.Undefined();
273
+ }
274
+ }
275
+
276
+ // MARK: - JS to ObjC Return Value Conversion
277
+
278
+ // Set the return value on an NSInvocation from a JS value
279
+ inline void SetInvocationReturnFromJS(NSInvocation *invocation,
280
+ Napi::Value result, char typeCode,
281
+ const char *selectorName) {
282
+ if (result.IsUndefined() || result.IsNull()) {
283
+ // For null/undefined, set nil for object types, skip for others
284
+ if (typeCode == '@') {
285
+ id objcValue = nil;
286
+ [invocation setReturnValue:&objcValue];
287
+ }
288
+ return;
289
+ }
290
+
291
+ auto asSigned64 = [&](int64_t &out) -> bool {
292
+ if (result.IsBoolean()) {
293
+ out = result.As<Napi::Boolean>().Value() ? 1 : 0;
294
+ return true;
295
+ }
296
+ if (result.IsNumber()) {
297
+ out = result.As<Napi::Number>().Int64Value();
298
+ return true;
299
+ }
300
+ return false;
301
+ };
302
+
303
+ auto asUnsigned64 = [&](uint64_t &out) -> bool {
304
+ if (result.IsBoolean()) {
305
+ out = result.As<Napi::Boolean>().Value() ? 1 : 0;
306
+ return true;
307
+ }
308
+ if (result.IsNumber()) {
309
+ out = static_cast<uint64_t>(result.As<Napi::Number>().Int64Value());
310
+ return true;
311
+ }
312
+ return false;
313
+ };
314
+
315
+ auto asDouble = [&](double &out) -> bool {
316
+ if (result.IsBoolean()) {
317
+ out = result.As<Napi::Boolean>().Value() ? 1.0 : 0.0;
318
+ return true;
319
+ }
320
+ if (result.IsNumber()) {
321
+ out = result.As<Napi::Number>().DoubleValue();
322
+ return true;
323
+ }
324
+ return false;
325
+ };
326
+
327
+ switch (typeCode) {
328
+ case 'c': {
329
+ int64_t value = 0;
330
+ if (!asSigned64(value)) {
331
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
332
+ selectorName);
333
+ break;
334
+ }
335
+ char valueChar = static_cast<char>(value);
336
+ [invocation setReturnValue:&valueChar];
337
+ break;
338
+ }
339
+ case 'i': {
340
+ int64_t value = 0;
341
+ if (!asSigned64(value)) {
342
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
343
+ selectorName);
344
+ break;
345
+ }
346
+ int valueInt = static_cast<int>(value);
347
+ [invocation setReturnValue:&valueInt];
348
+ break;
349
+ }
350
+ case 's': {
351
+ int64_t value = 0;
352
+ if (!asSigned64(value)) {
353
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
354
+ selectorName);
355
+ break;
356
+ }
357
+ short valueShort = static_cast<short>(value);
358
+ [invocation setReturnValue:&valueShort];
359
+ break;
360
+ }
361
+ case 'l': {
362
+ int64_t value = 0;
363
+ if (!asSigned64(value)) {
364
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
365
+ selectorName);
366
+ break;
367
+ }
368
+ long valueLong = static_cast<long>(value);
369
+ [invocation setReturnValue:&valueLong];
370
+ break;
371
+ }
372
+ case 'q': {
373
+ int64_t value = 0;
374
+ if (!asSigned64(value)) {
375
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
376
+ selectorName);
377
+ break;
378
+ }
379
+ long long valueLongLong = static_cast<long long>(value);
380
+ [invocation setReturnValue:&valueLongLong];
381
+ break;
382
+ }
383
+ case 'C': {
384
+ uint64_t value = 0;
385
+ if (!asUnsigned64(value)) {
386
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
387
+ selectorName);
388
+ break;
389
+ }
390
+ unsigned char valueChar = static_cast<unsigned char>(value);
391
+ [invocation setReturnValue:&valueChar];
392
+ break;
393
+ }
394
+ case 'I': {
395
+ uint64_t value = 0;
396
+ if (!asUnsigned64(value)) {
397
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
398
+ selectorName);
399
+ break;
400
+ }
401
+ unsigned int valueInt = static_cast<unsigned int>(value);
402
+ [invocation setReturnValue:&valueInt];
403
+ break;
404
+ }
405
+ case 'S': {
406
+ uint64_t value = 0;
407
+ if (!asUnsigned64(value)) {
408
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
409
+ selectorName);
410
+ break;
411
+ }
412
+ unsigned short valueShort = static_cast<unsigned short>(value);
413
+ [invocation setReturnValue:&valueShort];
414
+ break;
415
+ }
416
+ case 'L': {
417
+ uint64_t value = 0;
418
+ if (!asUnsigned64(value)) {
419
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
420
+ selectorName);
421
+ break;
422
+ }
423
+ unsigned long valueLong = static_cast<unsigned long>(value);
424
+ [invocation setReturnValue:&valueLong];
425
+ break;
426
+ }
427
+ case 'Q': {
428
+ uint64_t value = 0;
429
+ if (!asUnsigned64(value)) {
430
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
431
+ selectorName);
432
+ break;
433
+ }
434
+ unsigned long long valueLongLong =
435
+ static_cast<unsigned long long>(value);
436
+ [invocation setReturnValue:&valueLongLong];
437
+ break;
438
+ }
439
+ case 'f': {
440
+ double value = 0;
441
+ if (!asDouble(value)) {
442
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
443
+ selectorName);
444
+ break;
445
+ }
446
+ float valueFloat = static_cast<float>(value);
447
+ [invocation setReturnValue:&valueFloat];
448
+ break;
449
+ }
450
+ case 'd': {
451
+ double value = 0;
452
+ if (!asDouble(value)) {
453
+ NSLog(@"Warning: result is not a number/boolean for selector %s",
454
+ selectorName);
455
+ break;
456
+ }
457
+ [invocation setReturnValue:&value];
458
+ break;
459
+ }
460
+ case 'B': {
461
+ bool value = false;
462
+ if (result.IsBoolean()) {
463
+ value = result.As<Napi::Boolean>().Value();
464
+ } else if (result.IsNumber()) {
465
+ value = result.As<Napi::Number>().Int32Value() != 0;
466
+ } else {
467
+ NSLog(@"Warning: result is not a boolean/number for selector %s",
468
+ selectorName);
469
+ break;
470
+ }
471
+ [invocation setReturnValue:&value];
472
+ break;
473
+ }
474
+ case '@': {
475
+ // Log the result
476
+ NSLog(@"Result for selector %s: %s", selectorName, result.IsObject() ? "Object" : "nil");
477
+
478
+ if (result.IsObject()) {
479
+ Napi::Object resultObj = result.As<Napi::Object>();
480
+ if (resultObj.InstanceOf(ObjcObject::constructor.Value())) {
481
+ ObjcObject *objcObj = Napi::ObjectWrap<ObjcObject>::Unwrap(resultObj);
482
+ id objcValue = objcObj->objcObject;
483
+ NSLog(@"ObjcObject: %@", objcValue);
484
+ [invocation setReturnValue:&objcValue];
485
+ } else {
486
+ NSLog(@"Warning: result object is not an ObjcObject instance");
487
+ }
488
+ } else {
489
+ NSLog(@"Warning: result is not an object (type: %d)", result.Type());
490
+ }
491
+ break;
492
+ }
493
+ default:
494
+ NSLog(@"Warning: Unsupported return type '%c' for selector %s", typeCode,
495
+ selectorName);
496
+ break;
497
+ }
498
+ }
499
+
500
+ // MARK: - Return Value Extraction from NSInvocation
501
+
502
+ // Get return value from NSInvocation and convert to JS
503
+ inline Napi::Value GetInvocationReturnAsJS(Napi::Env env,
504
+ NSInvocation *invocation,
505
+ NSMethodSignature *methodSignature) {
506
+ SimplifiedTypeEncoding returnType([methodSignature methodReturnType]);
507
+
508
+ switch (returnType[0]) {
509
+ case 'c': {
510
+ char result;
511
+ [invocation getReturnValue:&result];
512
+ return Napi::Number::New(env, result);
513
+ }
514
+ case 'i': {
515
+ int result;
516
+ [invocation getReturnValue:&result];
517
+ return Napi::Number::New(env, result);
518
+ }
519
+ case 's': {
520
+ short result;
521
+ [invocation getReturnValue:&result];
522
+ return Napi::Number::New(env, result);
523
+ }
524
+ case 'l': {
525
+ long result;
526
+ [invocation getReturnValue:&result];
527
+ return Napi::Number::New(env, result);
528
+ }
529
+ case 'q': {
530
+ long long result;
531
+ [invocation getReturnValue:&result];
532
+ return Napi::Number::New(env, result);
533
+ }
534
+ case 'C': {
535
+ unsigned char result;
536
+ [invocation getReturnValue:&result];
537
+ return Napi::Number::New(env, result);
538
+ }
539
+ case 'I': {
540
+ unsigned int result;
541
+ [invocation getReturnValue:&result];
542
+ return Napi::Number::New(env, result);
543
+ }
544
+ case 'S': {
545
+ unsigned short result;
546
+ [invocation getReturnValue:&result];
547
+ return Napi::Number::New(env, result);
548
+ }
549
+ case 'L': {
550
+ unsigned long result;
551
+ [invocation getReturnValue:&result];
552
+ return Napi::Number::New(env, result);
553
+ }
554
+ case 'Q': {
555
+ unsigned long long result;
556
+ [invocation getReturnValue:&result];
557
+ return Napi::Number::New(env, result);
558
+ }
559
+ case 'f': {
560
+ float result;
561
+ [invocation getReturnValue:&result];
562
+ return Napi::Number::New(env, result);
563
+ }
564
+ case 'd': {
565
+ double result;
566
+ [invocation getReturnValue:&result];
567
+ return Napi::Number::New(env, result);
568
+ }
569
+ case 'B': {
570
+ bool result;
571
+ [invocation getReturnValue:&result];
572
+ return Napi::Boolean::New(env, result);
573
+ }
574
+ case 'v':
575
+ return env.Undefined();
576
+ case '*': {
577
+ char *result = nullptr;
578
+ [invocation getReturnValue:&result];
579
+ if (result == nullptr) {
580
+ return env.Null();
581
+ }
582
+ return Napi::String::New(env, result);
583
+ }
584
+ case '@':
585
+ case '#': {
586
+ id result = nil;
587
+ [invocation getReturnValue:&result];
588
+ if (result == nil) {
589
+ return env.Null();
590
+ }
591
+ return ObjcObject::NewInstance(env, result);
592
+ }
593
+ case ':': {
594
+ SEL result = nullptr;
595
+ [invocation getReturnValue:&result];
596
+ if (result == nullptr) {
597
+ return env.Null();
598
+ }
599
+ NSString *selectorString = NSStringFromSelector(result);
600
+ if (selectorString == nil) {
601
+ return env.Null();
602
+ }
603
+ return Napi::String::New(env, [selectorString UTF8String]);
604
+ }
605
+ default:
606
+ Napi::TypeError::New(env, "Unsupported return type (post-invoke)")
607
+ .ThrowAsJavaScriptException();
608
+ return env.Null();
609
+ }
610
+ }
611
+
612
+ #endif // TYPE_CONVERSION_H