package com.rnnopass import android.annotation.SuppressLint import android.app.Application import android.graphics.Bitmap import android.util.Base64 import android.util.Log import by.fro.core.external.* import by.fro.core.external.authorization.NoPassUserManager import by.fro.core.external.entities.NoPassUser import com.rnnopass.dto.NativeAuthRequestData import com.rnnopass.dto.NativeKeyphraseData import com.rnnopass.dto.NativeRadiusAuthRequestData import by.fro.core.external.registration.NoPassRegistrationManager import com.facebook.react.bridge.* import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter import com.github.florent37.application.provider.ApplicationProvider import com.google.gson.Gson import java.io.ByteArrayOutputStream fun provideApp(application: Application) = provideApplication(application) fun toJsonString(vararg item: Any?): String { return if (item.size == 1) { Gson().toJson(item[0]) } else Gson().toJson(item) } fun bitmapToBase64(bitmap: Bitmap): String? { val byteArrayOutputStream = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream) val byteArray: ByteArray = byteArrayOutputStream.toByteArray() return Base64.encodeToString(byteArray, Base64.DEFAULT) } fun recursivelyDeconstructReadableMap(readableMap: ReadableMap): Map? { val iterator = readableMap.keySetIterator() val deconstructedMap: MutableMap = HashMap() while (iterator.hasNextKey()) { val key = iterator.nextKey() val type = readableMap.getType(key) when (type) { ReadableType.Null -> deconstructedMap[key] = null ReadableType.Boolean -> deconstructedMap[key] = readableMap.getBoolean(key) ReadableType.Number -> deconstructedMap[key] = readableMap.getDouble(key) ReadableType.String -> deconstructedMap[key] = readableMap.getString(key) ReadableType.Map -> deconstructedMap[key] = recursivelyDeconstructReadableMap(readableMap.getMap(key)!!) ReadableType.Array -> deconstructedMap[key] = recursivelyDeconstructReadableArray(readableMap.getArray(key)!!) else -> throw IllegalArgumentException("Could not convert object with key: $key.") } } return deconstructedMap } fun recursivelyDeconstructReadableArray(readableArray: ReadableArray): List? { val deconstructedList: MutableList = mutableListOf() for (i in 0 until readableArray.size()) { val indexType = readableArray.getType(i) when (indexType) { ReadableType.Null -> deconstructedList.add(i, null) ReadableType.Boolean -> deconstructedList.add(i, readableArray.getBoolean(i)) ReadableType.Number -> deconstructedList.add(i, readableArray.getDouble(i)) ReadableType.String -> deconstructedList.add(i, readableArray.getString(i)) ReadableType.Map -> deconstructedList.add(i, recursivelyDeconstructReadableMap(readableArray.getMap(i))) ReadableType.Array -> deconstructedList.add(i, recursivelyDeconstructReadableArray(readableArray.getArray(i))) else -> throw java.lang.IllegalArgumentException("Could not convert object at index $i.") } } return deconstructedList } @SuppressLint("CheckResult") class NoPassRNBridgeModule(val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private var userManager: NoPassUserManager? = null private var registrationManager: NoPassRegistrationManager? = null private val LOG_TAG = "NoPassRNBridge" override fun getName() = "NoPassRNBridgeModule" private val ON_AUTH_REQUEST_RECEIVED = "OnAuthRequestReceived" private val ON_RADIUS_AUTH_REQUEST_RECEIVED = "OnRadiusAuthRequestReceived" private val ON_OTP_CHANGED = "OnOTPChangedListener" private val ON_AUTH_RESULT = "OnAuthenticationResultListener" private val ON_TICK = "OnTickListener" private val ON_UPDATE = "OnUserUpdateResultListener" private val ON_DELETE = "OnUserDeleteResultListener" private val ON_BACKUP = "OnUsersBackupResultListener" private val ON_RESTORE = "OnUsersRestoreResultListener" private val ON_SESSION_TIMEOUT = "OnSessionTimeoutListener" private val ON_CONFIRMATION_CODE_REQUIRED = "OnConfirmationCodeRequiredListener" private val ON_REGISTRATION_RESULT = "OnRegistrationResultListener" private val ON_SYNC_CONFIRMATION_CODE_ACCEPTED = "OnSyncConfirmationCodeAcceptedListener" private val ON_SYNC_CONFIRMATION_CODE_REQUIRED = "OnSyncConfirmationCodeRequiredListener" private val ON_SYNC_RESULT = "OnSyncResultListener" /**Registration manager methods*/ @ReactMethod fun initializeNoPass() { provideApp(ApplicationProvider.application!!) userManager = NoPassUserManager.configure() .setOnAuthRequestReceivedListener { noPassUser, keyphraseData, initialProgress -> sendEvent(ON_AUTH_REQUEST_RECEIVED, toJsonString(NativeAuthRequestData( noPassUser, NativeKeyphraseData( keyphraseData.digits, bitmapToBase64(keyphraseData.picture) ?: "", keyphraseData.changeTimeMillis, keyphraseData.radiusSessionTtl ), initialProgress ))) } .setOnRadiusUserAuthRequestReceivedListener { noPassUser, initProgress, radiusSessionTTL -> sendEvent(ON_RADIUS_AUTH_REQUEST_RECEIVED, toJsonString(NativeRadiusAuthRequestData( noPassUser, initProgress, radiusSessionTTL ))) } .setOnOTPChangedListener { keyphraseData -> sendEvent(ON_OTP_CHANGED, toJsonString(NativeKeyphraseData( keyphraseData.digits, bitmapToBase64(keyphraseData.picture) ?: "", keyphraseData.changeTimeMillis, keyphraseData.radiusSessionTtl ))) } .setOnAuthenticationResultListener { success, throwable -> sendEvent(ON_AUTH_RESULT, toJsonString(if (throwable == null) success else throwable.message)) } .setOnTickListener { sendEvent(ON_TICK, toJsonString(it)) } .setOnUserUpdateResultListener { noPassUser, success, throwable -> sendEvent(ON_UPDATE, toJsonString(if (throwable == null) noPassUser else throwable.message)) } .setOnUserDeleteResultListener { noPassUser, b, throwable -> sendEvent(ON_DELETE, toJsonString(if (throwable == null) noPassUser else throwable.message)) } .setOnUsersBackupResultListener { backupResultString, list, map, throwable -> sendEvent(ON_BACKUP, toJsonString(if (throwable == null) backupResultString else throwable.message)) } .setOnUsersRestoreResultListener { success, list, map, throwable -> sendEvent(ON_RESTORE, toJsonString(if (throwable == null) success else throwable.message)) } .setOnSessionTimeoutListener { sendEvent(ON_SESSION_TIMEOUT, toJsonString(it)) } .build() registrationManager = NoPassRegistrationManager.configure() .setOnConfirmationCodeRequiredListener { sendEvent(ON_CONFIRMATION_CODE_REQUIRED, toJsonString(it)) } .setOnRegistrationResultListener { success, throwable -> sendEvent(ON_REGISTRATION_RESULT, toJsonString(if (throwable == null) success else throwable.message)) } .setOnSyncConfirmationCodeAcceptedListener { sendEvent(ON_SYNC_CONFIRMATION_CODE_ACCEPTED, toJsonString(it)) } .setOnSyncConfirmationCodeRequiredListener { sendEvent(ON_SYNC_CONFIRMATION_CODE_REQUIRED, toJsonString(it)) } .setOnSyncResultListener { success, throwable -> sendEvent(ON_SYNC_RESULT, if (throwable == null) success else throwable.message) } .build() } @ReactMethod fun onConfirmationCodeRequired(successPromise: Promise) { // TODO: 18.06.2021 move to events NoPassRegistrationManager.configure().setOnConfirmationCodeRequiredListener { successPromise.resolve(it.toString()) } } @ReactMethod fun onSyncConfirmationCodeRequired(successPromise: Promise) {// TODO: 18.06.2021 move to events NoPassRegistrationManager.configure().setOnSyncConfirmationCodeRequiredListener { successPromise.resolve(it.toString()) } } @ReactMethod fun onSyncConfirmationCodeAccepted(successPromise: Promise) {// TODO: 18.06.2021 move to events NoPassRegistrationManager.configure().setOnSyncConfirmationCodeAcceptedListener { successPromise.resolve(it.toString()) } } @ReactMethod fun registerUser(registrationData: String, resultPromise: Promise) { if (registrationManager == null) Log.e(LOG_TAG,"RegistrationManager is null. Initialize it before registration.") registrationManager?.apply { register(registrationData) NoPassRegistrationManager.configure() .setOnRegistrationResultListener { success, error -> if (success) resultPromise.resolve(true) else resultPromise.reject(error) } } } @ReactMethod fun provideFirebaseToken(token: String) { NoPassUtil.provideFirebaseToken(token) } @ReactMethod fun provideFirebaseMessage(data: ReadableMap) { NoPassUtil.provideFirebaseMessage(recursivelyDeconstructReadableMap(data) as HashMap) } @ReactMethod fun syncAccounts(syncData: String, resultPromise: Promise) { if (registrationManager == null) Log.w(LOG_TAG, "RegistrationManager is null. Initialize it before synchronization.") registrationManager?.apply { syncAccounts(syncData) NoPassRegistrationManager.configure() .setOnSyncResultListener { success, error -> if (success) resultPromise.resolve(true) else resultPromise.reject(error) } } } /**Authentication manager methods*/ @ReactMethod fun backupAccounts(pin: Int, oldBackupData: String?, resultPromise: Promise) { userManager?.apply { backupAccounts(pin, oldBackupData) NoPassUserManager.configure().setOnUsersBackupResultListener { result, list, map, throwable -> println("backup result string: $result") if (throwable != null) println("Error on backup: ${throwable.message}") if (throwable == null) resultPromise.resolve(result) else resultPromise.reject(throwable) } } } @ReactMethod fun deleteBackup(resultPromise: Promise) { userManager?.apply { invalidateBackup { success: Boolean, throwable: Throwable? -> if (success) resultPromise.resolve(true) else resultPromise.reject(throwable) } } } @ReactMethod fun restoreAccounts(backupData: String, pin: Int, resultPromise: Promise) { userManager?.apply { restoreAccounts(backupData, pin) NoPassUserManager.configure().setOnUsersRestoreResultListener { success, list, map, throwable -> if (success) resultPromise.resolve(Gson().toJson(list)) else resultPromise.reject(throwable) } } } @ReactMethod fun authenticate() { userManager?.authenticate() } @ReactMethod fun decline() { userManager?.decline() } @ReactMethod fun sendDeclineReason(reason: String) { val reasonObject = try { NoPassUserManager.DeclineReason.valueOf(reason) } catch (e: Throwable) { Log.w(LOG_TAG, "Invalid decline reason string"); null } reasonObject?.let { // userManager?.sendDeclineReason(it) println("Not implemented yet") } } @ReactMethod fun getUsers(resultPromise: Promise) { userManager?.getAllUsers { users, throwable -> if (throwable == null) { resultPromise.resolve(Gson().toJson(users)) } else resultPromise.reject(throwable) } } @ReactMethod fun getAuthenticationHistory(resultPromise: Promise) { userManager?.getAuthenticationHistory { list, throwable -> if (throwable == null) { resultPromise.resolve(Gson().toJson(list)) } else resultPromise.reject(throwable) } } @ReactMethod fun deleteUserById(userId: String, resultPromise: Promise) { userManager?.deleteUser(userId) { noPassUser: NoPassUser?/*no idea why I put it there, let's just ignore it*/, result: Boolean, throwable: Throwable? -> if (throwable == null) { resultPromise.resolve(result) } else resultPromise.reject(throwable) } } @ReactMethod fun validatePin(encryptedString: String, password: String, resultPromise: Promise) { userManager?.validatePin(encryptedString, password) ?.subscribe({ resultPromise.resolve(it) }, { resultPromise.reject(it) }) } /**Event processing*/ private fun sendEvent(eventName: String, params: Any?) { reactContext .getJSModule(RCTDeviceEventEmitter::class.java) .emit(eventName, params) } }