objc-js 0.0.12 → 0.0.14

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,4 +1,5 @@
1
1
  #include "method-forwarding.h"
2
+ #include "debug.h"
2
3
  #include "ObjcObject.h"
3
4
  #include "protocol-storage.h"
4
5
  #include "type-conversion.h"
@@ -10,16 +11,20 @@
10
11
  // MARK: - ThreadSafeFunction Callback Handler
11
12
 
12
13
  // This function runs on the JavaScript thread
14
+ // Handles both protocol implementation and subclass method forwarding
13
15
  void CallJSCallback(Napi::Env env, Napi::Function jsCallback,
14
16
  InvocationData *data) {
15
17
  if (!data) {
16
- NSLog(@"Error: InvocationData is null in CallJSCallback");
18
+ NOBJC_ERROR("InvocationData is null in CallJSCallback");
17
19
  return;
18
20
  }
19
21
 
22
+ NOBJC_LOG("CallJSCallback: Called for selector %s, callbackType=%d",
23
+ data->selectorName.c_str(), (int)data->callbackType);
24
+
20
25
  // Check if the callback is valid before proceeding
21
26
  if (jsCallback.IsEmpty()) {
22
- NSLog(@"Error: jsCallback is null/empty in CallJSCallback for selector %s",
27
+ NOBJC_ERROR("jsCallback is null/empty in CallJSCallback for selector %s",
23
28
  data->selectorName.c_str());
24
29
  if (data->invocation) {
25
30
  [data->invocation release];
@@ -31,16 +36,18 @@ void CallJSCallback(Napi::Env env, Napi::Function jsCallback,
31
36
 
32
37
  NSInvocation *invocation = data->invocation;
33
38
  if (!invocation) {
34
- NSLog(@"Error: NSInvocation is null in CallJSCallback");
39
+ NOBJC_ERROR("NSInvocation is null in CallJSCallback");
35
40
  SignalInvocationComplete(data);
36
41
  delete data;
37
42
  return;
38
43
  }
39
44
 
45
+ NOBJC_LOG("CallJSCallback: About to get method signature");
46
+
40
47
  // Extract arguments using NSInvocation
41
48
  NSMethodSignature *sig = [invocation methodSignature];
42
49
  if (!sig) {
43
- NSLog(@"Error: Failed to get method signature for selector %s",
50
+ NOBJC_ERROR("Failed to get method signature for selector %s",
44
51
  data->selectorName.c_str());
45
52
  SignalInvocationComplete(data);
46
53
  [invocation release];
@@ -48,42 +55,74 @@ void CallJSCallback(Napi::Env env, Napi::Function jsCallback,
48
55
  return;
49
56
  }
50
57
 
58
+ NOBJC_LOG("CallJSCallback: Method signature: %s, numArgs: %lu",
59
+ [sig description].UTF8String, (unsigned long)[sig numberOfArguments]);
60
+
51
61
  std::vector<napi_value> jsArgs;
52
62
 
53
- // Skip first two arguments (self and _cmd)
63
+ // For subclass methods, include 'self' as first JavaScript argument
64
+ if (data->callbackType == CallbackType::Subclass) {
65
+ NOBJC_LOG("CallJSCallback: Extracting 'self' argument for subclass method");
66
+ __unsafe_unretained id selfObj;
67
+ [invocation getArgument:&selfObj atIndex:0];
68
+ NOBJC_LOG("CallJSCallback: About to create ObjcObject for self=%p", selfObj);
69
+ Napi::Value selfValue = ObjcObject::NewInstance(env, selfObj);
70
+ NOBJC_LOG("CallJSCallback: Created ObjcObject for self (JS wrapper created)");
71
+ jsArgs.push_back(selfValue);
72
+ NOBJC_LOG("CallJSCallback: Added self to jsArgs");
73
+ }
74
+
75
+ // Extract remaining arguments (skip self and _cmd, start at index 2)
76
+ NOBJC_LOG("CallJSCallback: Extracting %lu method arguments", (unsigned long)[sig numberOfArguments] - 2);
54
77
  for (NSUInteger i = 2; i < [sig numberOfArguments]; i++) {
55
78
  const char *type = [sig getArgumentTypeAtIndex:i];
56
79
  SimplifiedTypeEncoding argType(type);
80
+
81
+ NOBJC_LOG("CallJSCallback: Processing arg %lu, type=%s, simplified=%s",
82
+ (unsigned long)i, type, argType.c_str());
83
+
84
+ // Handle out-parameters (e.g., NSError**) by passing null
85
+ // This avoids creating N-API Function objects which triggers Bun crashes
86
+ if (argType[0] == '^' && argType[1] == '@') {
87
+ NOBJC_LOG("CallJSCallback: Arg %lu is out-param (^@), passing null", (unsigned long)i);
88
+ jsArgs.push_back(env.Null());
89
+ continue;
90
+ }
91
+
92
+ NOBJC_LOG("CallJSCallback: About to extract arg %lu to JS", (unsigned long)i);
57
93
  jsArgs.push_back(ExtractInvocationArgumentToJS(env, invocation, i, argType[0]));
94
+ NOBJC_LOG("CallJSCallback: Successfully extracted arg %lu", (unsigned long)i);
58
95
  }
59
96
 
60
97
  // Call the JavaScript callback
61
98
  try {
99
+ NOBJC_LOG("CallJSCallback: About to call JS function with %zu args", jsArgs.size());
62
100
  Napi::Value result = jsCallback.Call(jsArgs);
101
+ NOBJC_LOG("CallJSCallback: JS function returned successfully");
63
102
 
64
103
  // Handle return value if the method expects one
65
104
  const char *returnType = [sig methodReturnType];
66
105
  SimplifiedTypeEncoding retType(returnType);
67
106
 
68
107
  if (retType[0] != 'v') { // Not void
69
- NSLog(@"Setting return value for %s, return type: %c",
70
- data->selectorName.c_str(), retType[0]);
71
108
  SetInvocationReturnFromJS(invocation, result, retType[0],
72
109
  data->selectorName.c_str());
73
110
  }
74
111
  } catch (const Napi::Error &e) {
75
- NSLog(@"Error calling JavaScript callback for %s: %s",
112
+ NOBJC_ERROR("Error calling JavaScript callback for %s: %s",
76
113
  data->selectorName.c_str(), e.what());
77
114
  } catch (const std::exception &e) {
78
- NSLog(@"Exception calling JavaScript callback for %s: %s",
115
+ NOBJC_ERROR("Exception calling JavaScript callback for %s: %s",
79
116
  data->selectorName.c_str(), e.what());
80
117
  } catch (...) {
81
- NSLog(@"Unknown error calling JavaScript callback for %s",
118
+ NOBJC_ERROR("Unknown error calling JavaScript callback for %s",
82
119
  data->selectorName.c_str());
83
120
  }
84
121
 
85
122
  // Signal completion to the waiting ForwardInvocation
123
+ NOBJC_LOG("CallJSCallback: About to signal completion for %s", data->selectorName.c_str());
86
124
  SignalInvocationComplete(data);
125
+ NOBJC_LOG("CallJSCallback: Signaled completion for %s", data->selectorName.c_str());
87
126
 
88
127
  // Clean up the invocation data
89
128
  // Release the invocation that we retained in ForwardInvocation
@@ -91,6 +130,46 @@ void CallJSCallback(Napi::Env env, Napi::Function jsCallback,
91
130
  delete data;
92
131
  }
93
132
 
133
+ // MARK: - Fallback Helper
134
+
135
+ // Helper function to fallback to ThreadSafeFunction when direct call fails
136
+ // This is used when direct JS callback invocation fails (e.g., in Electron)
137
+ bool FallbackToTSFN(Napi::ThreadSafeFunction &tsfn, InvocationData *data,
138
+ const std::string &selectorName) {
139
+ // Set up synchronization primitives
140
+ std::mutex completionMutex;
141
+ std::condition_variable completionCv;
142
+ bool isComplete = false;
143
+
144
+ data->completionMutex = &completionMutex;
145
+ data->completionCv = &completionCv;
146
+ data->isComplete = &isComplete;
147
+
148
+ // Call via ThreadSafeFunction
149
+ napi_status status = tsfn.NonBlockingCall(data, CallJSCallback);
150
+ tsfn.Release();
151
+
152
+ if (status != napi_ok) {
153
+ NOBJC_ERROR("FallbackToTSFN failed for selector %s (status: %d)",
154
+ selectorName.c_str(), status);
155
+ return false;
156
+ }
157
+
158
+ // Wait for callback by pumping CFRunLoop
159
+ CFTimeInterval timeout = 0.001; // 1ms per iteration
160
+ while (true) {
161
+ {
162
+ std::unique_lock<std::mutex> lock(completionMutex);
163
+ if (isComplete) {
164
+ break;
165
+ }
166
+ }
167
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
168
+ }
169
+
170
+ return true;
171
+ }
172
+
94
173
  // MARK: - Message Forwarding Implementation
95
174
 
96
175
  // Override respondsToSelector to return YES for methods we implement
@@ -139,7 +218,7 @@ NSMethodSignature *MethodSignatureForSelector(id self, SEL _cmd, SEL selector) {
139
218
  // Handle forwarded invocations
140
219
  void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
141
220
  if (!invocation) {
142
- NSLog(@"Error: ForwardInvocation called with nil invocation");
221
+ NOBJC_ERROR("ForwardInvocation called with nil invocation");
143
222
  return;
144
223
  }
145
224
 
@@ -151,7 +230,7 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
151
230
  SEL selector = [invocation selector];
152
231
  NSString *selectorString = NSStringFromSelector(selector);
153
232
  if (!selectorString) {
154
- NSLog(@"Error: Failed to convert selector to string");
233
+ NOBJC_ERROR("Failed to convert selector to string");
155
234
  return;
156
235
  }
157
236
 
@@ -165,19 +244,18 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
165
244
  Napi::ThreadSafeFunction tsfn;
166
245
  std::string typeEncoding;
167
246
  pthread_t js_thread;
247
+ bool isElectron;
168
248
  {
169
249
  std::lock_guard<std::mutex> lock(g_implementations_mutex);
170
250
  auto it = g_implementations.find(ptr);
171
251
  if (it == g_implementations.end()) {
172
- NSLog(@"Warning: Protocol implementation not found for instance %p",
173
- self);
252
+ NOBJC_WARN("Protocol implementation not found for instance %p", self);
174
253
  return;
175
254
  }
176
255
 
177
256
  auto callbackIt = it->second.callbacks.find(selectorName);
178
257
  if (callbackIt == it->second.callbacks.end()) {
179
- NSLog(@"Warning: Callback not found for selector %s",
180
- selectorName.c_str());
258
+ NOBJC_WARN("Callback not found for selector %s", selectorName.c_str());
181
259
  return;
182
260
  }
183
261
 
@@ -188,7 +266,7 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
188
266
  tsfn = callbackIt->second;
189
267
  napi_status acq_status = tsfn.Acquire();
190
268
  if (acq_status != napi_ok) {
191
- NSLog(@"Warning: Failed to acquire ThreadSafeFunction for selector %s",
269
+ NOBJC_WARN("Failed to acquire ThreadSafeFunction for selector %s",
192
270
  selectorName.c_str());
193
271
  return;
194
272
  }
@@ -201,6 +279,7 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
201
279
 
202
280
  // Get the JS thread ID to check if we're on the same thread
203
281
  js_thread = it->second.js_thread;
282
+ isElectron = it->second.isElectron;
204
283
  }
205
284
 
206
285
  // Check if we're on the JS thread
@@ -208,17 +287,20 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
208
287
 
209
288
  // IMPORTANT: We call directly on the JS thread so return values are set
210
289
  // synchronously; otherwise we use a ThreadSafeFunction to marshal work.
290
+ // EXCEPTION: In Electron, we ALWAYS use TSFN even on the JS thread because
291
+ // Electron's V8 context isn't properly set up for direct handle creation.
211
292
 
212
293
  // Create invocation data
213
294
  auto data = new InvocationData();
214
295
  data->invocation = invocation;
215
296
  data->selectorName = selectorName;
216
297
  data->typeEncoding = typeEncoding;
298
+ data->callbackType = CallbackType::Protocol;
217
299
 
218
300
  napi_status status;
219
301
 
220
- if (is_js_thread) {
221
- // We're on the JS thread (e.g., called from performSelector in Bun/Node)
302
+ if (is_js_thread && !isElectron) {
303
+ // We're on the JS thread in Node/Bun (NOT Electron)
222
304
  // Call directly to ensure return values are set synchronously.
223
305
  data->completionMutex = nullptr;
224
306
  data->completionCv = nullptr;
@@ -232,9 +314,7 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
232
314
  std::lock_guard<std::mutex> lock(g_implementations_mutex);
233
315
  auto it = g_implementations.find(ptr);
234
316
  if (it == g_implementations.end()) {
235
- NSLog(@"Warning: Protocol implementation not found for instance %p "
236
- @"(JS thread path)",
237
- self);
317
+ NOBJC_WARN("Protocol implementation not found for instance %p (JS thread path)", self);
238
318
  [invocation release];
239
319
  delete data;
240
320
  return;
@@ -242,8 +322,7 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
242
322
 
243
323
  auto jsCallbackIt = it->second.jsCallbacks.find(selectorName);
244
324
  if (jsCallbackIt == it->second.jsCallbacks.end()) {
245
- NSLog(@"Warning: JS callback not found for selector %s "
246
- @"(JS thread path)",
325
+ NOBJC_WARN("JS callback not found for selector %s (JS thread path)",
247
326
  selectorName.c_str());
248
327
  [invocation release];
249
328
  delete data;
@@ -267,14 +346,11 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
267
346
  CallJSCallback(callEnv, jsFn, data);
268
347
  // CallJSCallback releases invocation and deletes data.
269
348
  } catch (const std::exception &e) {
270
- NSLog(@"Error calling JS callback directly (likely invalid env in "
271
- @"Electron): %s",
272
- e.what());
273
- NSLog(@"Falling back to ThreadSafeFunction for selector %s",
274
- selectorName.c_str());
349
+ NOBJC_ERROR("Error calling JS callback directly (likely invalid env in Electron): %s", e.what());
350
+ NOBJC_LOG("Falling back to ThreadSafeFunction for selector %s", selectorName.c_str());
275
351
 
276
352
  // Fallback to TSFN if direct call fails (e.g., invalid env in Electron)
277
- // We need to re-acquire the TSFN and set up sync primitives
353
+ // We need to re-acquire the TSFN
278
354
  {
279
355
  std::lock_guard<std::mutex> lock(g_implementations_mutex);
280
356
  auto it = g_implementations.find(ptr);
@@ -284,30 +360,8 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
284
360
  tsfn = callbackIt->second;
285
361
  napi_status acq_status = tsfn.Acquire();
286
362
  if (acq_status == napi_ok) {
287
- // Set up synchronization for fallback path
288
- std::mutex completionMutex;
289
- std::condition_variable completionCv;
290
- bool isComplete = false;
291
-
292
- data->completionMutex = &completionMutex;
293
- data->completionCv = &completionCv;
294
- data->isComplete = &isComplete;
295
-
296
- status = tsfn.NonBlockingCall(data, CallJSCallback);
297
- tsfn.Release();
298
-
299
- if (status == napi_ok) {
300
- // Wait for callback by pumping CFRunLoop
301
- CFTimeInterval timeout = 0.001; // 1ms per iteration
302
- while (true) {
303
- {
304
- std::unique_lock<std::mutex> lock(completionMutex);
305
- if (isComplete) {
306
- break;
307
- }
308
- }
309
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
310
- }
363
+ // Use helper function for fallback
364
+ if (FallbackToTSFN(tsfn, data, selectorName)) {
311
365
  return; // Data cleaned up in callback
312
366
  }
313
367
  }
@@ -335,8 +389,7 @@ void ForwardInvocation(id self, SEL _cmd, NSInvocation *invocation) {
335
389
  tsfn.Release();
336
390
 
337
391
  if (status != napi_ok) {
338
- NSLog(@"Error: Failed to call ThreadSafeFunction for selector %s "
339
- @"(status: %d)",
392
+ NOBJC_ERROR("Failed to call ThreadSafeFunction for selector %s (status: %d)",
340
393
  selectorName.c_str(), status);
341
394
  [invocation release];
342
395
  delete data;
@@ -382,8 +435,7 @@ void DeallocImplementation(id self, SEL _cmd) {
382
435
  it->second.typeEncodings.clear();
383
436
  } catch (...) {
384
437
  // Ignore errors during cleanup
385
- NSLog(@"Warning: Exception during callback cleanup for instance %p",
386
- self);
438
+ NOBJC_WARN("Exception during callback cleanup for instance %p", self);
387
439
  }
388
440
  g_implementations.erase(it);
389
441
  }
@@ -1,5 +1,6 @@
1
1
  #include "ObjcObject.h"
2
2
  #include "protocol-impl.h"
3
+ #include "subclass-impl.h"
3
4
  #include <Foundation/Foundation.h>
4
5
  #include <dlfcn.h>
5
6
  #include <napi.h>
@@ -107,6 +108,8 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
107
108
  exports.Set("FromPointer", Napi::Function::New(env, FromPointer));
108
109
  exports.Set("CreateProtocolImplementation",
109
110
  Napi::Function::New(env, CreateProtocolImplementation));
111
+ exports.Set("DefineClass", Napi::Function::New(env, DefineClass));
112
+ exports.Set("CallSuper", Napi::Function::New(env, CallSuper));
110
113
  return exports;
111
114
  }
112
115
 
@@ -1,4 +1,5 @@
1
1
  #include "protocol-impl.h"
2
+ #include "debug.h"
2
3
  #include "method-forwarding.h"
3
4
  #include "ObjcObject.h"
4
5
  #include "protocol-storage.h"
@@ -103,8 +104,7 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
103
104
  protocol = objc_getProtocol(protocolName.c_str());
104
105
  if (protocol == nullptr) {
105
106
  // Log warning but continue (for informal protocols)
106
- NSLog(@"Warning: Protocol %s not found, creating class without protocol "
107
- @"conformance",
107
+ NOBJC_WARN("Protocol %s not found, creating class without protocol conformance",
108
108
  protocolName.c_str());
109
109
  }
110
110
  }
@@ -130,13 +130,33 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
130
130
  // Get the method implementations object's property names
131
131
  Napi::Array propertyNames = methodImplementations.GetPropertyNames();
132
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
+ // Electron runtime detected, will always use TSFN path
150
+ }
151
+
133
152
  // Store callbacks for this instance (we'll set the instance pointer later)
134
153
  ProtocolImplementation impl{
135
154
  .callbacks = {},
136
155
  .typeEncodings = {},
137
156
  .className = className,
138
157
  .env = env,
139
- .js_thread = pthread_self() // Store the current (JS) thread ID
158
+ .js_thread = pthread_self(), // Store the current (JS) thread ID
159
+ .isElectron = isElectron
140
160
  };
141
161
 
142
162
  // Store default type encodings to keep them alive
@@ -153,7 +173,7 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
153
173
  Napi::Value value = methodImplementations.Get(key);
154
174
 
155
175
  if (!value.IsFunction()) {
156
- NSLog(@"Warning: Value for selector %s is not a function, skipping",
176
+ NOBJC_WARN("Value for selector %s is not a function, skipping",
157
177
  selectorName.c_str());
158
178
  continue;
159
179
  }
@@ -198,8 +218,7 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
198
218
  defaultTypeEncodings.push_back(std::move(defaultEncoding));
199
219
  typeEncoding = defaultTypeEncodings.back().c_str();
200
220
 
201
- NSLog(@"Warning: No type encoding found for selector %s, using default: "
202
- @"%s",
221
+ NOBJC_WARN("No type encoding found for selector %s, using default: %s",
203
222
  selectorName.c_str(), typeEncoding);
204
223
  }
205
224
 
@@ -224,7 +243,7 @@ Napi::Value CreateProtocolImplementation(const Napi::CallbackInfo &info) {
224
243
  // methods yet
225
244
  if (!class_addMethod(newClass, @selector(respondsToSelector:),
226
245
  (IMP)RespondsToSelector, "B@::")) {
227
- NSLog(@"Warning: Failed to add respondsToSelector: method");
246
+ NOBJC_WARN("Failed to add respondsToSelector: method");
228
247
  }
229
248
 
230
249
  // Add message forwarding methods to the class
@@ -13,20 +13,33 @@
13
13
  @class NSInvocation;
14
14
  #else
15
15
  typedef struct NSInvocation NSInvocation;
16
+ typedef struct objc_class *Class;
16
17
  #endif
17
18
 
18
19
  // MARK: - Data Structures
19
20
 
21
+ // Callback type for method forwarding
22
+ enum class CallbackType {
23
+ Protocol, // Protocol implementation - args start at index 2
24
+ Subclass // Subclass override - include self at index 0 as first JS arg
25
+ };
26
+
20
27
  // Data passed from native thread to JS thread for invocation handling
21
28
  struct InvocationData {
22
29
  NSInvocation *invocation;
23
30
  std::string selectorName;
24
31
  std::string typeEncoding;
32
+ // Type of callback (protocol or subclass)
33
+ CallbackType callbackType;
25
34
  // Synchronization: we use NonBlockingCall + runloop pumping to avoid
26
35
  // deadlocks in Electron while still getting return values
27
36
  std::mutex *completionMutex;
28
37
  std::condition_variable *completionCv;
29
38
  bool *isComplete;
39
+ // For subclass method calls: the instance pointer (for super calls)
40
+ void *instancePtr;
41
+ // For subclass method calls: the superclass for super calls
42
+ void *superClassPtr;
30
43
  };
31
44
 
32
45
  // Stores information about a protocol implementation instance
@@ -43,6 +56,37 @@ struct ProtocolImplementation {
43
56
  napi_env env;
44
57
  // Store the JS thread ID for thread detection
45
58
  pthread_t js_thread;
59
+ // Flag to indicate if running in Electron (requires TSFN path always)
60
+ bool isElectron;
61
+ };
62
+
63
+ // MARK: - Subclass Storage
64
+
65
+ // Information about an overridden method in a subclass
66
+ struct SubclassMethodInfo {
67
+ Napi::ThreadSafeFunction callback;
68
+ Napi::FunctionReference jsCallback;
69
+ std::string typeEncoding;
70
+ std::string selectorName;
71
+ bool isClassMethod;
72
+ };
73
+
74
+ // Stores information about a JS-defined subclass
75
+ struct SubclassImplementation {
76
+ // Class name
77
+ std::string className;
78
+ // The Objective-C Class object
79
+ void *objcClass; // Class
80
+ // The superclass for super calls
81
+ void *superClass; // Class
82
+ // Methods defined by JS (selector -> info)
83
+ std::unordered_map<std::string, SubclassMethodInfo> methods;
84
+ // Store the environment for direct calls
85
+ napi_env env;
86
+ // Store the JS thread ID for thread detection
87
+ pthread_t js_thread;
88
+ // Flag to indicate if running in Electron
89
+ bool isElectron;
46
90
  };
47
91
 
48
92
  // MARK: - Global Storage
@@ -53,6 +97,11 @@ struct ProtocolImplementation {
53
97
  extern std::unordered_map<void *, ProtocolImplementation> g_implementations;
54
98
  extern std::mutex g_implementations_mutex;
55
99
 
100
+ // Global map: Class pointer -> subclass implementation details
101
+ // This stores information about JS-defined subclasses
102
+ extern std::unordered_map<void *, SubclassImplementation> g_subclasses;
103
+ extern std::mutex g_subclasses_mutex;
104
+
56
105
  // MARK: - Storage Access Helpers
57
106
 
58
107
  // Helper to signal completion of an invocation
@@ -76,5 +125,17 @@ FindImplementation(void *instancePtr) {
76
125
  return nullptr;
77
126
  }
78
127
 
128
+ // Look up subclass implementation for a Class pointer
129
+ // Returns nullptr if not found
130
+ // Caller must hold g_subclasses_mutex
131
+ inline SubclassImplementation *
132
+ FindSubclassImplementation(void *classPtr) {
133
+ auto it = g_subclasses.find(classPtr);
134
+ if (it != g_subclasses.end()) {
135
+ return &it->second;
136
+ }
137
+ return nullptr;
138
+ }
139
+
79
140
  #endif // PROTOCOL_STORAGE_H
80
141
 
@@ -0,0 +1,20 @@
1
+ #ifndef SUBCLASS_IMPL_H
2
+ #define SUBCLASS_IMPL_H
3
+
4
+ #include <napi.h>
5
+
6
+ // Define a new Objective-C class at runtime from JavaScript
7
+ // Arguments: { name: string, superclass: string|Class, protocols?: string[], methods?: {...} }
8
+ // Returns: The new Class object
9
+ Napi::Value DefineClass(const Napi::CallbackInfo &info);
10
+
11
+ // Call the superclass implementation of a method
12
+ // Arguments: self, selectorName, ...args
13
+ // Returns: The result of the super call
14
+ Napi::Value CallSuper(const Napi::CallbackInfo &info);
15
+
16
+ // Callback handler for subclass methods (called from JS thread)
17
+ void CallSubclassJSCallback(Napi::Env env, Napi::Function jsCallback,
18
+ struct InvocationData *data);
19
+
20
+ #endif // SUBCLASS_IMPL_H