objc-js 1.0.0 → 1.0.2
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/README.md +30 -288
- package/binding.gyp +2 -2
- package/dist/native.js +2 -1
- package/package.json +11 -6
- package/prebuilds/darwin-arm64/objc-js.node +0 -0
- package/prebuilds/darwin-x64/objc-js.node +0 -0
- package/build/Release/nobjc_native.node +0 -0
package/README.md
CHANGED
|
@@ -1,312 +1,54 @@
|
|
|
1
1
|
# objc-js
|
|
2
2
|
|
|
3
|
-
> [!WARNING]
|
|
4
|
-
> This is not production ready.
|
|
5
|
-
|
|
6
3
|
**objc-js** is an Objective-C bridge for Node.js. This is a fork of [nobjc](https://github.com/nmggithub/nobjc) by [Noah Gregory](https://github.com/nmggithub).
|
|
7
4
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
### Basic Usage
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
import { NobjcLibrary } from "objc-js";
|
|
14
|
-
|
|
15
|
-
// Load a framework
|
|
16
|
-
const foundation = new NobjcLibrary("/System/Library/Frameworks/Foundation.framework/Foundation");
|
|
17
|
-
|
|
18
|
-
// Get a class and call methods
|
|
19
|
-
const NSString = foundation["NSString"];
|
|
20
|
-
const str = NSString.stringWithUTF8String$("Hello, World!");
|
|
21
|
-
console.log(str.toString());
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Subclassing Objective-C Classes
|
|
25
|
-
|
|
26
|
-
**objc-js** supports defining new Objective-C classes and subclassing existing ones from JavaScript. This allows you to override methods, implement custom behavior, and integrate deeply with macOS frameworks.
|
|
27
|
-
|
|
28
|
-
See [Subclassing Documentation](./docs/subclassing.md) for detailed usage and examples.
|
|
29
|
-
|
|
30
|
-
#### Quick Example
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
import { NobjcClass } from "objc-js";
|
|
34
|
-
|
|
35
|
-
const MyClass = NobjcClass.define({
|
|
36
|
-
name: "MyCustomClass",
|
|
37
|
-
superclass: "NSObject",
|
|
38
|
-
methods: {
|
|
39
|
-
description: {
|
|
40
|
-
types: "@@:", // returns NSString
|
|
41
|
-
implementation: (self) => {
|
|
42
|
-
const superDesc = NobjcClass.super(self, "description");
|
|
43
|
-
const prefix = NSString.stringWithUTF8String$("Custom: ");
|
|
44
|
-
return prefix.stringByAppendingString$(superDesc);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const instance = MyClass.alloc().init();
|
|
51
|
-
console.log(instance.description().toString()); // "Custom: <MyCustomClass: 0x...>"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Protocol Implementation
|
|
55
|
-
|
|
56
|
-
**objc-js** supports creating Objective-C protocol implementations from JavaScript. This allows you to create delegate objects that can be passed to Objective-C APIs.
|
|
57
|
-
|
|
58
|
-
#### Creating a Protocol Implementation
|
|
59
|
-
|
|
60
|
-
Use `NobjcProtocol.implement()` to create an object that implements a protocol:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import { NobjcProtocol } from "objc-js";
|
|
64
|
-
|
|
65
|
-
const delegate = NobjcProtocol.implement("ASAuthorizationControllerDelegate", {
|
|
66
|
-
authorizationController$didCompleteWithAuthorization$: (controller, authorization) => {
|
|
67
|
-
console.log("Authorization completed successfully!");
|
|
68
|
-
console.log("Authorization:", authorization);
|
|
69
|
-
},
|
|
70
|
-
authorizationController$didCompleteWithError$: (controller, error) => {
|
|
71
|
-
console.error("Authorization failed:", error);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Pass the delegate to an Objective-C API
|
|
76
|
-
authController.setDelegate$(delegate);
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
#### Method Naming Convention
|
|
80
|
-
|
|
81
|
-
Method names use the `$` notation to represent colons in Objective-C selectors:
|
|
82
|
-
|
|
83
|
-
- Objective-C: `authorizationController:didCompleteWithAuthorization:`
|
|
84
|
-
- JavaScript: `authorizationController$didCompleteWithAuthorization$`
|
|
85
|
-
|
|
86
|
-
#### Argument and Return Value Marshalling
|
|
87
|
-
|
|
88
|
-
Arguments are automatically converted between JavaScript and Objective-C:
|
|
89
|
-
|
|
90
|
-
- **Primitives**: Numbers, booleans, and strings are automatically converted
|
|
91
|
-
- **Objects**: Objective-C objects are wrapped in `NobjcObject` instances
|
|
92
|
-
- **null/nil**: JavaScript `null` maps to Objective-C `nil` and vice versa
|
|
93
|
-
|
|
94
|
-
#### Memory Management
|
|
95
|
-
|
|
96
|
-
Memory is automatically managed:
|
|
97
|
-
|
|
98
|
-
- JavaScript callbacks are kept alive as long as the delegate object exists
|
|
99
|
-
- When the Objective-C object is deallocated, the callbacks are automatically released
|
|
100
|
-
- No manual cleanup is required
|
|
101
|
-
|
|
102
|
-
#### Example: WebAuthn/Passkeys with AuthenticationServices
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
import { NobjcLibrary, NobjcProtocol } from "objc-js";
|
|
106
|
-
|
|
107
|
-
const authServices = new NobjcLibrary(
|
|
108
|
-
"/System/Library/Frameworks/AuthenticationServices.framework/AuthenticationServices"
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// Create authorization requests
|
|
112
|
-
const ASAuthorizationController = authServices["ASAuthorizationController"];
|
|
113
|
-
const controller = ASAuthorizationController.alloc().initWithAuthorizationRequests$(requests);
|
|
114
|
-
|
|
115
|
-
// Create a delegate
|
|
116
|
-
const delegate = NobjcProtocol.implement("ASAuthorizationControllerDelegate", {
|
|
117
|
-
authorizationController$didCompleteWithAuthorization$: (controller, authorization) => {
|
|
118
|
-
// Handle successful authorization
|
|
119
|
-
const credential = authorization.credential();
|
|
120
|
-
console.log("Credential:", credential);
|
|
121
|
-
},
|
|
122
|
-
authorizationController$didCompleteWithError$: (controller, error) => {
|
|
123
|
-
// Handle error
|
|
124
|
-
console.error("Authorization error:", error.localizedDescription());
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Set the delegate and perform requests
|
|
129
|
-
controller.setDelegate$(delegate);
|
|
130
|
-
controller.performRequests();
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
#### Notes
|
|
134
|
-
|
|
135
|
-
- Protocol implementations are created at runtime using the Objective-C runtime APIs
|
|
136
|
-
- If a protocol is not found, a class is still created (useful for informal protocols)
|
|
137
|
-
- Method signatures are inferred from the protocol or from the method name
|
|
138
|
-
- Thread safety: Currently assumes single-threaded (main thread) usage
|
|
139
|
-
|
|
140
|
-
### API Reference
|
|
141
|
-
|
|
142
|
-
#### `NobjcLibrary`
|
|
143
|
-
|
|
144
|
-
Creates a proxy for accessing Objective-C classes from a framework.
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
const framework = new NobjcLibrary(path: string);
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
#### `NobjcObject`
|
|
151
|
-
|
|
152
|
-
Wrapper for Objective-C objects. Methods can be called using the `$` notation.
|
|
5
|
+
## Installation
|
|
153
6
|
|
|
154
|
-
|
|
155
|
-
const result = object.methodName$arg1$arg2$(arg1, arg2);
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
#### `NobjcClass`
|
|
159
|
-
|
|
160
|
-
Static class for defining new Objective-C classes and subclassing existing ones.
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
NobjcClass.define(definition: ClassDefinition): NobjcObject
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Parameters:**
|
|
7
|
+
### Prerequisites
|
|
167
8
|
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
- `
|
|
171
|
-
- `definition.methods`: Object mapping selector names to method definitions
|
|
9
|
+
- Node.js / Bun
|
|
10
|
+
- Xcode Command Line Tools (Run `xcode-select --install` to install)
|
|
11
|
+
- `pkg-config` from Homebrew (Run `brew install pkgconf` to install)
|
|
172
12
|
|
|
173
|
-
|
|
13
|
+
> [!NOTE]
|
|
14
|
+
> **Why are these prerequisites required?**
|
|
15
|
+
>
|
|
16
|
+
> These are required to rebuild the native code for your system.
|
|
174
17
|
|
|
175
|
-
|
|
18
|
+
### Install using npm
|
|
176
19
|
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Call the superclass implementation of a method. Supports methods with any number of arguments, including methods with out-parameters (like `NSError**`).
|
|
182
|
-
|
|
183
|
-
#### `NobjcProtocol`
|
|
184
|
-
|
|
185
|
-
Static class for creating protocol implementations.
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
NobjcProtocol.implement(
|
|
189
|
-
protocolName: string,
|
|
190
|
-
methodImplementations: Record<string, (...args: any[]) => any>
|
|
191
|
-
): NobjcObject
|
|
20
|
+
```bash
|
|
21
|
+
npm install objc-js
|
|
192
22
|
```
|
|
193
23
|
|
|
194
|
-
|
|
24
|
+
### Install using bun
|
|
195
25
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
**Returns:** A `NobjcObject` that can be passed to Objective-C APIs expecting the protocol
|
|
200
|
-
|
|
201
|
-
#### `getPointer()`
|
|
202
|
-
|
|
203
|
-
Get the raw native pointer for a NobjcObject as a Node Buffer. This is useful for passing Objective-C objects to native APIs that expect raw pointers, such as Electron's native window handles.
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
function getPointer(obj: NobjcObject): Buffer;
|
|
26
|
+
```bash
|
|
27
|
+
bun add objc-js
|
|
28
|
+
# and `bun pm trust -a` to run the rebuild if needed
|
|
207
29
|
```
|
|
208
30
|
|
|
209
|
-
|
|
31
|
+
## Documentation
|
|
210
32
|
|
|
211
|
-
|
|
33
|
+
The documentation is organized into several guides:
|
|
212
34
|
|
|
213
|
-
**
|
|
35
|
+
- **[Basic Usage](./docs/basic-usage.md)** - Getting started with loading frameworks and calling methods
|
|
36
|
+
- **[Subclassing Objective-C Classes](./docs/subclassing.md)** - Creating and subclassing Objective-C classes from JavaScript
|
|
37
|
+
- **[Protocol Implementation](./docs/protocol-implementation.md)** - Creating delegate objects that implement protocols
|
|
38
|
+
- **[API Reference](./docs/api-reference.md)** - Complete API documentation for all classes and functions
|
|
214
39
|
|
|
215
|
-
|
|
40
|
+
## Quick Start
|
|
216
41
|
|
|
217
42
|
```typescript
|
|
218
|
-
import { NobjcLibrary
|
|
219
|
-
|
|
220
|
-
// Load AppKit framework
|
|
221
|
-
const appKit = new NobjcLibrary("/System/Library/Frameworks/AppKit.framework/AppKit");
|
|
222
|
-
|
|
223
|
-
// Get an NSWindow and its content view
|
|
224
|
-
const NSApplication = appKit["NSApplication"];
|
|
225
|
-
const app = NSApplication.sharedApplication();
|
|
226
|
-
const window = app.mainWindow();
|
|
227
|
-
const view = window.contentView();
|
|
228
|
-
|
|
229
|
-
// Get the raw pointer as a Buffer
|
|
230
|
-
const pointerBuffer = getPointer(view);
|
|
231
|
-
|
|
232
|
-
// Read the pointer as a BigInt (64-bit unsigned integer)
|
|
233
|
-
const pointer = pointerBuffer.readBigUInt64LE(0);
|
|
234
|
-
console.log(`NSView pointer: 0x${pointer.toString(16)}`);
|
|
235
|
-
|
|
236
|
-
// Use with Electron or other native APIs
|
|
237
|
-
// const { BrowserWindow } = require('electron');
|
|
238
|
-
// const win = BrowserWindow.fromId(pointer);
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
**Note:** The pointer is returned as a Buffer in little-endian format. Use `readBigUInt64LE(0)` to read it as a 64-bit unsigned integer, which is the standard pointer size on macOS.
|
|
242
|
-
|
|
243
|
-
#### `fromPointer()`
|
|
244
|
-
|
|
245
|
-
Create a NobjcObject from a raw native pointer. This is the inverse of `getPointer()` and allows you to reconstruct an Objective-C object from a pointer address.
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
function fromPointer(pointer: Buffer | bigint): NobjcObject;
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
**Parameters:**
|
|
252
|
-
|
|
253
|
-
- `pointer`: A Buffer (8 bytes in little-endian format) or BigInt containing the pointer address
|
|
254
|
-
|
|
255
|
-
**Returns:** A NobjcObject wrapping the native Objective-C object
|
|
256
|
-
|
|
257
|
-
**Example: Round-trip pointer conversion**
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
import { NobjcLibrary, getPointer, fromPointer } from "objc-js";
|
|
43
|
+
import { NobjcLibrary } from "objc-js";
|
|
261
44
|
|
|
45
|
+
// Load a framework
|
|
262
46
|
const foundation = new NobjcLibrary("/System/Library/Frameworks/Foundation.framework/Foundation");
|
|
263
|
-
const NSString = foundation["NSString"];
|
|
264
|
-
|
|
265
|
-
// Create an original object
|
|
266
|
-
const original = NSString.stringWithUTF8String$("Hello, World!");
|
|
267
|
-
console.log(original.toString()); // "Hello, World!"
|
|
268
|
-
|
|
269
|
-
// Get the pointer
|
|
270
|
-
const pointerBuffer = getPointer(original);
|
|
271
|
-
const pointer = pointerBuffer.readBigUInt64LE(0);
|
|
272
|
-
console.log(`Pointer: 0x${pointer.toString(16)}`);
|
|
273
|
-
|
|
274
|
-
// Reconstruct the object from the pointer
|
|
275
|
-
const reconstructed = fromPointer(pointer);
|
|
276
|
-
// or: const reconstructed = fromPointer(pointerBuffer);
|
|
277
|
-
|
|
278
|
-
console.log(reconstructed.toString()); // "Hello, World!"
|
|
279
47
|
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
console.log(
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**Example: Using with external native APIs**
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
// Receive a pointer from an external API
|
|
290
|
-
const externalPointer = 0x12345678n; // Example pointer from native code
|
|
291
|
-
|
|
292
|
-
// Convert it to a NobjcObject
|
|
293
|
-
const nsObject = fromPointer(externalPointer);
|
|
294
|
-
|
|
295
|
-
// Now you can call Objective-C methods on it
|
|
296
|
-
console.log(nsObject.description());
|
|
48
|
+
// Get a class and call methods
|
|
49
|
+
const NSString = foundation["NSString"];
|
|
50
|
+
const str = NSString.stringWithUTF8String$("Hello, World!");
|
|
51
|
+
console.log(str.toString());
|
|
297
52
|
```
|
|
298
53
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
This function is **inherently unsafe** and should be used with extreme caution:
|
|
302
|
-
|
|
303
|
-
- **Invalid pointers will crash your program**: The pointer must point to a valid Objective-C object
|
|
304
|
-
- **Dangling pointers**: The object must still be alive (not deallocated). Accessing a deallocated object will crash
|
|
305
|
-
- **No type checking**: There's no way to verify the pointer points to the expected type of object
|
|
306
|
-
- **Memory management**: Be aware of Objective-C reference counting. The object must remain valid for the lifetime of your usage
|
|
307
|
-
|
|
308
|
-
Only use this function when:
|
|
309
|
-
|
|
310
|
-
- You received the pointer from `getPointer()` and the object is still alive
|
|
311
|
-
- You received the pointer from a trusted native API that guarantees the object's validity
|
|
312
|
-
- You're interfacing with external native code that provides valid Objective-C object pointers
|
|
54
|
+
For more examples and detailed guides, see the [documentation](./docs/basic-usage.md).
|
package/binding.gyp
CHANGED
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
],
|
|
17
17
|
"include_dirs": [
|
|
18
18
|
"<!@(node -p \"require('node-addon-api').include\")",
|
|
19
|
-
"<!@(
|
|
19
|
+
"<!@(brew --prefix libffi)/include"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": [
|
|
22
22
|
"<!(node -p \"require('node-addon-api').gyp\")"
|
|
23
23
|
],
|
|
24
24
|
"libraries": [
|
|
25
|
-
"
|
|
25
|
+
"<!@(brew --prefix libffi)/lib/libffi.a"
|
|
26
26
|
],
|
|
27
27
|
"xcode_settings": {
|
|
28
28
|
"MACOSX_DEPLOYMENT_TARGET": "13.3",
|
package/dist/native.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
|
-
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const binding = require("node-gyp-build")(path.join(__dirname, ".."));
|
|
4
5
|
const { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation, DefineClass, CallSuper } = binding;
|
|
5
6
|
export { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation, DefineClass, CallSuper };
|
package/package.json
CHANGED
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist/",
|
|
21
|
-
"
|
|
21
|
+
"prebuilds/",
|
|
22
22
|
"binding.gyp",
|
|
23
23
|
"src/native"
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
|
-
"install": "node-gyp
|
|
26
|
+
"install": "node-gyp-build",
|
|
27
27
|
"build-native": "node-gyp build",
|
|
28
28
|
"build-scripts": "tsc --project scripts/tsconfig.json",
|
|
29
29
|
"build-source": "tsc --project src/ts/tsconfig.json",
|
|
@@ -38,18 +38,23 @@
|
|
|
38
38
|
"test:protocol-implementation": "bun test tests/test-protocol-implementation.test.ts",
|
|
39
39
|
"make-clangd-config": "node ./scripts/make-clangd-config.js",
|
|
40
40
|
"format": "prettier --write \"**/*.{ts,js,json,md}\"",
|
|
41
|
-
"preinstall-disabled": "npm run build-scripts && npm run make-clangd-config"
|
|
41
|
+
"preinstall-disabled": "npm run build-scripts && npm run make-clangd-config",
|
|
42
|
+
"prebuild:arm64": "prebuildify --napi --strip --arch arm64",
|
|
43
|
+
"prebuild:x64": "prebuildify --napi --strip --arch x64",
|
|
44
|
+
"prebuild:all": "bun run prebuild:arm64 && bun run prebuild:x64"
|
|
42
45
|
},
|
|
43
|
-
"version": "1.0.
|
|
46
|
+
"version": "1.0.2",
|
|
44
47
|
"description": "Objective-C bridge for Node.js",
|
|
45
48
|
"main": "dist/index.js",
|
|
46
49
|
"dependencies": {
|
|
47
|
-
"node-addon-api": "^8.5.0"
|
|
50
|
+
"node-addon-api": "^8.5.0",
|
|
51
|
+
"node-gyp-build": "^4.8.4"
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
50
54
|
"@types/bun": "latest",
|
|
51
55
|
"@types/node": "^20.0.0",
|
|
52
|
-
"node-gyp": "^
|
|
56
|
+
"node-gyp": "^12.1.0",
|
|
57
|
+
"prebuildify": "^6.0.1",
|
|
53
58
|
"prettier": "^3.7.4",
|
|
54
59
|
"typescript": "^5.9.3"
|
|
55
60
|
},
|
|
Binary file
|
|
Binary file
|
|
Binary file
|