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.
@@ -1,17 +1,17 @@
1
1
  #include "protocol-impl.h"
2
+ #include "method-forwarding.h"
2
3
  #include "ObjcObject.h"
3
- #include "bridge.h"
4
+ #include "protocol-storage.h"
5
+ #include "type-conversion.h"
4
6
  #include <Foundation/Foundation.h>
5
7
  #include <atomic>
6
8
  #include <chrono>
7
- #include <mutex>
8
9
  #include <napi.h>
9
- #include <objc/message.h>
10
10
  #include <objc/runtime.h>
11
- #include <pthread.h>
12
11
  #include <sstream>
13
12
 
14
- // Global storage for protocol implementations
13
+ // MARK: - Global Storage Definition
14
+
15
15
  std::unordered_map<void *, ProtocolImplementation> g_implementations;
16
16
  std::mutex g_implementations_mutex;
17
17
 
@@ -75,594 +75,6 @@ std::vector<std::string> ParseMethodSignature(const char *typeEncoding) {
75
75
  return types;
76
76
  }
77
77
 
78
- // Convert Objective-C value to JavaScript value
79
- Napi::Value ConvertObjCValueToJS(Napi::Env env, void *valuePtr,
80
- const char *typeEncoding) {
81
- SimplifiedTypeEncoding simplifiedType(typeEncoding);
82
-
83
- switch (simplifiedType[0]) {
84
- case 'c': {
85
- char value = *(char *)valuePtr;
86
- return Napi::Number::New(env, value);
87
- }
88
- case 'i': {
89
- int value = *(int *)valuePtr;
90
- return Napi::Number::New(env, value);
91
- }
92
- case 's': {
93
- short value = *(short *)valuePtr;
94
- return Napi::Number::New(env, value);
95
- }
96
- case 'l': {
97
- long value = *(long *)valuePtr;
98
- return Napi::Number::New(env, value);
99
- }
100
- case 'q': {
101
- long long value = *(long long *)valuePtr;
102
- return Napi::Number::New(env, value);
103
- }
104
- case 'C': {
105
- unsigned char value = *(unsigned char *)valuePtr;
106
- return Napi::Number::New(env, value);
107
- }
108
- case 'I': {
109
- unsigned int value = *(unsigned int *)valuePtr;
110
- return Napi::Number::New(env, value);
111
- }
112
- case 'S': {
113
- unsigned short value = *(unsigned short *)valuePtr;
114
- return Napi::Number::New(env, value);
115
- }
116
- case 'L': {
117
- unsigned long value = *(unsigned long *)valuePtr;
118
- return Napi::Number::New(env, value);
119
- }
120
- case 'Q': {
121
- unsigned long long value = *(unsigned long long *)valuePtr;
122
- return Napi::Number::New(env, value);
123
- }
124
- case 'f': {
125
- float value = *(float *)valuePtr;
126
- return Napi::Number::New(env, value);
127
- }
128
- case 'd': {
129
- double value = *(double *)valuePtr;
130
- return Napi::Number::New(env, value);
131
- }
132
- case 'B': {
133
- bool value = *(bool *)valuePtr;
134
- return Napi::Boolean::New(env, value);
135
- }
136
- case '*': {
137
- char *value = *(char **)valuePtr;
138
- if (value == nullptr) {
139
- return env.Null();
140
- }
141
- return Napi::String::New(env, value);
142
- }
143
- case '@': {
144
- id value = *(__strong id *)valuePtr;
145
- if (value == nil) {
146
- return env.Null();
147
- }
148
- return ObjcObject::NewInstance(env, value);
149
- }
150
- case '#': {
151
- Class value = *(Class *)valuePtr;
152
- if (value == nil) {
153
- return env.Null();
154
- }
155
- return ObjcObject::NewInstance(env, value);
156
- }
157
- case ':': {
158
- SEL value = *(SEL *)valuePtr;
159
- if (value == nullptr) {
160
- return env.Null();
161
- }
162
- NSString *selectorString = NSStringFromSelector(value);
163
- if (selectorString == nil) {
164
- return env.Null();
165
- }
166
- return Napi::String::New(env, [selectorString UTF8String]);
167
- }
168
- case 'v':
169
- return env.Undefined();
170
- default:
171
- return env.Undefined();
172
- }
173
- }
174
-
175
- // MARK: - ThreadSafeFunction Callback Handler
176
-
177
- // This function runs on the JavaScript thread
178
- void CallJSCallback(Napi::Env env, Napi::Function jsCallback, InvocationData* data) {
179
- if (!data) {
180
- NSLog(@"Error: InvocationData is null in CallJSCallback");
181
- return;
182
- }
183
-
184
- NSInvocation *invocation = data->invocation;
185
- if (!invocation) {
186
- NSLog(@"Error: NSInvocation is null in CallJSCallback");
187
- return;
188
- }
189
-
190
- // Extract arguments using NSInvocation
191
- NSMethodSignature *sig = [invocation methodSignature];
192
- if (!sig) {
193
- NSLog(@"Error: Failed to get method signature for selector %s",
194
- data->selectorName.c_str());
195
- return;
196
- }
197
-
198
- std::vector<napi_value> jsArgs;
199
-
200
- // Skip first two arguments (self and _cmd)
201
- for (NSUInteger i = 2; i < [sig numberOfArguments]; i++) {
202
- const char *type = [sig getArgumentTypeAtIndex:i];
203
- SimplifiedTypeEncoding argType(type);
204
-
205
- switch (argType[0]) {
206
- case 'c': {
207
- char value;
208
- [invocation getArgument:&value atIndex:i];
209
- jsArgs.push_back(Napi::Number::New(env, value));
210
- break;
211
- }
212
- case 'i': {
213
- int value;
214
- [invocation getArgument:&value atIndex:i];
215
- jsArgs.push_back(Napi::Number::New(env, value));
216
- break;
217
- }
218
- case 's': {
219
- short value;
220
- [invocation getArgument:&value atIndex:i];
221
- jsArgs.push_back(Napi::Number::New(env, value));
222
- break;
223
- }
224
- case 'l': {
225
- long value;
226
- [invocation getArgument:&value atIndex:i];
227
- jsArgs.push_back(Napi::Number::New(env, value));
228
- break;
229
- }
230
- case 'q': {
231
- long long value;
232
- [invocation getArgument:&value atIndex:i];
233
- jsArgs.push_back(Napi::Number::New(env, value));
234
- break;
235
- }
236
- case 'C': {
237
- unsigned char value;
238
- [invocation getArgument:&value atIndex:i];
239
- jsArgs.push_back(Napi::Number::New(env, value));
240
- break;
241
- }
242
- case 'I': {
243
- unsigned int value;
244
- [invocation getArgument:&value atIndex:i];
245
- jsArgs.push_back(Napi::Number::New(env, value));
246
- break;
247
- }
248
- case 'S': {
249
- unsigned short value;
250
- [invocation getArgument:&value atIndex:i];
251
- jsArgs.push_back(Napi::Number::New(env, value));
252
- break;
253
- }
254
- case 'L': {
255
- unsigned long value;
256
- [invocation getArgument:&value atIndex:i];
257
- jsArgs.push_back(Napi::Number::New(env, value));
258
- break;
259
- }
260
- case 'Q': {
261
- unsigned long long value;
262
- [invocation getArgument:&value atIndex:i];
263
- jsArgs.push_back(Napi::Number::New(env, value));
264
- break;
265
- }
266
- case 'f': {
267
- float value;
268
- [invocation getArgument:&value atIndex:i];
269
- jsArgs.push_back(Napi::Number::New(env, value));
270
- break;
271
- }
272
- case 'd': {
273
- double value;
274
- [invocation getArgument:&value atIndex:i];
275
- jsArgs.push_back(Napi::Number::New(env, value));
276
- break;
277
- }
278
- case 'B': {
279
- bool value;
280
- [invocation getArgument:&value atIndex:i];
281
- jsArgs.push_back(Napi::Boolean::New(env, value));
282
- break;
283
- }
284
- case '*': {
285
- char *value;
286
- [invocation getArgument:&value atIndex:i];
287
- if (value == nullptr) {
288
- jsArgs.push_back(env.Null());
289
- } else {
290
- jsArgs.push_back(Napi::String::New(env, value));
291
- }
292
- break;
293
- }
294
- case '@': {
295
- __unsafe_unretained id value;
296
- [invocation getArgument:&value atIndex:i];
297
- if (value == nil) {
298
- jsArgs.push_back(env.Null());
299
- } else {
300
- jsArgs.push_back(ObjcObject::NewInstance(env, value));
301
- }
302
- break;
303
- }
304
- case '#': {
305
- Class value;
306
- [invocation getArgument:&value atIndex:i];
307
- if (value == nil) {
308
- jsArgs.push_back(env.Null());
309
- } else {
310
- jsArgs.push_back(ObjcObject::NewInstance(env, value));
311
- }
312
- break;
313
- }
314
- case ':': {
315
- SEL value;
316
- [invocation getArgument:&value atIndex:i];
317
- if (value == nullptr) {
318
- jsArgs.push_back(env.Null());
319
- } else {
320
- NSString *selString = NSStringFromSelector(value);
321
- if (selString == nil) {
322
- jsArgs.push_back(env.Null());
323
- } else {
324
- jsArgs.push_back(Napi::String::New(env, [selString UTF8String]));
325
- }
326
- }
327
- break;
328
- }
329
- case '^': {
330
- void *value;
331
- [invocation getArgument:&value atIndex:i];
332
- if (value == nullptr) {
333
- jsArgs.push_back(env.Null());
334
- } else {
335
- jsArgs.push_back(env.Undefined());
336
- }
337
- break;
338
- }
339
- default:
340
- jsArgs.push_back(env.Undefined());
341
- break;
342
- }
343
- }
344
-
345
- // Call the JavaScript callback
346
- try {
347
- Napi::Value result = jsCallback.Call(jsArgs);
348
-
349
- // Handle return value if the method expects one
350
- const char *returnType = [sig methodReturnType];
351
- SimplifiedTypeEncoding retType(returnType);
352
-
353
- if (retType[0] != 'v') { // Not void
354
- // Convert JS return value to Objective-C and set it
355
- if (!result.IsUndefined() && !result.IsNull()) {
356
- switch (retType[0]) {
357
- case 'c': {
358
- char value = result.As<Napi::Number>().Int32Value();
359
- [invocation setReturnValue:&value];
360
- break;
361
- }
362
- case 'i': {
363
- int value = result.As<Napi::Number>().Int32Value();
364
- [invocation setReturnValue:&value];
365
- break;
366
- }
367
- case 's': {
368
- short value = result.As<Napi::Number>().Int32Value();
369
- [invocation setReturnValue:&value];
370
- break;
371
- }
372
- case 'l': {
373
- long value = result.As<Napi::Number>().Int64Value();
374
- [invocation setReturnValue:&value];
375
- break;
376
- }
377
- case 'q': {
378
- long long value = result.As<Napi::Number>().Int64Value();
379
- [invocation setReturnValue:&value];
380
- break;
381
- }
382
- case 'C': {
383
- unsigned char value = result.As<Napi::Number>().Uint32Value();
384
- [invocation setReturnValue:&value];
385
- break;
386
- }
387
- case 'I': {
388
- unsigned int value = result.As<Napi::Number>().Uint32Value();
389
- [invocation setReturnValue:&value];
390
- break;
391
- }
392
- case 'S': {
393
- unsigned short value = result.As<Napi::Number>().Uint32Value();
394
- [invocation setReturnValue:&value];
395
- break;
396
- }
397
- case 'L': {
398
- unsigned long value = result.As<Napi::Number>().Int64Value();
399
- [invocation setReturnValue:&value];
400
- break;
401
- }
402
- case 'Q': {
403
- unsigned long long value = result.As<Napi::Number>().Int64Value();
404
- [invocation setReturnValue:&value];
405
- break;
406
- }
407
- case 'f': {
408
- float value = result.As<Napi::Number>().FloatValue();
409
- [invocation setReturnValue:&value];
410
- break;
411
- }
412
- case 'd': {
413
- double value = result.As<Napi::Number>().DoubleValue();
414
- [invocation setReturnValue:&value];
415
- break;
416
- }
417
- case 'B': {
418
- bool value = result.As<Napi::Boolean>().Value();
419
- [invocation setReturnValue:&value];
420
- break;
421
- }
422
- case '@': {
423
- // Return an Objective-C object
424
- if (result.IsObject()) {
425
- Napi::Object resultObj = result.As<Napi::Object>();
426
- // Check if it's an ObjcObject instance
427
- if (resultObj.InstanceOf(ObjcObject::constructor.Value())) {
428
- ObjcObject *objcObj = Napi::ObjectWrap<ObjcObject>::Unwrap(resultObj);
429
- id objcValue = objcObj->objcObject;
430
- [invocation setReturnValue:&objcValue];
431
- } else {
432
- NSLog(@"Warning: result object is not an ObjcObject instance");
433
- }
434
- } else if (result.IsNull()) {
435
- id objcValue = nil;
436
- [invocation setReturnValue:&objcValue];
437
- } else {
438
- NSLog(@"Warning: result is not an object (type: %d)", result.Type());
439
- }
440
- break;
441
- }
442
- default:
443
- NSLog(@"Warning: Unsupported return type '%c' for selector %s",
444
- retType[0], data->selectorName.c_str());
445
- break;
446
- }
447
- }
448
- }
449
- } catch (const Napi::Error &e) {
450
- NSLog(@"Error calling JavaScript callback for %s: %s",
451
- data->selectorName.c_str(), e.what());
452
- } catch (const std::exception &e) {
453
- NSLog(@"Exception calling JavaScript callback for %s: %s",
454
- data->selectorName.c_str(), e.what());
455
- } catch (...) {
456
- NSLog(@"Unknown error calling JavaScript callback for %s",
457
- data->selectorName.c_str());
458
- }
459
-
460
- // Clean up the invocation data
461
- delete data;
462
- }
463
-
464
- // MARK: - Message Forwarding Implementation
465
-
466
- // Override respondsToSelector to return YES for methods we implement
467
- BOOL RespondsToSelector(id self, SEL _cmd, SEL selector) {
468
- void *ptr = (__bridge void *)self;
469
-
470
- // Check if this is one of our implemented methods
471
- {
472
- std::lock_guard<std::mutex> lock(g_implementations_mutex);
473
- auto it = g_implementations.find(ptr);
474
- if (it != g_implementations.end()) {
475
- NSString *selectorString = NSStringFromSelector(selector);
476
- if (selectorString != nil) {
477
- std::string selName = [selectorString UTF8String];
478
- auto callbackIt = it->second.callbacks.find(selName);
479
- if (callbackIt != it->second.callbacks.end()) {
480
- return YES;
481
- }
482
- }
483
- }
484
- }
485
-
486
- // For methods we don't implement, check if NSObject responds to them
487
- // This handles standard NSObject methods like description, isEqual:, etc.
488
- return [NSObject instancesRespondToSelector:selector];
489
- }
490
-
491
- // Provide method signature for message forwarding
492
- NSMethodSignature* MethodSignatureForSelector(id self, SEL _cmd, SEL selector) {
493
- void *ptr = (__bridge void *)self;
494
-
495
- std::lock_guard<std::mutex> lock(g_implementations_mutex);
496
- auto it = g_implementations.find(ptr);
497
- if (it != g_implementations.end()) {
498
- NSString *selectorString = NSStringFromSelector(selector);
499
- std::string selName = [selectorString UTF8String];
500
- auto encIt = it->second.typeEncodings.find(selName);
501
- if (encIt != it->second.typeEncodings.end()) {
502
- return [NSMethodSignature signatureWithObjCTypes:encIt->second.c_str()];
503
- }
504
- }
505
- // Fall back to superclass for methods we don't implement
506
- return [NSObject instanceMethodSignatureForSelector:selector];
507
- }
508
-
509
- // Handle forwarded invocations
510
- void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
511
- if (!invocation) {
512
- NSLog(@"Error: ForwardInvocation called with nil invocation");
513
- return;
514
- }
515
-
516
- // Retain the invocation to keep it alive during async call
517
- [invocation retainArguments];
518
-
519
- SEL selector = [invocation selector];
520
- NSString *selectorString = NSStringFromSelector(selector);
521
- if (!selectorString) {
522
- NSLog(@"Error: Failed to convert selector to string");
523
- return;
524
- }
525
-
526
- std::string selectorName = [selectorString UTF8String];
527
-
528
- // Store self pointer for later lookups
529
- void *ptr = (__bridge void *)self;
530
-
531
- // First pass: get ONLY thread-safe data (pthread_t, TSFN, typeEncoding)
532
- // DO NOT access any N-API values here - we may not be on the JS thread!
533
- pthread_t js_thread;
534
- Napi::ThreadSafeFunction tsfn;
535
- std::string typeEncoding;
536
- {
537
- std::lock_guard<std::mutex> lock(g_implementations_mutex);
538
- auto it = g_implementations.find(ptr);
539
- if (it == g_implementations.end()) {
540
- NSLog(@"Warning: Protocol implementation not found for instance %p", self);
541
- return;
542
- }
543
-
544
- auto callbackIt = it->second.callbacks.find(selectorName);
545
- if (callbackIt == it->second.callbacks.end()) {
546
- NSLog(@"Warning: Callback not found for selector %s", selectorName.c_str());
547
- return;
548
- }
549
-
550
- // Get thread ID first - this is a plain C type, safe from any thread
551
- js_thread = it->second.js_thread;
552
-
553
- // Get the ThreadSafeFunction - this is thread-safe by design
554
- // IMPORTANT: We must Acquire() to increment the ref count, because copying
555
- // a ThreadSafeFunction does NOT increment it. If DeallocImplementation runs
556
- // and calls Release() on the original, our copy would become invalid.
557
- tsfn = callbackIt->second;
558
- napi_status acq_status = tsfn.Acquire();
559
- if (acq_status != napi_ok) {
560
- NSLog(@"Warning: Failed to acquire ThreadSafeFunction for selector %s", selectorName.c_str());
561
- return;
562
- }
563
-
564
- // Get the type encoding for return value handling
565
- auto encIt = it->second.typeEncodings.find(selectorName);
566
- if (encIt != it->second.typeEncodings.end()) {
567
- typeEncoding = encIt->second;
568
- }
569
- }
570
-
571
- // Check if we're on the JS thread BEFORE any N-API value access
572
- bool is_js_thread = pthread_equal(pthread_self(), js_thread);
573
-
574
- // Create invocation data
575
- auto data = new InvocationData();
576
- data->invocation = invocation;
577
- data->selectorName = selectorName;
578
- data->typeEncoding = typeEncoding;
579
-
580
- if (is_js_thread) {
581
- // We're on the JS thread, so it's safe to access N-API values directly
582
- // Release the TSFN we acquired - we don't need it on this path
583
- tsfn.Release();
584
-
585
- // Do a second lookup to get the JS callback
586
- // IMPORTANT: We must call .Value() while holding the lock to prevent
587
- // DeallocImplementation from erasing the entry while we're using it
588
- napi_env stored_env;
589
- Napi::Function jsFn;
590
- {
591
- std::lock_guard<std::mutex> lock(g_implementations_mutex);
592
- auto it = g_implementations.find(ptr);
593
- if (it == g_implementations.end()) {
594
- NSLog(@"Warning: Protocol implementation not found for instance %p (JS thread path)", self);
595
- delete data;
596
- return;
597
- }
598
-
599
- auto jsCallbackIt = it->second.jsCallbacks.find(selectorName);
600
- if (jsCallbackIt == it->second.jsCallbacks.end()) {
601
- NSLog(@"Warning: JS callback not found for selector %s (JS thread path)", selectorName.c_str());
602
- delete data;
603
- return;
604
- }
605
-
606
- stored_env = it->second.env;
607
- // Get the function value WHILE HOLDING THE LOCK
608
- // This prevents a race with DeallocImplementation clearing the jsCallbacks map
609
- jsFn = jsCallbackIt->second.Value();
610
- }
611
-
612
- // Now jsFn is a local Napi::Function (napi_value), safe to use after lock release
613
- Napi::Env env(stored_env);
614
- CallJSCallback(env, jsFn, data);
615
- // Data is deleted in CallJSCallback
616
- } else {
617
- // We're on a different thread - NEVER access FunctionReference here!
618
- // Use ThreadSafeFunction to marshal to JS thread
619
- napi_status status = tsfn.BlockingCall(data, CallJSCallback);
620
-
621
- // Release our acquired reference to the TSFN
622
- // This balances the Acquire() call above
623
- tsfn.Release();
624
-
625
- if (status != napi_ok) {
626
- NSLog(@"Error: Failed to call ThreadSafeFunction for selector %s (status: %d)",
627
- selectorName.c_str(), status);
628
- delete data;
629
- return;
630
- }
631
- // Data will be deleted in the callback
632
- }
633
- }
634
-
635
- // Deallocation implementation
636
- void DeallocImplementation(id self, SEL _cmd) {
637
- @autoreleasepool {
638
- // Remove the implementation from the global map
639
- std::lock_guard<std::mutex> lock(g_implementations_mutex);
640
- void *ptr = (__bridge void *)self;
641
- auto it = g_implementations.find(ptr);
642
- if (it != g_implementations.end()) {
643
- // Release all ThreadSafeFunctions and JS callbacks
644
- // Do this carefully to avoid issues during shutdown
645
- try {
646
- for (auto& pair : it->second.callbacks) {
647
- // Release the ThreadSafeFunction
648
- pair.second.Release();
649
- }
650
- it->second.callbacks.clear();
651
- it->second.jsCallbacks.clear();
652
- it->second.typeEncodings.clear();
653
- } catch (...) {
654
- // Ignore errors during cleanup
655
- NSLog(@"Warning: Exception during callback cleanup for instance %p", self);
656
- }
657
- g_implementations.erase(it);
658
- }
659
- }
660
-
661
- // Call the superclass dealloc
662
- // Note: Under ARC, we don't need to manually call [super dealloc]
663
- // The runtime handles this automatically
664
- }
665
-
666
78
  // MARK: - Main Implementation
