objc-js 0.0.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 iamEvan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # objc-js
2
+
3
+ > [!WARNING]
4
+ > This is not production ready.
5
+
6
+ **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
+
8
+ ## Usage
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
+ ### Protocol Implementation
25
+
26
+ **objc-js** now supports creating Objective-C protocol implementations from JavaScript. This allows you to create delegate objects that can be passed to Objective-C APIs.
27
+
28
+ #### Creating a Protocol Implementation
29
+
30
+ Use `NobjcProtocol.implement()` to create an object that implements a protocol:
31
+
32
+ ```typescript
33
+ import { NobjcProtocol } from "objc-js";
34
+
35
+ const delegate = NobjcProtocol.implement("ASAuthorizationControllerDelegate", {
36
+ authorizationController$didCompleteWithAuthorization$: (controller, authorization) => {
37
+ console.log("Authorization completed successfully!");
38
+ console.log("Authorization:", authorization);
39
+ },
40
+ authorizationController$didCompleteWithError$: (controller, error) => {
41
+ console.error("Authorization failed:", error);
42
+ }
43
+ });
44
+
45
+ // Pass the delegate to an Objective-C API
46
+ authController.setDelegate$(delegate);
47
+ ```
48
+
49
+ #### Method Naming Convention
50
+
51
+ Method names use the `$` notation to represent colons in Objective-C selectors:
52
+
53
+ - Objective-C: `authorizationController:didCompleteWithAuthorization:`
54
+ - JavaScript: `authorizationController$didCompleteWithAuthorization$`
55
+
56
+ #### Argument and Return Value Marshalling
57
+
58
+ Arguments are automatically converted between JavaScript and Objective-C:
59
+
60
+ - **Primitives**: Numbers, booleans, and strings are automatically converted
61
+ - **Objects**: Objective-C objects are wrapped in `NobjcObject` instances
62
+ - **null/nil**: JavaScript `null` maps to Objective-C `nil` and vice versa
63
+
64
+ #### Memory Management
65
+
66
+ Memory is automatically managed:
67
+
68
+ - JavaScript callbacks are kept alive as long as the delegate object exists
69
+ - When the Objective-C object is deallocated, the callbacks are automatically released
70
+ - No manual cleanup is required
71
+
72
+ #### Example: WebAuthn/Passkeys with AuthenticationServices
73
+
74
+ ```typescript
75
+ import { NobjcLibrary, NobjcProtocol } from "objc-js";
76
+
77
+ const authServices = new NobjcLibrary(
78
+ "/System/Library/Frameworks/AuthenticationServices.framework/AuthenticationServices"
79
+ );
80
+
81
+ // Create authorization requests
82
+ const ASAuthorizationController = authServices["ASAuthorizationController"];
83
+ const controller = ASAuthorizationController.alloc().initWithAuthorizationRequests$(requests);
84
+
85
+ // Create a delegate
86
+ const delegate = NobjcProtocol.implement("ASAuthorizationControllerDelegate", {
87
+ authorizationController$didCompleteWithAuthorization$: (controller, authorization) => {
88
+ // Handle successful authorization
89
+ const credential = authorization.credential();
90
+ console.log("Credential:", credential);
91
+ },
92
+ authorizationController$didCompleteWithError$: (controller, error) => {
93
+ // Handle error
94
+ console.error("Authorization error:", error.localizedDescription());
95
+ }
96
+ });
97
+
98
+ // Set the delegate and perform requests
99
+ controller.setDelegate$(delegate);
100
+ controller.performRequests();
101
+ ```
102
+
103
+ #### Notes
104
+
105
+ - Protocol implementations are created at runtime using the Objective-C runtime APIs
106
+ - If a protocol is not found, a class is still created (useful for informal protocols)
107
+ - Method signatures are inferred from the protocol or from the method name
108
+ - Thread safety: Currently assumes single-threaded (main thread) usage
109
+
110
+ ### API Reference
111
+
112
+ #### `NobjcLibrary`
113
+
114
+ Creates a proxy for accessing Objective-C classes from a framework.
115
+
116
+ ```typescript
117
+ const framework = new NobjcLibrary(path: string);
118
+ ```
119
+
120
+ #### `NobjcObject`
121
+
122
+ Wrapper for Objective-C objects. Methods can be called using the `$` notation.
123
+
124
+ ```typescript
125
+ const result = object.methodName$arg1$arg2$(arg1, arg2);
126
+ ```
127
+
128
+ #### `NobjcProtocol`
129
+
130
+ Static class for creating protocol implementations.
131
+
132
+ ```typescript
133
+ NobjcProtocol.implement(
134
+ protocolName: string,
135
+ methodImplementations: Record<string, (...args: any[]) => any>
136
+ ): NobjcObject
137
+ ```
138
+
139
+ **Parameters:**
140
+
141
+ - `protocolName`: The name of the Objective-C protocol (e.g., "NSCopying", "ASAuthorizationControllerDelegate")
142
+ - `methodImplementations`: An object mapping method names (using `$` notation) to JavaScript functions
143
+
144
+ **Returns:** A `NobjcObject` that can be passed to Objective-C APIs expecting the protocol
Binary file
@@ -0,0 +1,16 @@
1
+ import { NobjcNative } from "./native.js";
2
+ declare class NobjcLibrary {
3
+ [key: string]: NobjcObject;
4
+ constructor(library: string);
5
+ }
6
+ declare class NobjcObject {
7
+ [key: string]: NobjcMethod;
8
+ constructor(object: NobjcNative.ObjcObject);
9
+ }
10
+ declare class NobjcMethod {
11
+ constructor(object: NobjcNative.ObjcObject, methodName: string);
12
+ }
13
+ declare class NobjcProtocol {
14
+ static implement(protocolName: string, methodImplementations: Record<string, (...args: any[]) => any>): NobjcObject;
15
+ }
16
+ export { NobjcLibrary, NobjcObject, NobjcMethod, NobjcProtocol };
package/dist/index.js ADDED
@@ -0,0 +1,112 @@
1
+ import { LoadLibrary, GetClassObject, ObjcObject, CreateProtocolImplementation } from "./native.js";
2
+ const NATIVE_OBJC_OBJECT = Symbol("nativeObjcObject");
3
+ class NobjcLibrary {
4
+ constructor(library) {
5
+ const handler = {
6
+ wasLoaded: false,
7
+ get(_, className) {
8
+ if (!this.wasLoaded) {
9
+ LoadLibrary(library);
10
+ this.wasLoaded = true;
11
+ }
12
+ return new NobjcObject(GetClassObject(className));
13
+ }
14
+ };
15
+ return new Proxy({}, handler);
16
+ }
17
+ }
18
+ function NobjcMethodNameToObjcSelector(methodName) {
19
+ return methodName.replace(/\$/g, ":");
20
+ }
21
+ // unused, might be useful for codegen later
22
+ function ObjcSelectorToNobjcMethodName(selector) {
23
+ return selector.replace(/:/g, "$");
24
+ }
25
+ class NobjcObject {
26
+ constructor(object) {
27
+ const handler = {
28
+ has(target, p) {
29
+ // Return true for the special Symbol to enable unwrapping
30
+ if (p === NATIVE_OBJC_OBJECT)
31
+ return true;
32
+ // guard against other symbols
33
+ if (typeof p === "symbol")
34
+ return Reflect.has(target, p);
35
+ // toString is always present
36
+ if (p === "toString")
37
+ return true;
38
+ // check if the object responds to the selector
39
+ return target.$msgSend("respondsToSelector:", NobjcMethodNameToObjcSelector(p.toString()));
40
+ },
41
+ get(target, methodName, receiver) {
42
+ // Return the underlying native object when Symbol is accessed
43
+ if (methodName === NATIVE_OBJC_OBJECT) {
44
+ return target;
45
+ }
46
+ // guard against symbols
47
+ if (typeof methodName === "symbol") {
48
+ return Reflect.get(object, methodName, receiver);
49
+ }
50
+ // handle toString separately
51
+ if (methodName === "toString") {
52
+ return () => String(object.$msgSend("description"));
53
+ }
54
+ if (!(methodName in receiver)) {
55
+ throw new Error(`Method ${methodName} not found on object ${receiver}`);
56
+ }
57
+ return new NobjcMethod(object, methodName);
58
+ }
59
+ };
60
+ return new Proxy(object, handler);
61
+ }
62
+ }
63
+ function unwrapArg(arg) {
64
+ if (arg && typeof arg === "object" && NATIVE_OBJC_OBJECT in arg) {
65
+ return arg[NATIVE_OBJC_OBJECT];
66
+ }
67
+ return arg;
68
+ }
69
+ class NobjcMethod {
70
+ constructor(object, methodName) {
71
+ const selector = NobjcMethodNameToObjcSelector(methodName);
72
+ // This cannot be an arrow function because we need to access `arguments`.
73
+ function methodFunc() {
74
+ const unwrappedArgs = Array.from(arguments).map(unwrapArg);
75
+ const result = object.$msgSend(selector, ...unwrappedArgs);
76
+ if (typeof result == "object" && result instanceof ObjcObject) {
77
+ return new NobjcObject(result);
78
+ }
79
+ return result;
80
+ }
81
+ const handler = {};
82
+ return new Proxy(methodFunc, handler);
83
+ }
84
+ }
85
+ class NobjcProtocol {
86
+ static implement(protocolName, methodImplementations) {
87
+ // Convert method names from $ notation to : notation
88
+ const convertedMethods = {};
89
+ for (const [methodName, impl] of Object.entries(methodImplementations)) {
90
+ const selector = NobjcMethodNameToObjcSelector(methodName);
91
+ // Wrap the implementation to unwrap args and wrap return values
92
+ convertedMethods[selector] = function (...args) {
93
+ const unwrappedArgs = args.map(unwrapArg);
94
+ const result = impl(...unwrappedArgs);
95
+ // If the result is already a NobjcObject, unwrap it to get the native object
96
+ if (result && typeof result === "object" && NATIVE_OBJC_OBJECT in result) {
97
+ return result[NATIVE_OBJC_OBJECT];
98
+ }
99
+ // If the result is a native ObjcObject, return it as-is
100
+ if (typeof result === "object" && result instanceof ObjcObject) {
101
+ return result;
102
+ }
103
+ return result;
104
+ };
105
+ }
106
+ // Call native implementation
107
+ const nativeObj = CreateProtocolImplementation(protocolName, convertedMethods);
108
+ // Wrap in NobjcObject proxy
109
+ return new NobjcObject(nativeObj);
110
+ }
111
+ }
112
+ export { NobjcLibrary, NobjcObject, NobjcMethod, NobjcProtocol };
@@ -0,0 +1,4 @@
1
+ import * as _binding from "#nobjc_native";
2
+ declare const LoadLibrary: typeof _binding.LoadLibrary, GetClassObject: typeof _binding.GetClassObject, ObjcObject: typeof _binding.ObjcObject, CreateProtocolImplementation: typeof _binding.CreateProtocolImplementation;
3
+ export { LoadLibrary, GetClassObject, ObjcObject, CreateProtocolImplementation };
4
+ export type { _binding as NobjcNative };
package/dist/native.js ADDED
@@ -0,0 +1,5 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ const binding = require("#nobjc_native");
4
+ const { LoadLibrary, GetClassObject, ObjcObject, CreateProtocolImplementation } = binding;
5
+ export { LoadLibrary, GetClassObject, ObjcObject, CreateProtocolImplementation };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "objc-js",
3
+ "private": false,
4
+ "access": "public",
5
+ "type": "module",
6
+ "os": [
7
+ "darwin"
8
+ ],
9
+ "repository": "https://github.com/iamEvanYT/objc-js",
10
+ "author": "iamEvan",
11
+ "imports": {
12
+ "#nobjc_native": "./build/Release/nobjc_native.node"
13
+ },
14
+ "exports": {
15
+ ".": "./dist/index.js",
16
+ "./native": "./build/Release/nobjc_native.node"
17
+ },
18
+ "files": [
19
+ "dist/",
20
+ "build/Release/nobjc_native.node"
21
+ ],
22
+ "scripts": {
23
+ "build-native": "node-gyp build",
24
+ "build-scripts": "tsc --project scripts/tsconfig.json",
25
+ "build-source": "tsc --project src/ts/tsconfig.json",
26
+ "prebuild": "node-gyp clean && node-gyp configure",
27
+ "build": "npm run build-native && npm run build-scripts && npm run build-source",
28
+ "pretest": "npm run build",
29
+ "test": "bun test",
30
+ "test:native": "bun test tests/test-native-code.test.ts",
31
+ "test:js": "bun test tests/test-js-code.test.ts",
32
+ "test:string-lifetime": "bun test tests/test-string-lifetime.test.ts",
33
+ "test:object-arguments": "bun test tests/test-object-arguments.test.ts",
34
+ "test:protocol-implementation": "bun test tests/test-protocol-implementation.test.ts",
35
+ "make-clangd-config": "node ./scripts/make-clangd-config.js",
36
+ "format": "prettier --write \"**/*.{ts,js,json,md}\"",
37
+ "preinstall-disabled": "npm run build-scripts && npm run make-clangd-config"
38
+ },
39
+ "version": "0.0.4",
40
+ "description": "Objective-C bridge for Node.js",
41
+ "main": "dist/index.js",
42
+ "devDependencies": {
43
+ "@types/bun": "latest",
44
+ "@types/node": "^20.0.0",
45
+ "node-addon-api": "^8.5.0",
46
+ "node-gyp": "^11.4.2",
47
+ "prettier": "^3.7.4",
48
+ "typescript": "^5.9.3"
49
+ },
50
+ "gypfile": true
51
+ }