objc-js 1.3.0 → 1.3.1

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.
package/dist/index.js CHANGED
@@ -32,7 +32,12 @@ class NobjcLibrary {
32
32
  LoadLibrary(library);
33
33
  this.wasLoaded = true;
34
34
  }
35
- cls = new NobjcObject(GetClassObject(className));
35
+ const classObject = GetClassObject(className);
36
+ if (classObject === undefined) {
37
+ // Class not found. Make sure the class exists before trying to access it.
38
+ return undefined;
39
+ }
40
+ cls = new NobjcObject(classObject);
36
41
  classCache.set(className, cls);
37
42
  return cls;
38
43
  }
@@ -55,6 +60,9 @@ class NobjcObject {
55
60
  // Return true for the special Symbol to enable unwrapping
56
61
  if (p === NATIVE_OBJC_OBJECT)
57
62
  return true;
63
+ // Return true for inspect symbols so console.log uses custom inspect
64
+ if (p === customInspectSymbol)
65
+ return true;
58
66
  // guard against other symbols
59
67
  if (typeof p === "symbol")
60
68
  return Reflect.has(target, p);
@@ -117,6 +125,10 @@ class NobjcObject {
117
125
  // object (avoids triggering proxy 'has' trap which would be a second FFI call)
118
126
  const selector = NobjcMethodNameToObjcSelector(methodName);
119
127
  if (!target.$respondsToSelector(selector)) {
128
+ // special case since JS checks for `.then` on Promise objects
129
+ if (methodName === "then")
130
+ return undefined;
131
+ // Otherwise, throw an error
120
132
  throw new Error(`Method ${methodName} not found on object`);
121
133
  }
122
134
  method = NobjcMethod(object, methodName);
@@ -127,6 +139,9 @@ class NobjcObject {
127
139
  };
128
140
  // Create the proxy
129
141
  const proxy = new Proxy(object, handler);
142
+ // Set custom inspect on the native object so console.log works through the Proxy.
143
+ // Runtimes (Node, Bun) bypass Proxy traps during inspect and read the target directly.
144
+ object[customInspectSymbol] = () => proxy.toString();
130
145
  // Store proxy → native mapping in WeakMap for O(1) unwrap (bypasses Proxy traps)
131
146
  nativeObjectMap.set(proxy, object);
132
147
  // Return the proxy
package/package.json CHANGED
@@ -46,7 +46,7 @@
46
46
  "prebuild:x64": "prebuildify --napi --strip --arch x64 -r node",
47
47
  "prebuild:all": "bun run prebuild:arm64 && bun run prebuild:x64"
48
48
  },
49
- "version": "1.3.0",
49
+ "version": "1.3.1",
50
50
  "description": "Objective-C bridge for Node.js",
51
51
  "main": "dist/index.js",
52
52
  "dependencies": {
Binary file
@@ -4,11 +4,18 @@
4
4
  #include <napi.h>
5
5
  #include <objc/objc.h>
6
6
  #include <objc/runtime.h>
7
+ #include <objc/message.h>
7
8
  #include <optional>
8
9
  #include <variant>
9
10
  #include <unordered_map>
10
11
  #include <vector>
11
12
 
13
+ // objc_retain/objc_release are part of the stable ObjC ABI (macOS 10.12+)
14
+ // but not declared in public headers. We use them for manual reference counting
15
+ // since ARC is not enabled for .mm files in this project.
16
+ extern "C" id objc_retain(id value);
17
+ extern "C" void objc_release(id value);
18
+
12
19
  #ifdef __OBJC__
13
20
  @class NSMethodSignature;
14
21
  #else
@@ -51,6 +58,14 @@ public:
51
58
  // This better be an Napi::External<id>! We lost the type info at runtime.
52
59
  Napi::External<id> external = info[0].As<Napi::External<id>>();
53
60
  objcObject = *(external.Data());
61
+ // Retain the ObjC object so it stays alive as long as this JS wrapper
62
+ // exists. Without this, ARC/autorelease can deallocate the object while
63
+ // JS still holds a reference, causing Use-After-Free crashes (SIGTRAP)
64
+ // in completion handler callbacks and other async contexts.
65
+ // Note: ARC is not enabled for .mm files in this project (the -fobjc-arc
66
+ // flag is in OTHER_CFLAGS, not OTHER_CPLUSPLUSFLAGS), so __strong has
67
+ // no effect — we must manage retain/release manually.
68
+ if (objcObject) objc_retain(objcObject);
54
69
  return;
55
70
  }
56
71
  // If someone tries `new ObjcObject()` from JS, forbid it:
@@ -58,7 +73,12 @@ public:
58
73
  .ThrowAsJavaScriptException();
59
74
  }
60
75
  static Napi::Object NewInstance(Napi::Env env, id obj);
61
- ~ObjcObject() = default;
76
+ ~ObjcObject() {
77
+ if (objcObject) {
78
+ objc_release(objcObject);
79
+ objcObject = nil;
80
+ }
81
+ }
62
82
 
63
83
  private:
64
84
  Napi::Value $MsgSend(const Napi::CallbackInfo &info);