brick-module 0.1.26 → 0.4.0
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/BrickModule.podspec +1 -1
- package/android/brick_modules.gradle +45 -2
- package/android/src/main/java/com/brickmodule/BrickModuleBase.kt +61 -27
- package/android/src/main/java/com/brickmodule/BrickModuleRegistry.kt +1 -32
- package/dist/BrickError.d.ts +29 -0
- package/dist/BrickError.js +49 -0
- package/dist/BrickModule.d.ts +1 -10
- package/dist/BrickModule.js +36 -30
- package/dist/index.d.ts +2 -10
- package/dist/index.js +2 -16
- package/ios/BrickModule/BrickCoreModule.swift +68 -40
- package/ios/BrickModule/BrickModuleRegistry.swift +22 -27
- package/package.json +2 -6
- package/podfile_helper.rb +26 -3
- package/src/BrickError.ts +78 -0
- package/src/BrickModule.ts +83 -63
- package/src/index.ts +1 -14
package/BrickModule.podspec
CHANGED
|
@@ -68,6 +68,29 @@ ext.getBrickAndroidPath = { projectRoot ->
|
|
|
68
68
|
return new File(projectRoot, 'android/.brick').canonicalPath
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
ext.getBrickAndroidAppPath = { projectRoot ->
|
|
72
|
+
// Read brick.json from explicit project root
|
|
73
|
+
try {
|
|
74
|
+
def brickConfigFile = new File(projectRoot, 'brick.json')
|
|
75
|
+
if (brickConfigFile.exists()) {
|
|
76
|
+
def config = new JsonSlurper().parse(brickConfigFile)
|
|
77
|
+
def androidAppPath = config?.output?.androidAppPath
|
|
78
|
+
if (androidAppPath && !androidAppPath.isEmpty()) {
|
|
79
|
+
if (new File(androidAppPath).isAbsolute()) {
|
|
80
|
+
println("[Brick] Warning: Using absolute path for Android app path is not recommended: ${androidAppPath}")
|
|
81
|
+
return androidAppPath
|
|
82
|
+
}
|
|
83
|
+
return new File(projectRoot, androidAppPath).canonicalPath
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (Exception e) {
|
|
87
|
+
println("[Brick] Warning: Failed to read brick.json from ${projectRoot}: ${e.message}")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Default: android/app in project root
|
|
91
|
+
return new File(projectRoot, 'android/app').canonicalPath
|
|
92
|
+
}
|
|
93
|
+
|
|
71
94
|
ext.getAllDependencies = { packageJson ->
|
|
72
95
|
def deps = []
|
|
73
96
|
deps.addAll(packageJson.dependencies?.keySet() ?: [])
|
|
@@ -396,7 +419,23 @@ ${moduleNames}
|
|
|
396
419
|
project.preBuild.doFirst {
|
|
397
420
|
def workingDir = projectRoot
|
|
398
421
|
|
|
399
|
-
//
|
|
422
|
+
// Skip codegen if already generated to prevent deleting Gradle build outputs
|
|
423
|
+
// The codegen is already run during settings.gradle phase (applyBrickModules -> runBrickCodegen)
|
|
424
|
+
// Re-running here with --clean would delete .brick_android/build/ directory
|
|
425
|
+
// which contains package-aware-r.txt needed by dependent modules
|
|
426
|
+
def brickOutputDir = getBrickAndroidPath(workingDir)
|
|
427
|
+
def generatedMarker = new File(brickOutputDir, "src/main/kotlin/BrickModule.kt")
|
|
428
|
+
def androidAppPath = getBrickAndroidAppPath(workingDir)
|
|
429
|
+
def jniDir = new File(androidAppPath, "build/generated/autolinking/src/main/jni")
|
|
430
|
+
def hasJniOutputs = new File(jniDir, "BrickModuleSpec-generated.cpp").exists() &&
|
|
431
|
+
new File(jniDir, "BrickModuleSpec.h").exists() &&
|
|
432
|
+
new File(jniDir, "CMakeLists.txt").exists()
|
|
433
|
+
if (generatedMarker.exists() && hasJniOutputs) {
|
|
434
|
+
// Already generated, skip to preserve build outputs
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Run codegen only if not yet generated
|
|
400
439
|
try {
|
|
401
440
|
def proc = ['node', '-p', "require.resolve('brick-module/package.json')"].execute(null, workingDir)
|
|
402
441
|
proc.waitFor()
|
|
@@ -408,7 +447,11 @@ ${moduleNames}
|
|
|
408
447
|
def codegenPath = new File(brickModuleDir, "bin/brick-codegen.js")
|
|
409
448
|
|
|
410
449
|
if (codegenPath.exists()) {
|
|
411
|
-
def
|
|
450
|
+
def codegenArgs = ['node', codegenPath.absolutePath, '--platform', 'android', '--projectRoot', workingDir.absolutePath]
|
|
451
|
+
if (generatedMarker.exists()) {
|
|
452
|
+
codegenArgs.add('--no-clean')
|
|
453
|
+
}
|
|
454
|
+
def codegenProc = codegenArgs.execute(null, workingDir)
|
|
412
455
|
codegenProc.waitFor()
|
|
413
456
|
}
|
|
414
457
|
}
|
|
@@ -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,38 +40,63 @@ 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()
|
|
42
|
-
|
|
43
|
-
// Format event name with module prefix
|
|
44
|
-
val fullEventName = "${moduleName}_$eventName"
|
|
46
|
+
var eventEmitterCallback: ((String, Map<String, Any>) -> Unit)? = null
|
|
45
47
|
|
|
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
|
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
// MARK: - BrickError Interface
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Error interface for Brick modules.
|
|
65
|
+
* Implement this interface to propagate all error properties to JavaScript.
|
|
66
|
+
* Follows the same pattern as iOS BrickError protocol.
|
|
67
|
+
*
|
|
68
|
+
* Note: message is inherited from Throwable, so it's not defined in this interface.
|
|
69
|
+
* Classes implementing BrickError must extend Exception/Throwable.
|
|
70
|
+
*/
|
|
71
|
+
interface BrickError {
|
|
72
|
+
/** Error code (accessed as error.name in JS) */
|
|
73
|
+
val code: String
|
|
74
|
+
|
|
75
|
+
/** Additional properties (accessed as error.userInfo.xxx in JS) */
|
|
76
|
+
val userInfo: Map<String, Any>?
|
|
77
|
+
get() = null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Default implementation of BrickError.
|
|
82
|
+
* Extend this class to create custom errors.
|
|
83
|
+
*/
|
|
84
|
+
open class BrickException(
|
|
85
|
+
override val code: String,
|
|
86
|
+
message: String,
|
|
87
|
+
override val userInfo: Map<String, Any>? = null,
|
|
88
|
+
cause: Throwable? = null
|
|
89
|
+
) : Exception(message, cause), BrickError
|
|
90
|
+
|
|
91
|
+
/** Error types for Brick modules - implements BrickError interface */
|
|
92
|
+
open class BrickModuleError(
|
|
93
|
+
message: String,
|
|
94
|
+
override val code: String
|
|
95
|
+
) : Exception(message), BrickError {
|
|
96
|
+
|
|
97
|
+
override val userInfo: Map<String, Any>?
|
|
98
|
+
get() = null
|
|
99
|
+
|
|
66
100
|
class TypeMismatch(message: String) : BrickModuleError("Type mismatch: $message", "TYPE_ERROR")
|
|
67
101
|
class ExecutionError(message: String) :
|
|
68
102
|
BrickModuleError("Execution error: $message", "EXECUTION_ERROR")
|
|
@@ -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> {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/BrickError.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* BrickError - Type-safe class for custom errors from native modules.
|
|
4
|
+
* Matches the structure of iOS/Android BrickError.
|
|
5
|
+
*/
|
|
6
|
+
declare class BrickError extends Error {
|
|
7
|
+
/** Error code (same as error.name) */
|
|
8
|
+
readonly code: string;
|
|
9
|
+
/** Additional properties (e.g., amount, currency) */
|
|
10
|
+
readonly userInfo: Record<string, unknown>;
|
|
11
|
+
/** Name of the module where the error occurred */
|
|
12
|
+
readonly moduleName?: string;
|
|
13
|
+
constructor(message: string, code: string, userInfo?: Record<string, unknown>, moduleName?: string);
|
|
14
|
+
/**
|
|
15
|
+
* Type guard to check if an error object is a BrickError.
|
|
16
|
+
* @param error - The error object to check
|
|
17
|
+
* @returns true if the error is a BrickError
|
|
18
|
+
*/
|
|
19
|
+
static isBrickError(error: unknown): error is BrickError;
|
|
20
|
+
/**
|
|
21
|
+
* Converts a general error object to a BrickError.
|
|
22
|
+
* @param error - The error object to convert
|
|
23
|
+
* @param moduleName - Name of the module where the error occurred
|
|
24
|
+
* @returns A BrickError instance
|
|
25
|
+
*/
|
|
26
|
+
static from(error: unknown, moduleName?: string): BrickError;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { BrickError };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/BrickError.ts
|
|
2
|
+
/**
|
|
3
|
+
* BrickError - Type-safe class for custom errors from native modules.
|
|
4
|
+
* Matches the structure of iOS/Android BrickError.
|
|
5
|
+
*/
|
|
6
|
+
var BrickError = class BrickError extends Error {
|
|
7
|
+
/** Error code (same as error.name) */
|
|
8
|
+
code;
|
|
9
|
+
/** Additional properties (e.g., amount, currency) */
|
|
10
|
+
userInfo;
|
|
11
|
+
/** Name of the module where the error occurred */
|
|
12
|
+
moduleName;
|
|
13
|
+
constructor(message, code, userInfo = {}, moduleName) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = code;
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.userInfo = userInfo;
|
|
18
|
+
this.moduleName = moduleName;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Type guard to check if an error object is a BrickError.
|
|
22
|
+
* @param error - The error object to check
|
|
23
|
+
* @returns true if the error is a BrickError
|
|
24
|
+
*/
|
|
25
|
+
static isBrickError(error) {
|
|
26
|
+
return error instanceof Error && typeof error.code === "string" && typeof error.userInfo === "object" && error.userInfo !== null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Converts a general error object to a BrickError.
|
|
30
|
+
* @param error - The error object to convert
|
|
31
|
+
* @param moduleName - Name of the module where the error occurred
|
|
32
|
+
* @returns A BrickError instance
|
|
33
|
+
*/
|
|
34
|
+
static from(error, moduleName) {
|
|
35
|
+
if (error instanceof BrickError) {
|
|
36
|
+
if (moduleName && !error.moduleName) return new BrickError(error.message, error.code, error.userInfo, moduleName);
|
|
37
|
+
return error;
|
|
38
|
+
}
|
|
39
|
+
if (BrickError.isBrickError(error)) return new BrickError(error.message, error.code, error.userInfo, moduleName || error.moduleName);
|
|
40
|
+
if (error instanceof Error) {
|
|
41
|
+
const anyError = error;
|
|
42
|
+
return new BrickError(error.message, anyError.code || anyError.name || "UNKNOWN_ERROR", anyError.userInfo || {}, moduleName || anyError.moduleName);
|
|
43
|
+
}
|
|
44
|
+
return new BrickError(String(error), "UNKNOWN_ERROR", {}, moduleName);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { BrickError };
|
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,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BrickError } from "./BrickError.js";
|
|
2
|
+
import { TurboModuleRegistry } from "react-native";
|
|
2
3
|
|
|
3
4
|
//#region src/BrickModule.ts
|
|
4
5
|
const moduleCache = /* @__PURE__ */ new Map();
|
|
5
6
|
let nativeModule = null;
|
|
6
|
-
let eventEmitter = null;
|
|
7
7
|
/**
|
|
8
8
|
* Gets the native TurboModule instance
|
|
9
9
|
* @private
|
|
@@ -12,13 +12,6 @@ function getNativeModule() {
|
|
|
12
12
|
if (!nativeModule) nativeModule = TurboModuleRegistry.getEnforcing("BrickModule");
|
|
13
13
|
return nativeModule;
|
|
14
14
|
}
|
|
15
|
-
function getEventEmitter() {
|
|
16
|
-
if (!eventEmitter) {
|
|
17
|
-
eventEmitter = new NativeEventEmitter(getNativeModule());
|
|
18
|
-
console.log("eventEmitter", eventEmitter);
|
|
19
|
-
}
|
|
20
|
-
return eventEmitter;
|
|
21
|
-
}
|
|
22
15
|
/**
|
|
23
16
|
* Gets a typed module instance by name with explicit type parameter
|
|
24
17
|
* @param moduleName - The exact name of the module as defined in its spec
|
|
@@ -28,33 +21,46 @@ function get(moduleName) {
|
|
|
28
21
|
const cacheKey = moduleName;
|
|
29
22
|
if (moduleCache.has(cacheKey)) return moduleCache.get(cacheKey);
|
|
30
23
|
const nativeModuleInstance = getNativeModule();
|
|
24
|
+
let constantsCache = null;
|
|
31
25
|
const moduleProxy = new Proxy({}, {
|
|
32
26
|
get: (_target, property) => {
|
|
33
27
|
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
|
-
|
|
28
|
+
if (property.startsWith("on") && property.length > 2 && property[2] === property[2].toUpperCase()) return (listener) => {
|
|
29
|
+
const nativeEventMethod = `${moduleName}_${property}`;
|
|
30
|
+
const nativeFn = nativeModuleInstance[nativeEventMethod];
|
|
31
|
+
if (typeof nativeFn === "function") return nativeFn(listener);
|
|
32
|
+
throw new Error(`${nativeEventMethod} is not available. Did you run RN codegen?`);
|
|
33
|
+
};
|
|
34
|
+
if (property === "getConstants") return () => {
|
|
35
|
+
if (constantsCache !== null) return constantsCache;
|
|
36
|
+
const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
|
|
37
|
+
const moduleConstants = {};
|
|
38
|
+
const constantPrefix = `${moduleName}_`;
|
|
39
|
+
for (const key in allConstants) if (key.startsWith(constantPrefix)) {
|
|
40
|
+
const propName = key.substring(constantPrefix.length);
|
|
41
|
+
moduleConstants[propName] = allConstants[key];
|
|
42
|
+
}
|
|
43
|
+
constantsCache = moduleConstants;
|
|
44
|
+
return moduleConstants;
|
|
51
45
|
};
|
|
52
|
-
const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
|
|
53
|
-
const constantKey = `${moduleName}_${property}`;
|
|
54
|
-
if (constantKey in allConstants) return allConstants[constantKey];
|
|
55
46
|
return (...args) => {
|
|
56
47
|
const methodKey = `${moduleName}_${property}`;
|
|
57
|
-
if (typeof nativeModuleInstance[methodKey] === "function")
|
|
48
|
+
if (typeof nativeModuleInstance[methodKey] === "function") {
|
|
49
|
+
const result = nativeModuleInstance[methodKey](...args);
|
|
50
|
+
if (result && typeof result === "object" && result["~sync"] === true) if (result.success === true) return result.value;
|
|
51
|
+
else {
|
|
52
|
+
const errorInfo = result.error || {};
|
|
53
|
+
const message = errorInfo.message || errorInfo.errorMessage || "Unknown error";
|
|
54
|
+
const code = errorInfo.code || errorInfo.errorCode || "BRICK_ERROR";
|
|
55
|
+
const userInfo = { code };
|
|
56
|
+
for (const key of Object.keys(errorInfo)) if (key !== "code" && key !== "message" && key !== "errorCode" && key !== "errorMessage") userInfo[key] = errorInfo[key];
|
|
57
|
+
throw new BrickError(message, code, userInfo, moduleName);
|
|
58
|
+
}
|
|
59
|
+
if (result && typeof result.then === "function") return result.catch((error) => {
|
|
60
|
+
throw BrickError.from(error, moduleName);
|
|
61
|
+
});
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
58
64
|
throw new Error(`Method ${methodKey} not found`);
|
|
59
65
|
};
|
|
60
66
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { BrickModule, BrickModuleInterface, BrickModuleSpec } from "./BrickModule.js";
|
|
2
|
+
import { BrickError } from "./BrickError.js";
|
|
2
3
|
import { Any, AnyObject } from "./types.js";
|
|
3
4
|
|
|
4
5
|
//#region src/index.d.ts
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
* Error types for Brick modules
|
|
8
|
-
*/
|
|
9
|
-
declare class BrickModuleError extends Error {
|
|
10
|
-
code: string;
|
|
11
|
-
moduleName?: string | undefined;
|
|
12
|
-
methodName?: string | undefined;
|
|
13
|
-
constructor(message: string, code?: string, moduleName?: string | undefined, methodName?: string | undefined);
|
|
14
|
-
}
|
|
15
7
|
/**
|
|
16
8
|
* Configuration interface for brick-codegen
|
|
17
9
|
*/
|
|
@@ -29,4 +21,4 @@ interface BrickCodegenConfig {
|
|
|
29
21
|
dev?: boolean;
|
|
30
22
|
}
|
|
31
23
|
//#endregion
|
|
32
|
-
export { Any, AnyObject, BrickCodegenConfig,
|
|
24
|
+
export { Any, AnyObject, BrickCodegenConfig, BrickError, BrickModule, type BrickModuleInterface, type BrickModuleSpec };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
+
import { BrickError } from "./BrickError.js";
|
|
1
2
|
import BrickModule_default from "./BrickModule.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Error types for Brick modules
|
|
6
|
-
*/
|
|
7
|
-
var BrickModuleError = class extends Error {
|
|
8
|
-
constructor(message, code = "BRICK_ERROR", moduleName, methodName) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.code = code;
|
|
11
|
-
this.moduleName = moduleName;
|
|
12
|
-
this.methodName = methodName;
|
|
13
|
-
this.name = "BrickModuleError";
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
//#endregion
|
|
18
|
-
export { BrickModule_default as BrickModule, BrickModuleError };
|
|
4
|
+
export { BrickError, BrickModule_default as BrickModule };
|
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import React
|
|
3
3
|
|
|
4
|
+
// MARK: - BrickError Protocol
|
|
5
|
+
|
|
6
|
+
/// Error protocol for Brick modules.
|
|
7
|
+
/// Conforming to this protocol propagates all error properties to JavaScript.
|
|
8
|
+
public protocol BrickError: Error {
|
|
9
|
+
/// Error message (accessed as error.message in JS)
|
|
10
|
+
var message: String { get }
|
|
11
|
+
|
|
12
|
+
/// Error code (accessed as error.code in JS, optional)
|
|
13
|
+
var code: String? { get }
|
|
14
|
+
|
|
15
|
+
/// Additional properties (accessed as error.userInfo.xxx in JS)
|
|
16
|
+
var userInfo: [String: Any]? { get }
|
|
17
|
+
|
|
18
|
+
/// Convert to NSError (for React Native reject)
|
|
19
|
+
func asNSError() -> NSError
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public extension BrickError {
|
|
23
|
+
/// Default code implementation
|
|
24
|
+
var code: String? { nil }
|
|
25
|
+
|
|
26
|
+
/// Default userInfo implementation
|
|
27
|
+
var userInfo: [String: Any]? { nil }
|
|
28
|
+
|
|
29
|
+
/// Default NSError conversion implementation
|
|
30
|
+
func asNSError() -> NSError {
|
|
31
|
+
var info: [String: Any] = userInfo ?? [:]
|
|
32
|
+
info["code"] = code ?? "EXECUTION_ERROR"
|
|
33
|
+
info["message"] = message
|
|
34
|
+
info[NSLocalizedDescriptionKey] = message
|
|
35
|
+
return NSError(domain: "BrickModule", code: 0, userInfo: info)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Error code to use when calling reject
|
|
39
|
+
var rejectCode: String {
|
|
40
|
+
code ?? "EXECUTION_ERROR"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: - BrickModuleBase
|
|
45
|
+
|
|
4
46
|
/**
|
|
5
47
|
* Base class for all Brick modules
|
|
6
48
|
* Provides common functionality including event emission
|
|
@@ -9,7 +51,9 @@ open class BrickModuleBase: NSObject {
|
|
|
9
51
|
/// The name of the module (required for registration)
|
|
10
52
|
public let moduleName: String
|
|
11
53
|
|
|
12
|
-
|
|
54
|
+
/// The registry that manages this module (assigned during registration)
|
|
55
|
+
public weak var registry: BrickModuleRegistry?
|
|
56
|
+
|
|
13
57
|
public weak var bridgeProxy: RCTBridgeProxy?
|
|
14
58
|
|
|
15
59
|
/// Initialize with module name
|
|
@@ -18,61 +62,45 @@ open class BrickModuleBase: NSObject {
|
|
|
18
62
|
super.init()
|
|
19
63
|
}
|
|
20
64
|
|
|
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)")
|
|
65
|
+
// Emit via Registry event map (ObjC++ injects typed handlers per event)
|
|
66
|
+
public func emit(_ eventName: String, payload: [String: Any]) {
|
|
67
|
+
guard let registry = registry else {
|
|
68
|
+
print("⚠️ BrickModuleBase: registry not attached; cannot emit \(moduleName).\(eventName)")
|
|
69
|
+
return
|
|
35
70
|
}
|
|
71
|
+
registry.emitEvent(module: moduleName, event: eventName, payload: payload)
|
|
36
72
|
}
|
|
37
73
|
}
|
|
38
74
|
|
|
75
|
+
// MARK: - BrickModuleError
|
|
76
|
+
|
|
39
77
|
/**
|
|
40
78
|
* Error types for Brick modules
|
|
41
79
|
*/
|
|
42
|
-
public enum BrickModuleError:
|
|
80
|
+
public enum BrickModuleError: BrickError {
|
|
43
81
|
case typeMismatch(String)
|
|
44
82
|
case executionError(String)
|
|
45
83
|
case invalidDefinition(String)
|
|
46
84
|
case methodNotFound(String)
|
|
47
85
|
case moduleNotFound(String)
|
|
48
|
-
|
|
49
|
-
public var
|
|
86
|
+
|
|
87
|
+
public var code: String? {
|
|
50
88
|
switch self {
|
|
51
|
-
case .typeMismatch
|
|
52
|
-
|
|
53
|
-
case .
|
|
54
|
-
|
|
55
|
-
case .
|
|
56
|
-
return "Invalid definition: \(message)"
|
|
57
|
-
case .methodNotFound(let message):
|
|
58
|
-
return "Method not found: \(message)"
|
|
59
|
-
case .moduleNotFound(let message):
|
|
60
|
-
return "Module not found: \(message)"
|
|
89
|
+
case .typeMismatch: return "TYPE_ERROR"
|
|
90
|
+
case .executionError: return "EXECUTION_ERROR"
|
|
91
|
+
case .invalidDefinition: return "DEFINITION_ERROR"
|
|
92
|
+
case .methodNotFound: return "METHOD_NOT_FOUND"
|
|
93
|
+
case .moduleNotFound: return "MODULE_NOT_FOUND"
|
|
61
94
|
}
|
|
62
95
|
}
|
|
63
|
-
|
|
64
|
-
public var
|
|
96
|
+
|
|
97
|
+
public var message: String {
|
|
65
98
|
switch self {
|
|
66
|
-
case .typeMismatch:
|
|
67
|
-
|
|
68
|
-
case .
|
|
69
|
-
|
|
70
|
-
case .
|
|
71
|
-
return "DEFINITION_ERROR"
|
|
72
|
-
case .methodNotFound:
|
|
73
|
-
return "METHOD_NOT_FOUND"
|
|
74
|
-
case .moduleNotFound:
|
|
75
|
-
return "MODULE_NOT_FOUND"
|
|
99
|
+
case .typeMismatch(let msg): return "Type mismatch: \(msg)"
|
|
100
|
+
case .executionError(let msg): return "Execution error: \(msg)"
|
|
101
|
+
case .invalidDefinition(let msg): return "Invalid definition: \(msg)"
|
|
102
|
+
case .methodNotFound(let msg): return "Method not found: \(msg)"
|
|
103
|
+
case .moduleNotFound(let msg): return "Module not found: \(msg)"
|
|
76
104
|
}
|
|
77
105
|
}
|
|
78
106
|
}
|
|
@@ -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.
|
|
3
|
+
"version": "0.4.0",
|
|
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.
|
|
61
|
+
"brick-codegen": "0.4.0"
|
|
66
62
|
},
|
|
67
63
|
"peerDependencies": {
|
|
68
64
|
"react": ">=18.2.0",
|
package/podfile_helper.rb
CHANGED
|
@@ -27,7 +27,14 @@ def use_brick_modules!(app_path: nil)
|
|
|
27
27
|
else
|
|
28
28
|
File.expand_path(File.join(brick_root, ios_brick_path))
|
|
29
29
|
end
|
|
30
|
-
brick_codegen_podspec_path = File.join(
|
|
30
|
+
brick_codegen_podspec_path = File.join(
|
|
31
|
+
brick_codegen_pod_path,
|
|
32
|
+
'BrickCodegen.podspec'
|
|
33
|
+
)
|
|
34
|
+
brick_codegen_pod_relative_path = relative_pod_path(
|
|
35
|
+
brick_codegen_pod_path,
|
|
36
|
+
podfile_dir
|
|
37
|
+
)
|
|
31
38
|
|
|
32
39
|
# Run brick-codegen with real-time output and colors (iOS only)
|
|
33
40
|
exit_status = system("cd #{brick_root} && FORCE_COLOR=1 npx brick-codegen --platform ios --projectRoot \"#{brick_root}\"")
|
|
@@ -42,8 +49,8 @@ def use_brick_modules!(app_path: nil)
|
|
|
42
49
|
end
|
|
43
50
|
|
|
44
51
|
# Link generated BrickCodegen pod
|
|
45
|
-
pod 'BrickCodegen', :path =>
|
|
46
|
-
Pod::UI.puts "[Brick] Linked BrickCodegen from #{
|
|
52
|
+
pod 'BrickCodegen', :path => brick_codegen_pod_relative_path
|
|
53
|
+
Pod::UI.puts "[Brick] Linked BrickCodegen from #{brick_codegen_pod_relative_path}"
|
|
47
54
|
rescue => e
|
|
48
55
|
# Re-raise so CocoaPods fails the install
|
|
49
56
|
raise e
|
|
@@ -74,3 +81,19 @@ def get_brick_ios_path(project_root)
|
|
|
74
81
|
|
|
75
82
|
return File.expand_path(File.join(project_root, 'ios/.brick'))
|
|
76
83
|
end
|
|
84
|
+
|
|
85
|
+
def podfile_dir
|
|
86
|
+
podfile_path = Pod::Config.instance.podfile_path
|
|
87
|
+
return Pathname.new(Dir.pwd).expand_path if podfile_path.nil?
|
|
88
|
+
|
|
89
|
+
podfile_path.dirname.expand_path
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def relative_pod_path(target_path, base_dir)
|
|
93
|
+
Pathname.new(target_path)
|
|
94
|
+
.expand_path
|
|
95
|
+
.relative_path_from(base_dir)
|
|
96
|
+
.to_s
|
|
97
|
+
rescue ArgumentError
|
|
98
|
+
target_path
|
|
99
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrickError - Type-safe class for custom errors from native modules.
|
|
3
|
+
* Matches the structure of iOS/Android BrickError.
|
|
4
|
+
*/
|
|
5
|
+
export class BrickError extends Error {
|
|
6
|
+
/** Error code (same as error.name) */
|
|
7
|
+
readonly code: string;
|
|
8
|
+
|
|
9
|
+
/** Additional properties (e.g., amount, currency) */
|
|
10
|
+
readonly userInfo: Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
/** Name of the module where the error occurred */
|
|
13
|
+
readonly moduleName?: string;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
code: string,
|
|
18
|
+
userInfo: Record<string, unknown> = {},
|
|
19
|
+
moduleName?: string
|
|
20
|
+
) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = code;
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.userInfo = userInfo;
|
|
25
|
+
this.moduleName = moduleName;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Type guard to check if an error object is a BrickError.
|
|
30
|
+
* @param error - The error object to check
|
|
31
|
+
* @returns true if the error is a BrickError
|
|
32
|
+
*/
|
|
33
|
+
static isBrickError(error: unknown): error is BrickError {
|
|
34
|
+
return (
|
|
35
|
+
error instanceof Error &&
|
|
36
|
+
typeof (error as any).code === "string" &&
|
|
37
|
+
typeof (error as any).userInfo === "object" &&
|
|
38
|
+
(error as any).userInfo !== null
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Converts a general error object to a BrickError.
|
|
44
|
+
* @param error - The error object to convert
|
|
45
|
+
* @param moduleName - Name of the module where the error occurred
|
|
46
|
+
* @returns A BrickError instance
|
|
47
|
+
*/
|
|
48
|
+
static from(error: unknown, moduleName?: string): BrickError {
|
|
49
|
+
if (error instanceof BrickError) {
|
|
50
|
+
// Update moduleName if provided
|
|
51
|
+
if (moduleName && !error.moduleName) {
|
|
52
|
+
return new BrickError(error.message, error.code, error.userInfo, moduleName);
|
|
53
|
+
}
|
|
54
|
+
return error;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (BrickError.isBrickError(error)) {
|
|
58
|
+
return new BrickError(
|
|
59
|
+
error.message,
|
|
60
|
+
error.code,
|
|
61
|
+
error.userInfo,
|
|
62
|
+
moduleName || (error as any).moduleName
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
const anyError = error as any;
|
|
68
|
+
return new BrickError(
|
|
69
|
+
error.message,
|
|
70
|
+
anyError.code || anyError.name || "UNKNOWN_ERROR",
|
|
71
|
+
anyError.userInfo || {},
|
|
72
|
+
moduleName || anyError.moduleName
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return new BrickError(String(error), "UNKNOWN_ERROR", {}, moduleName);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/BrickModule.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { TurboModule } from "react-native";
|
|
7
|
-
import { TurboModuleRegistry
|
|
7
|
+
import { TurboModuleRegistry } from "react-native";
|
|
8
|
+
import { BrickError } from "./BrickError";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Base interface that all Brick module specs must extend
|
|
11
12
|
*/
|
|
12
13
|
export interface BrickModuleInterface extends TurboModule {
|
|
13
14
|
readonly moduleName: string;
|
|
14
|
-
readonly supportedEvents?: readonly string[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -29,7 +29,6 @@ export type BrickModuleSpec = BrickModuleInterface;
|
|
|
29
29
|
// Module-level state (previously static class members)
|
|
30
30
|
const moduleCache = new Map<string, any>();
|
|
31
31
|
let nativeModule: any = null;
|
|
32
|
-
let eventEmitter: NativeEventEmitter | null = null;
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
34
|
* Gets the native TurboModule instance
|
|
@@ -42,30 +41,10 @@ function getNativeModule() {
|
|
|
42
41
|
return nativeModule;
|
|
43
42
|
}
|
|
44
43
|
|
|
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
44
|
/**
|
|
55
45
|
* Enhanced typed module interface with event listeners
|
|
56
46
|
*/
|
|
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
|
-
};
|
|
47
|
+
export type BrickModuleWithEvents<T extends BrickModuleInterface> = T;
|
|
69
48
|
|
|
70
49
|
/**
|
|
71
50
|
* Gets a typed module instance by name with explicit type parameter
|
|
@@ -83,6 +62,9 @@ function get<T extends BrickModuleInterface>(
|
|
|
83
62
|
|
|
84
63
|
const nativeModuleInstance = getNativeModule();
|
|
85
64
|
|
|
65
|
+
// Cache constants for this module (computed once, reused)
|
|
66
|
+
let constantsCache: Record<string, any> | null = null;
|
|
67
|
+
|
|
86
68
|
// Create a proxy that intercepts method calls and forwards them to the native module
|
|
87
69
|
const moduleProxy = new Proxy({} as BrickModuleWithEvents<T>, {
|
|
88
70
|
get: (_target, property: string | symbol) => {
|
|
@@ -90,57 +72,95 @@ function get<T extends BrickModuleInterface>(
|
|
|
90
72
|
return undefined;
|
|
91
73
|
}
|
|
92
74
|
|
|
93
|
-
//
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// ignore
|
|
75
|
+
// New-style event subscription: onXxx((event) => void) → EventSubscription
|
|
76
|
+
if (
|
|
77
|
+
property.startsWith("on") &&
|
|
78
|
+
property.length > 2 &&
|
|
79
|
+
property[2] === property[2].toUpperCase()
|
|
80
|
+
) {
|
|
81
|
+
return (listener: (event: unknown) => void) => {
|
|
82
|
+
// Respect RN CodegenTypes.EventEmitter: call `${moduleName}_onXxx` and return as-is
|
|
83
|
+
const nativeEventMethod = `${moduleName}_${property}`;
|
|
84
|
+
const nativeFn = (nativeModuleInstance as any)[nativeEventMethod];
|
|
85
|
+
if (typeof nativeFn === "function") {
|
|
86
|
+
return nativeFn(listener);
|
|
106
87
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
`${moduleName}_${eventName}`,
|
|
110
|
-
listener
|
|
88
|
+
throw new Error(
|
|
89
|
+
`${nativeEventMethod} is not available. Did you run RN codegen?`
|
|
111
90
|
);
|
|
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
91
|
};
|
|
128
92
|
}
|
|
129
93
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
94
|
+
// Special handling for getConstants method (with caching)
|
|
95
|
+
if (property === "getConstants") {
|
|
96
|
+
return () => {
|
|
97
|
+
if (constantsCache !== null) {
|
|
98
|
+
return constantsCache;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
|
|
102
|
+
const moduleConstants: Record<string, any> = {};
|
|
103
|
+
const constantPrefix = `${moduleName}_`;
|
|
104
|
+
|
|
105
|
+
for (const key in allConstants) {
|
|
106
|
+
if (key.startsWith(constantPrefix)) {
|
|
107
|
+
const propName = key.substring(constantPrefix.length);
|
|
108
|
+
moduleConstants[propName] = allConstants[key];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
constantsCache = moduleConstants;
|
|
113
|
+
return moduleConstants;
|
|
114
|
+
};
|
|
135
115
|
}
|
|
136
116
|
|
|
137
117
|
// Handle method calls
|
|
138
118
|
return (...args: any[]) => {
|
|
139
119
|
const methodKey = `${moduleName}_${property}`;
|
|
140
120
|
|
|
141
|
-
// Try direct method call
|
|
121
|
+
// Try direct method call (generated by codegen)
|
|
142
122
|
if (typeof nativeModuleInstance[methodKey] === "function") {
|
|
143
|
-
|
|
123
|
+
const result = nativeModuleInstance[methodKey](...args);
|
|
124
|
+
|
|
125
|
+
// Check if result is a wrapped sync method result
|
|
126
|
+
// Sync methods have explicit "~sync": true marker for reliable detection
|
|
127
|
+
// Format: { "~sync": true, success: true, value: T } | { "~sync": true, success: false, error: {...} }
|
|
128
|
+
if (
|
|
129
|
+
result &&
|
|
130
|
+
typeof result === "object" &&
|
|
131
|
+
result["~sync"] === true
|
|
132
|
+
) {
|
|
133
|
+
if (result.success === true) {
|
|
134
|
+
// Success: return the unwrapped value
|
|
135
|
+
return result.value;
|
|
136
|
+
} else {
|
|
137
|
+
// Failure: throw a BrickError with all properties from error object
|
|
138
|
+
const errorInfo = result.error || {};
|
|
139
|
+
const message = errorInfo.message || errorInfo.errorMessage || "Unknown error";
|
|
140
|
+
const code = errorInfo.code || errorInfo.errorCode || "BRICK_ERROR";
|
|
141
|
+
|
|
142
|
+
// Build userInfo from additional properties
|
|
143
|
+
const userInfo: Record<string, unknown> = { code };
|
|
144
|
+
for (const key of Object.keys(errorInfo)) {
|
|
145
|
+
if (key !== "code" && key !== "message" && key !== "errorCode" && key !== "errorMessage") {
|
|
146
|
+
userInfo[key] = errorInfo[key];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new BrickError(message, code, userInfo, moduleName);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Async method: wrap Promise and convert to BrickError
|
|
155
|
+
if (result && typeof result.then === "function") {
|
|
156
|
+
return result.catch((error: unknown) => {
|
|
157
|
+
// Convert to BrickError with moduleName for type-safe error handling
|
|
158
|
+
throw BrickError.from(error, moduleName);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Not wrapped: return as-is
|
|
163
|
+
return result;
|
|
144
164
|
}
|
|
145
165
|
|
|
146
166
|
throw new Error(`Method ${methodKey} not found`);
|
package/src/index.ts
CHANGED
|
@@ -4,20 +4,7 @@
|
|
|
4
4
|
export type { BrickModuleInterface, BrickModuleSpec } from "./BrickModule";
|
|
5
5
|
// Main API exports
|
|
6
6
|
export { default as BrickModule } from "./BrickModule";
|
|
7
|
-
|
|
8
|
-
* Error types for Brick modules
|
|
9
|
-
*/
|
|
10
|
-
export class BrickModuleError extends Error {
|
|
11
|
-
constructor(
|
|
12
|
-
message: string,
|
|
13
|
-
public code: string = "BRICK_ERROR",
|
|
14
|
-
public moduleName?: string,
|
|
15
|
-
public methodName?: string
|
|
16
|
-
) {
|
|
17
|
-
super(message);
|
|
18
|
-
this.name = "BrickModuleError";
|
|
19
|
-
}
|
|
20
|
-
}
|
|
7
|
+
export { BrickError } from "./BrickError";
|
|
21
8
|
|
|
22
9
|
/**
|
|
23
10
|
* Configuration interface for brick-codegen
|