brick-module 0.1.25 → 0.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/android/brick_modules.gradle +12 -1
- package/android/src/main/java/com/brickmodule/BrickModuleBase.kt +22 -25
- package/android/src/main/java/com/brickmodule/BrickModuleRegistry.kt +1 -32
- package/dist/BrickModule.d.ts +1 -10
- package/dist/BrickModule.js +31 -30
- package/ios/BrickModule/BrickCoreModule.swift +9 -15
- package/ios/BrickModule/BrickModuleRegistry.swift +22 -27
- package/package.json +2 -6
- package/src/BrickModule.ts +68 -63
|
@@ -396,7 +396,18 @@ ${moduleNames}
|
|
|
396
396
|
project.preBuild.doFirst {
|
|
397
397
|
def workingDir = projectRoot
|
|
398
398
|
|
|
399
|
-
//
|
|
399
|
+
// Skip codegen if already generated to prevent deleting Gradle build outputs
|
|
400
|
+
// The codegen is already run during settings.gradle phase (applyBrickModules -> runBrickCodegen)
|
|
401
|
+
// Re-running here with --clean would delete .brick_android/build/ directory
|
|
402
|
+
// which contains package-aware-r.txt needed by dependent modules
|
|
403
|
+
def brickOutputDir = getBrickAndroidPath(workingDir)
|
|
404
|
+
def generatedMarker = new File(brickOutputDir, "src/main/kotlin/BrickModule.kt")
|
|
405
|
+
if (generatedMarker.exists()) {
|
|
406
|
+
// Already generated, skip to preserve build outputs
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Run codegen only if not yet generated
|
|
400
411
|
try {
|
|
401
412
|
def proc = ['node', '-p', "require.resolve('brick-module/package.json')"].execute(null, workingDir)
|
|
402
413
|
proc.waitFor()
|
|
@@ -9,13 +9,22 @@ import com.facebook.react.bridge.ReactContext
|
|
|
9
9
|
interface BrickModuleBase {
|
|
10
10
|
/** The name of the module (required for registration) */
|
|
11
11
|
val moduleName: String
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
/**
|
|
14
14
|
* Returns the constants exposed by this module
|
|
15
15
|
* Override this method to provide module-specific constants
|
|
16
16
|
* @return Map of constant name to value
|
|
17
17
|
*/
|
|
18
18
|
fun getConstants(): Map<String, Any> = emptyMap()
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Emits an event to JavaScript using React Native's auto-generated event emitters
|
|
22
|
+
* This method should be implemented by the bridge layer to call the appropriate
|
|
23
|
+
* auto-generated emitModuleName_onEventName method from React Native Codegen
|
|
24
|
+
* @param eventName The name of the event (e.g., "onCalculationCompleted")
|
|
25
|
+
* @param payload The event data as a Map
|
|
26
|
+
*/
|
|
27
|
+
fun emitEvent(eventName: String, payload: Map<String, Any>)
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
/**
|
|
@@ -31,33 +40,21 @@ abstract class BrickModuleSpec(private val reactContext: ReactContext) : BrickMo
|
|
|
31
40
|
protected fun getReactContext(): ReactContext = reactContext
|
|
32
41
|
|
|
33
42
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @param eventName The name of the event (without module prefix)
|
|
38
|
-
* @param data The event data to send
|
|
43
|
+
* Event emitter callback set by the bridge layer
|
|
44
|
+
* This allows the bridge to provide access to auto-generated event emitter methods
|
|
39
45
|
*/
|
|
40
|
-
|
|
41
|
-
val context = getReactContext()
|
|
46
|
+
var eventEmitterCallback: ((String, Map<String, Any>) -> Unit)? = null
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Emits an event to JavaScript using React Native's auto-generated event emitters
|
|
50
|
+
* Delegates to the bridge layer which has access to the generated emitModuleName_onEventName methods
|
|
51
|
+
*/
|
|
52
|
+
override fun emitEvent(eventName: String, payload: Map<String, Any>) {
|
|
53
|
+
eventEmitterCallback?.invoke(eventName, payload)
|
|
54
|
+
?: throw IllegalStateException(
|
|
55
|
+
"Event emitter callback not set for module $moduleName. " +
|
|
56
|
+
"Events cannot be emitted until the module is registered with the bridge."
|
|
51
57
|
)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Send event via BrickHost's moduleRegistry
|
|
56
|
-
try {
|
|
57
|
-
activity.getModuleRegistry().sendEvent(context, fullEventName, data)
|
|
58
|
-
} catch (e: Exception) {
|
|
59
|
-
println("❌ $moduleName: Failed to send event $fullEventName: ${e.message}")
|
|
60
|
-
}
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.brickmodule
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReactContext
|
|
4
|
-
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
5
4
|
import java.util.concurrent.ConcurrentHashMap
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -73,37 +72,7 @@ class BrickModuleRegistry {
|
|
|
73
72
|
return modules.keys.toList().sorted()
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
* Sends an event to JavaScript Used by modules to emit events to JavaScript
|
|
78
|
-
*
|
|
79
|
-
* @param context The ReactContext to send the event to
|
|
80
|
-
* @param eventName The name of the event to send
|
|
81
|
-
* @param data The event data
|
|
82
|
-
*/
|
|
83
|
-
fun sendEvent(context: ReactContext, eventName: String, data: Any?) {
|
|
84
|
-
try {
|
|
85
|
-
// Ensure modules are registered
|
|
86
|
-
if (!isRegistered) {
|
|
87
|
-
println(
|
|
88
|
-
"⚠️ BrickModuleRegistry: Modules not registered, skipping event: $eventName"
|
|
89
|
-
)
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Convert data to React Native compatible format using helper
|
|
94
|
-
val eventData = EventDataConverter.convert(data)
|
|
95
|
-
|
|
96
|
-
println("[BrickModule] sendEvent Context: $context")
|
|
97
|
-
|
|
98
|
-
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
99
|
-
.emit(eventName, eventData)
|
|
100
|
-
|
|
101
|
-
println("📡 BrickModuleRegistry: Event sent successfully: $eventName")
|
|
102
|
-
} catch (e: Exception) {
|
|
103
|
-
println("❌ BrickModuleRegistry: Failed to send event $eventName: ${e.message}")
|
|
104
|
-
e.printStackTrace()
|
|
105
|
-
}
|
|
106
|
-
}
|
|
75
|
+
// No DeviceEventEmitter-based event path; events handled via RN Codegen EventEmitter
|
|
107
76
|
|
|
108
77
|
/** Returns all constants from registered modules */
|
|
109
78
|
fun getAllConstants(): Map<String, Any> {
|
package/dist/BrickModule.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { TurboModule } from "react-native";
|
|
|
7
7
|
*/
|
|
8
8
|
interface BrickModuleInterface extends TurboModule {
|
|
9
9
|
readonly moduleName: string;
|
|
10
|
-
readonly supportedEvents?: readonly string[];
|
|
11
10
|
}
|
|
12
11
|
/**
|
|
13
12
|
* Type alias for module specifications
|
|
@@ -23,15 +22,7 @@ type BrickModuleSpec = BrickModuleInterface;
|
|
|
23
22
|
/**
|
|
24
23
|
* Enhanced typed module interface with event listeners
|
|
25
24
|
*/
|
|
26
|
-
type BrickModuleWithEvents<T extends BrickModuleInterface> = T
|
|
27
|
-
/**
|
|
28
|
-
* Adds a type-safe event listener for this module
|
|
29
|
-
* @param eventName - One of the supported events defined in the module spec
|
|
30
|
-
* @param listener - Callback function to handle the event
|
|
31
|
-
* @returns Unsubscription function to remove the listener
|
|
32
|
-
*/
|
|
33
|
-
addEventListener<TEvent = unknown>(eventName: T["supportedEvents"] extends readonly (infer U)[] ? U : never, listener: (event: TEvent) => void): () => void;
|
|
34
|
-
};
|
|
25
|
+
type BrickModuleWithEvents<T extends BrickModuleInterface> = T;
|
|
35
26
|
/**
|
|
36
27
|
* Gets a typed module instance by name with explicit type parameter
|
|
37
28
|
* @param moduleName - The exact name of the module as defined in its spec
|
package/dist/BrickModule.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TurboModuleRegistry } from "react-native";
|
|
2
2
|
|
|
3
3
|
//#region src/BrickModule.ts
|
|
4
4
|
const moduleCache = /* @__PURE__ */ new Map();
|
|
5
5
|
let nativeModule = null;
|
|
6
|
-
let eventEmitter = null;
|
|
7
6
|
/**
|
|
8
7
|
* Gets the native TurboModule instance
|
|
9
8
|
* @private
|
|
@@ -12,13 +11,6 @@ function getNativeModule() {
|
|
|
12
11
|
if (!nativeModule) nativeModule = TurboModuleRegistry.getEnforcing("BrickModule");
|
|
13
12
|
return nativeModule;
|
|
14
13
|
}
|
|
15
|
-
function getEventEmitter() {
|
|
16
|
-
if (!eventEmitter) {
|
|
17
|
-
eventEmitter = new NativeEventEmitter(getNativeModule());
|
|
18
|
-
console.log("eventEmitter", eventEmitter);
|
|
19
|
-
}
|
|
20
|
-
return eventEmitter;
|
|
21
|
-
}
|
|
22
14
|
/**
|
|
23
15
|
* Gets a typed module instance by name with explicit type parameter
|
|
24
16
|
* @param moduleName - The exact name of the module as defined in its spec
|
|
@@ -28,33 +20,42 @@ function get(moduleName) {
|
|
|
28
20
|
const cacheKey = moduleName;
|
|
29
21
|
if (moduleCache.has(cacheKey)) return moduleCache.get(cacheKey);
|
|
30
22
|
const nativeModuleInstance = getNativeModule();
|
|
23
|
+
let constantsCache = null;
|
|
31
24
|
const moduleProxy = new Proxy({}, {
|
|
32
25
|
get: (_target, property) => {
|
|
33
26
|
if (typeof property !== "string") return;
|
|
34
|
-
if (property ===
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
27
|
+
if (property.startsWith("on") && property.length > 2 && property[2] === property[2].toUpperCase()) return (listener) => {
|
|
28
|
+
const nativeEventMethod = `${moduleName}_${property}`;
|
|
29
|
+
const nativeFn = nativeModuleInstance[nativeEventMethod];
|
|
30
|
+
if (typeof nativeFn === "function") return nativeFn(listener);
|
|
31
|
+
throw new Error(`${nativeEventMethod} is not available. Did you run RN codegen?`);
|
|
32
|
+
};
|
|
33
|
+
if (property === "getConstants") return () => {
|
|
34
|
+
if (constantsCache !== null) return constantsCache;
|
|
35
|
+
const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
|
|
36
|
+
const moduleConstants = {};
|
|
37
|
+
const constantPrefix = `${moduleName}_`;
|
|
38
|
+
for (const key in allConstants) if (key.startsWith(constantPrefix)) {
|
|
39
|
+
const propName = key.substring(constantPrefix.length);
|
|
40
|
+
moduleConstants[propName] = allConstants[key];
|
|
41
|
+
}
|
|
42
|
+
constantsCache = moduleConstants;
|
|
43
|
+
return moduleConstants;
|
|
51
44
|
};
|
|
52
|
-
const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
|
|
53
|
-
const constantKey = `${moduleName}_${property}`;
|
|
54
|
-
if (constantKey in allConstants) return allConstants[constantKey];
|
|
55
45
|
return (...args) => {
|
|
56
46
|
const methodKey = `${moduleName}_${property}`;
|
|
57
|
-
if (typeof nativeModuleInstance[methodKey] === "function")
|
|
47
|
+
if (typeof nativeModuleInstance[methodKey] === "function") {
|
|
48
|
+
const result = nativeModuleInstance[methodKey](...args);
|
|
49
|
+
if (result && typeof result === "object" && result["~sync"] === true) if (result.success === true) return result.value;
|
|
50
|
+
else {
|
|
51
|
+
const errorInfo = result.error || {};
|
|
52
|
+
const error = new Error(errorInfo.message || "Unknown error");
|
|
53
|
+
error.name = errorInfo.code || "BRICK_ERROR";
|
|
54
|
+
if (errorInfo.details) error.details = errorInfo.details;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
58
59
|
throw new Error(`Method ${methodKey} not found`);
|
|
59
60
|
};
|
|
60
61
|
},
|
|
@@ -9,7 +9,9 @@ open class BrickModuleBase: NSObject {
|
|
|
9
9
|
/// The name of the module (required for registration)
|
|
10
10
|
public let moduleName: String
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/// The registry that manages this module (assigned during registration)
|
|
13
|
+
public weak var registry: BrickModuleRegistry?
|
|
14
|
+
|
|
13
15
|
public weak var bridgeProxy: RCTBridgeProxy?
|
|
14
16
|
|
|
15
17
|
/// Initialize with module name
|
|
@@ -18,21 +20,13 @@ open class BrickModuleBase: NSObject {
|
|
|
18
20
|
super.init()
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Get the shared event emitter instance and send event
|
|
28
|
-
if let eventEmitter = self.eventEmitter {
|
|
29
|
-
eventEmitter.sendEvent(withName: fullEventName, body: data)
|
|
30
|
-
} else {
|
|
31
|
-
let error = NSError(domain: "BrickModule", code: 500, userInfo: [
|
|
32
|
-
NSLocalizedDescriptionKey: "Event emitter not available, cannot send event: \(fullEventName)"
|
|
33
|
-
])
|
|
34
|
-
print("Error: \(error.localizedDescription)")
|
|
23
|
+
// Emit via Registry event map (ObjC++ injects typed handlers per event)
|
|
24
|
+
public func emit(_ eventName: String, payload: [String: Any]) {
|
|
25
|
+
guard let registry = registry else {
|
|
26
|
+
print("⚠️ BrickModuleBase: registry not attached; cannot emit \(moduleName).\(eventName)")
|
|
27
|
+
return
|
|
35
28
|
}
|
|
29
|
+
registry.emitEvent(module: moduleName, event: eventName, payload: payload)
|
|
36
30
|
}
|
|
37
31
|
}
|
|
38
32
|
|
|
@@ -10,8 +10,11 @@ import React
|
|
|
10
10
|
|
|
11
11
|
private var modules: [String: BrickModuleBase] = [:]
|
|
12
12
|
private var isRegistered: Bool = false
|
|
13
|
-
|
|
13
|
+
public weak var brickTurboModule: NSObject?
|
|
14
14
|
private weak var bridgeProxy: RCTBridgeProxy?;
|
|
15
|
+
// Event map injected from BrickModuleImpl (already converted to Swift types)
|
|
16
|
+
// Nested structure: [ModuleName: [EventName: Handler]]
|
|
17
|
+
private var eventMap: [String: [String: ([String: Any]) -> Void]] = [:]
|
|
15
18
|
|
|
16
19
|
public override init() {
|
|
17
20
|
super.init()
|
|
@@ -59,8 +62,8 @@ import React
|
|
|
59
62
|
|
|
60
63
|
// Register the module
|
|
61
64
|
modules[moduleName] = module
|
|
62
|
-
modules[moduleName]?.eventEmitter = self.eventEmitter;
|
|
63
65
|
modules[moduleName]?.bridgeProxy = self.bridgeProxy;
|
|
66
|
+
modules[moduleName]?.registry = self
|
|
64
67
|
print("📦 BrickModuleRegistry: Registered module '\(moduleName)'")
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -82,33 +85,30 @@ import React
|
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@objc public func getRegisteredModules() -> [String] {
|
|
89
|
-
return Array(modules.keys).sorted()
|
|
88
|
+
// Receives event map from BrickModuleImpl (already converted to Swift types)
|
|
89
|
+
public func setEventMap(_ map: [String: [String: ([String: Any]) -> Void]]) {
|
|
90
|
+
self.eventMap = map
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for module in modules {
|
|
101
|
-
module.value.eventEmitter = self.eventEmitter
|
|
92
|
+
|
|
93
|
+
// Emit event by module+event via typed block
|
|
94
|
+
@objc public func emitEvent(module: String, event: String, payload: [String: Any]) {
|
|
95
|
+
// Direct lookup without string manipulation
|
|
96
|
+
if let moduleEvents = eventMap[module],
|
|
97
|
+
let handler = moduleEvents[event] {
|
|
98
|
+
handler(payload)
|
|
99
|
+
} else {
|
|
100
|
+
print("⚠️ BrickModuleRegistry: No handler for module '\(module)' event '\(event)'")
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
/**
|
|
106
|
-
* Returns
|
|
107
|
-
* Used by generated module code to emit events to JavaScript
|
|
105
|
+
* Returns list of registered module names
|
|
108
106
|
*/
|
|
109
|
-
@objc public func
|
|
110
|
-
return
|
|
107
|
+
@objc public func getRegisteredModules() -> [String] {
|
|
108
|
+
return Array(modules.keys).sorted()
|
|
111
109
|
}
|
|
110
|
+
|
|
111
|
+
// MARK: - Event Emitter Management removed (CodegenTypes.EventEmitter is used)
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* Unregisters all modules and clears their registry references
|
|
@@ -125,8 +125,3 @@ import React
|
|
|
125
125
|
unregister()
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
|
|
129
|
-
public class ModuleRegistry {
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brick-module",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Better React Native native module development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,10 +34,6 @@
|
|
|
34
34
|
"BrickModule.podspec",
|
|
35
35
|
"podfile_helper.rb"
|
|
36
36
|
],
|
|
37
|
-
"publishConfig": {
|
|
38
|
-
"registry": "https://registry.npmjs.org",
|
|
39
|
-
"access": "public"
|
|
40
|
-
},
|
|
41
37
|
"keywords": [
|
|
42
38
|
"react-native",
|
|
43
39
|
"native-module",
|
|
@@ -62,7 +58,7 @@
|
|
|
62
58
|
"brick-codegen": "./bin/brick-codegen.js"
|
|
63
59
|
},
|
|
64
60
|
"dependencies": {
|
|
65
|
-
"brick-codegen": "0.1
|
|
61
|
+
"brick-codegen": "0.3.1"
|
|
66
62
|
},
|
|
67
63
|
"peerDependencies": {
|
|
68
64
|
"react": ">=18.2.0",
|
package/src/BrickModule.ts
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { TurboModule } from "react-native";
|
|
7
|
-
import { TurboModuleRegistry
|
|
7
|
+
import { TurboModuleRegistry } from "react-native";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Base interface that all Brick module specs must extend
|
|
11
11
|
*/
|
|
12
12
|
export interface BrickModuleInterface extends TurboModule {
|
|
13
13
|
readonly moduleName: string;
|
|
14
|
-
readonly supportedEvents?: readonly string[];
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -29,7 +28,6 @@ export type BrickModuleSpec = BrickModuleInterface;
|
|
|
29
28
|
// Module-level state (previously static class members)
|
|
30
29
|
const moduleCache = new Map<string, any>();
|
|
31
30
|
let nativeModule: any = null;
|
|
32
|
-
let eventEmitter: NativeEventEmitter | null = null;
|
|
33
31
|
|
|
34
32
|
/**
|
|
35
33
|
* Gets the native TurboModule instance
|
|
@@ -42,30 +40,10 @@ function getNativeModule() {
|
|
|
42
40
|
return nativeModule;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
function getEventEmitter() {
|
|
46
|
-
if (!eventEmitter) {
|
|
47
|
-
const nativeModuleInstance = getNativeModule();
|
|
48
|
-
eventEmitter = new NativeEventEmitter(nativeModuleInstance);
|
|
49
|
-
console.log("eventEmitter", eventEmitter);
|
|
50
|
-
}
|
|
51
|
-
return eventEmitter;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
43
|
/**
|
|
55
44
|
* Enhanced typed module interface with event listeners
|
|
56
45
|
*/
|
|
57
|
-
export type BrickModuleWithEvents<T extends BrickModuleInterface> = T
|
|
58
|
-
/**
|
|
59
|
-
* Adds a type-safe event listener for this module
|
|
60
|
-
* @param eventName - One of the supported events defined in the module spec
|
|
61
|
-
* @param listener - Callback function to handle the event
|
|
62
|
-
* @returns Unsubscription function to remove the listener
|
|
63
|
-
*/
|
|
64
|
-
addEventListener<TEvent = unknown>(
|
|
65
|
-
eventName: T["supportedEvents"] extends readonly (infer U)[] ? U : never,
|
|
66
|
-
listener: (event: TEvent) => void
|
|
67
|
-
): () => void;
|
|
68
|
-
};
|
|
46
|
+
export type BrickModuleWithEvents<T extends BrickModuleInterface> = T;
|
|
69
47
|
|
|
70
48
|
/**
|
|
71
49
|
* Gets a typed module instance by name with explicit type parameter
|
|
@@ -83,6 +61,9 @@ function get<T extends BrickModuleInterface>(
|
|
|
83
61
|
|
|
84
62
|
const nativeModuleInstance = getNativeModule();
|
|
85
63
|
|
|
64
|
+
// Cache constants for this module (computed once, reused)
|
|
65
|
+
let constantsCache: Record<string, any> | null = null;
|
|
66
|
+
|
|
86
67
|
// Create a proxy that intercepts method calls and forwards them to the native module
|
|
87
68
|
const moduleProxy = new Proxy({} as BrickModuleWithEvents<T>, {
|
|
88
69
|
get: (_target, property: string | symbol) => {
|
|
@@ -90,57 +71,81 @@ function get<T extends BrickModuleInterface>(
|
|
|
90
71
|
return undefined;
|
|
91
72
|
}
|
|
92
73
|
|
|
93
|
-
//
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// ignore
|
|
74
|
+
// New-style event subscription: onXxx((event) => void) → EventSubscription
|
|
75
|
+
if (
|
|
76
|
+
property.startsWith("on") &&
|
|
77
|
+
property.length > 2 &&
|
|
78
|
+
property[2] === property[2].toUpperCase()
|
|
79
|
+
) {
|
|
80
|
+
return (listener: (event: unknown) => void) => {
|
|
81
|
+
// Respect RN CodegenTypes.EventEmitter: call `${moduleName}_onXxx` and return as-is
|
|
82
|
+
const nativeEventMethod = `${moduleName}_${property}`;
|
|
83
|
+
const nativeFn = (nativeModuleInstance as any)[nativeEventMethod];
|
|
84
|
+
if (typeof nativeFn === "function") {
|
|
85
|
+
return nativeFn(listener);
|
|
106
86
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
`${moduleName}_${eventName}`,
|
|
110
|
-
listener
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${nativeEventMethod} is not available. Did you run RN codegen?`
|
|
111
89
|
);
|
|
112
|
-
|
|
113
|
-
return () => {
|
|
114
|
-
const native = getNativeModule();
|
|
115
|
-
const moduleRemoveName = `${moduleName}_onEventListenerRemoved`;
|
|
116
|
-
try {
|
|
117
|
-
// Prefer module-specific native removal for precise cleanup
|
|
118
|
-
if (typeof native[moduleRemoveName] === "function") {
|
|
119
|
-
native[moduleRemoveName](eventName);
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
// ignore
|
|
123
|
-
} finally {
|
|
124
|
-
subscription.remove();
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
90
|
};
|
|
128
91
|
}
|
|
129
92
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
93
|
+
// Special handling for getConstants method (with caching)
|
|
94
|
+
if (property === "getConstants") {
|
|
95
|
+
return () => {
|
|
96
|
+
if (constantsCache !== null) {
|
|
97
|
+
return constantsCache;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
|
|
101
|
+
const moduleConstants: Record<string, any> = {};
|
|
102
|
+
const constantPrefix = `${moduleName}_`;
|
|
103
|
+
|
|
104
|
+
for (const key in allConstants) {
|
|
105
|
+
if (key.startsWith(constantPrefix)) {
|
|
106
|
+
const propName = key.substring(constantPrefix.length);
|
|
107
|
+
moduleConstants[propName] = allConstants[key];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
constantsCache = moduleConstants;
|
|
112
|
+
return moduleConstants;
|
|
113
|
+
};
|
|
135
114
|
}
|
|
136
115
|
|
|
137
116
|
// Handle method calls
|
|
138
117
|
return (...args: any[]) => {
|
|
139
118
|
const methodKey = `${moduleName}_${property}`;
|
|
140
119
|
|
|
141
|
-
// Try direct method call
|
|
120
|
+
// Try direct method call (generated by codegen)
|
|
142
121
|
if (typeof nativeModuleInstance[methodKey] === "function") {
|
|
143
|
-
|
|
122
|
+
const result = nativeModuleInstance[methodKey](...args);
|
|
123
|
+
|
|
124
|
+
// Check if result is a wrapped sync method result
|
|
125
|
+
// Sync methods have explicit "~sync": true marker for reliable detection
|
|
126
|
+
// Format: { "~sync": true, success: true, value: T } | { "~sync": true, success: false, error: {...} }
|
|
127
|
+
if (
|
|
128
|
+
result &&
|
|
129
|
+
typeof result === "object" &&
|
|
130
|
+
result["~sync"] === true
|
|
131
|
+
) {
|
|
132
|
+
if (result.success === true) {
|
|
133
|
+
// Success: return the unwrapped value
|
|
134
|
+
return result.value;
|
|
135
|
+
} else {
|
|
136
|
+
// Failure: throw an Error with details from error object
|
|
137
|
+
const errorInfo = result.error || {};
|
|
138
|
+
const error = new Error(errorInfo.message || "Unknown error");
|
|
139
|
+
error.name = errorInfo.code || "BRICK_ERROR";
|
|
140
|
+
if (errorInfo.details) {
|
|
141
|
+
(error as any).details = errorInfo.details;
|
|
142
|
+
}
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Not wrapped (async method or other): return as-is
|
|
148
|
+
return result;
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
throw new Error(`Method ${methodKey} not found`);
|