667
79
 
668
80
  Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
@@ -718,13 +130,33 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
718
130
  // Get the method implementations object's property names
719
131
  Napi::Array propertyNames = methodImplementations.GetPropertyNames();
720
132
 
133
+ // Detect if we're running in Electron by checking process.versions.electron
134
+ bool isElectron = false;
135
+ try {
136
+ Napi::Object global = env.Global();
137
+ if (global.Has("process")) {
138
+ Napi::Object process = global.Get("process").As<Napi::Object>();
139
+ if (process.Has("versions")) {
140
+ Napi::Object versions = process.Get("versions").As<Napi::Object>();
141
+ isElectron = versions.Has("electron");
142
+ }
143
+ }
144
+ } catch (...) {
145
+ // If detection fails, assume not Electron
146
+ }
147
+
148
+ if (isElectron) {
149
+ NSLog(@"[DEBUG] Detected Electron runtime, will always use TSFN path");
150
+ }
151
+
721
152
  // Store callbacks for this instance (we'll set the instance pointer later)
722
153
  ProtocolImplementation impl{
723
154
  .callbacks = {},
724
155
  .typeEncodings = {},
725
156
  .className = className,
726
157
  .env = env,
727
- .js_thread = pthread_self() // Store the current (JS) thread ID
158
+ .js_thread = pthread_self(), // Store the current (JS) thread ID
159
+ .isElectron = isElectron
728
160
  };
