package com.useideem.zsm.reactnative import com.facebook.react.bridge.* import com.facebook.react.modules.core.DeviceEventManagerModule import com.facebook.react.module.annotations.ReactModule import com.useideem.zsm.ZSM import com.useideem.zsm.ZSMConfig import com.useideem.zsm.ZSMError import org.json.JSONObject import com.useideem.zsm.reactnative.generated @ReactModule(name = ZSMNativeModule.NAME) class ZSMNativeModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { companion object { const val NAME = "ZSM" } @Volatile private var zsmInstance: ZSM? = null override fun getName(): String = NAME @ReactMethod(isBlockingSynchronousMethod = true) fun getVersionString(): String { return generated.ZSM_VERSION } @ReactMethod fun createInstance(config: ReadableMap, promise: Promise) { try { val jsonConfig = convertReadableMapToJson(config) val zsmConfig = ZSMConfig(jsonConfig) ZSM.createInstance(reactApplicationContext, zsmConfig) { zsm, error -> if (error != null) { promise.reject("zsm_instance_error", "Failed to create instance: ${error.message}") } else { zsmInstance = zsm val result = Arguments.createMap() result.putBoolean("success", true) promise.resolve(result) } } } catch (e: Exception) { promise.reject("zsm_config_error", e.message) } } @ReactMethod fun configure(config: ReadableMap, promise: Promise) { try { val jsonConfig = convertReadableMapToJson(config) val zsmConfig = ZSMConfig(jsonConfig) //todo: do an actual configuration rather than reinstantiating the ZSM instance ZSM.createInstance(reactApplicationContext, zsmConfig) { zsm, error -> if (error != null) { promise.reject("zsm_config_error", "Failed to configure: ${error.message}") } else { zsmInstance = zsm val result = Arguments.createMap() result.putBoolean("success", true) promise.resolve(result) } } } catch (e: Exception) { promise.reject("zsm_config_error", e.message) } } @ReactMethod fun webauthn_create(options: ReadableMap, promise: Promise) { val instance = zsmInstance ?: return promise.reject("zsm_not_initialized", "ZSM instance is not initialized") try { val jsonOptions = convertReadableMapToJson(options) instance.webauthnCreate(jsonOptions) { result, metadata, error -> handleCallback(promise, result, metadata, error) } } catch (e: Exception) { promise.reject("webauthn_create_error", e.message) } } @ReactMethod fun webauthn_get(options: ReadableMap, promise: Promise) { val instance = zsmInstance ?: return promise.reject("zsm_not_initialized", "ZSM instance is not initialized") try { val jsonOptions = convertReadableMapToJson(options) instance.webauthnGet(jsonOptions) { result, metadata, error -> handleCallback(promise, result, metadata, error) } } catch (e: Exception) { promise.reject("webauthn_get_error", e.message) } } @ReactMethod fun webauthn_retrieve(userIdentifier: String, promise: Promise) { val instance = zsmInstance ?: return promise.reject("zsm_not_initialized", "ZSM instance is not initialized") try { instance.webauthnRetrieve() { result: JSONObject? -> handleCallback(promise, result, null, null) } } catch (e: Exception) { promise.reject("webauthn_retrieve_error", e.message) } } private fun convertReadableMapToJson(map: ReadableMap): JSONObject { @Suppress("UNCHECKED_CAST") return JSONObject(map.toHashMap() as Map) } @ReactMethod fun logToReactNative(level: String, message: String) { val reactContext = reactApplicationContext reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(ZSMNativeModule.NAME, mapOf("level" to level, "message" to message)) } // Helper to convert Map to WritableMap // todo: remove the need for these manual conversions private fun convertMapToWritableMap(map: Map<*, *>?): WritableMap { val writableMap = Arguments.createMap() map?.forEach { (key, value) -> if (key is String) { // Ensure the key is of type String when (value) { is Map<*, *> -> writableMap.putMap(key, convertMapToWritableMap(value)) is List<*> -> writableMap.putArray(key, convertListToWritableArray(value)) is Boolean -> writableMap.putBoolean(key, value) is Int -> writableMap.putInt(key, value) is Double -> writableMap.putDouble(key, value) is String -> writableMap.putString(key, value) else -> writableMap.putString(key, value?.toString() ?: "null") } } } return writableMap } // Helper to convert List to WritableArray // todo: remove the need for these manual conversions private fun convertListToWritableArray(list: List<*>?): WritableArray { val writableArray = Arguments.createArray() list?.forEach { value -> when (value) { is Map<*, *> -> writableArray.pushMap(convertMapToWritableMap(value)) is List<*> -> writableArray.pushArray(convertListToWritableArray(value)) is Boolean -> writableArray.pushBoolean(value) is Int -> writableArray.pushInt(value) is Double -> writableArray.pushDouble(value) is String -> writableArray.pushString(value) else -> writableArray.pushString(value?.toString() ?: "null") } } return writableArray } // Updated handleCallback to handle safe calls and eliminate unnecessary warnings private fun handleCallback(promise: Promise, result: Any?, metadata: Map?, error: ZSMError?) { if (error != null) { val errorMap = Arguments.createMap() errorMap.putString("message", error.message ?: "Unknown error") errorMap.putInt("code", error.code ?: -1) // Default to -1 if code is null errorMap.putString("details", error.toString()) // Replace with actual details if available promise.reject("zsm_operation_error", error.message, Throwable(error.toString())) } else { val resultMap = Arguments.createMap() resultMap.putMap("result", convertJsonToWritableMap(result as? JSONObject ?: JSONObject())) resultMap.putMap("metadata", metadata?.let { convertMapToWritableMap(it) }) promise.resolve(resultMap) } } // Improved convertJsonToWritableMap to remove unnecessary safe calls // todo: remove the need for these manual conversions private fun convertJsonToWritableMap(json: JSONObject?): WritableMap { val writableMap = Arguments.createMap() json?.keys()?.forEach { key -> val value = json.get(key) when (value) { is JSONObject -> writableMap.putMap(key, convertJsonToWritableMap(value)) is org.json.JSONArray -> writableMap.putArray(key, convertJsonToWritableArray(value)) is Boolean -> writableMap.putBoolean(key, value) is Int -> writableMap.putInt(key, value) is Double -> writableMap.putDouble(key, value) is String -> writableMap.putString(key, value) else -> writableMap.putString(key, "$value") } } return writableMap } // Improved convertJsonToWritableArray // todo: remove the need for these manual conversions private fun convertJsonToWritableArray(jsonArray: org.json.JSONArray?): WritableArray { val writableArray = Arguments.createArray() jsonArray?.let { for (i in 0 until it.length()) { val value = it.get(i) when (value) { is JSONObject -> writableArray.pushMap(convertJsonToWritableMap(value)) is org.json.JSONArray -> writableArray.pushArray(convertJsonToWritableArray(value)) is Boolean -> writableArray.pushBoolean(value) is Int -> writableArray.pushInt(value) is Double -> writableArray.pushDouble(value) is String -> writableArray.pushString(value) else -> writableArray.pushString("$value") } } } return writableArray } }