729
161
 
730
162
  // Store default type encodings to keep them alive
@@ -781,7 +213,7 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
781
213
  for (size_t j = 0; j < colonCount; j++) {
782
214
  defaultEncoding += "@";
783
215
  }
784
-
216
+
785
217
  // Store the string to keep it alive, then use its c_str()
786
218
  defaultTypeEncodings.push_back(std::move(defaultEncoding));
787
219
  typeEncoding = defaultTypeEncodings.back().c_str();
@@ -793,27 +225,28 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
793
225
 
794
226
  // Create a ThreadSafeFunction for this callback
795
227
  Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(
796
- env,
797
- jsCallback, // The JS function to call
798
- "ProtocolCallback", // Resource name
799
- 0, // Max queue size (0 = unlimited)
800
- 1, // Initial thread count
801
- [](Napi::Env) {} // Finalizer (no context to clean up)
228
+ env,
229
+ jsCallback, // The JS function to call
230
+ "ProtocolCallback", // Resource name
231
+ 0, // Max queue size (0 = unlimited)
232
+ 1, // Initial thread count
233
+ [](Napi::Env) {} // Finalizer (no context to clean up)
802
234
  );
803
-
235
+
804
236
  // Store both the TSFN and the original JS function
805
237
  impl.callbacks[selectorName] = tsfn;
806
238
  impl.jsCallbacks[selectorName] = Napi::Persistent(jsCallback);
807
239
  impl.typeEncodings[selectorName] = std::string(typeEncoding);
808
240
  }
809
-
241
+
810
242
  // Override respondsToSelector
811
- // Use class_addMethod since the class is being created and doesn't have methods yet
243
+ // Use class_addMethod since the class is being created and doesn't have
244
+ // methods yet
812
245
  if (!class_addMethod(newClass, @selector(respondsToSelector:),
813
246
  (IMP)RespondsToSelector, "B@::")) {
814
247
  NSLog(@"Warning: Failed to add respondsToSelector: method");
815
248
  }
816
-
249
+
817
250
  // Add message forwarding methods to the class
818
251
  class_addMethod(newClass, @selector(methodSignatureForSelector:),
819
252
  (IMP)MethodSignatureForSelector, "@@::");
@@ -848,4 +281,3 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
848
281
  // Return wrapped object
849
282
  return ObjcObject::NewInstance(env, instance);
850
283
  }
851
-