Refactor: Improve handling of encrypted data

This commit refactors the handling of encryption and decryption requests to provide better context and a clearer user interface.

- **Introduce typed encrypted data models**: Replaces the generic `encryptDecryptResponse` string with specific data classes (`ClearTextEncryptedDataKind`, `EventEncryptedDataKind`, `TagArrayEncryptedDataKind`) to better represent the structure of the data being processed.
- **Enhance UI for encryption/decryption**: The UI now displays more contextual information about the content being encrypted or decrypted, such as distinguishing between plain text, events (including sealed rumors), and tag arrays.
- **Simplify intent handling**: Removes custom JSON serialization/deserialization for `AmberBunkerRequest` and streamlines intent processing logic.
- **Refactor bunker request creation**: Updates the creation of `AmberBunkerRequest` to use the new typed data models, improving type safety and code clarity throughout the app.
This commit is contained in:
greenart7c3
2025-11-07 09:45:25 -03:00
parent 7b4e249c6a
commit 09cf59fc09
12 changed files with 220 additions and 258 deletions
@@ -268,7 +268,7 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
Amber.instance.setMainActivity(this)
mainViewModel.showBunkerRequests(callingPackage)
mainViewModel.showBunkerRequests()
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR != "offline") {
val connectivityManager =
@@ -4,22 +4,18 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.service.BunkerRequestUtils
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents.intents
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
import com.greenart7c3.nostrsigner.ui.navigation.Route
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.Hex
import kotlin.text.isNotBlank
import kotlin.text.startsWith
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@@ -96,32 +92,13 @@ class MainViewModel(val context: Context) : ViewModel() {
}
}
fun showBunkerRequests(callingPackage: String?, accountStateViewModel: AccountStateViewModel? = null) {
fun showBunkerRequests(accountStateViewModel: AccountStateViewModel? = null) {
val requests =
BunkerRequestUtils.getBunkerRequests().map {
it.copy()
}
viewModelScope.launch(Dispatchers.IO) {
val account = LocalPreferences.loadFromEncryptedStorage(context)
account?.let { acc ->
requests.forEach {
val contentIntent =
Intent(Amber.instance, MainActivity::class.java).apply {
data = "nostrsigner:".toUri()
}
contentIntent.putExtra("bunker", it.toJson())
contentIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
val intentData = IntentUtils.getIntentData(context, contentIntent, callingPackage, Route.IncomingRequest.route, acc)
contentIntent.putExtra("current_account", acc.npub)
if (intentData != null) {
if (intents.value.none { item -> item.id == intentData.id }) {
intents.value += listOf(intentData)
}
}
}
}
if (requests.isNotEmpty()) {
val npub = getAccount(null)
val currentAccount = LocalPreferences.currentAccount(context)
@@ -69,8 +69,9 @@ class SignerProvider : ContentProvider() {
} else {
if (Hex.isHex(sortOrder)) {
sortOrder
} else {
null
}
null
}
if (packageName == null) {
Log.d(Amber.TAG, "No package name")
@@ -2,26 +2,10 @@ package com.greenart7c3.nostrsigner.models
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.ui.RememberType
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
import kotlin.collections.asIterable
enum class EncryptionType {
NIP44,
@@ -37,80 +21,8 @@ data class AmberBunkerRequest(
val closeApplication: Boolean,
val name: String,
val signedEvent: Event?,
val encryptDecryptResponse: String?,
val encryptedData: EncryptedDataKind?,
val checked: MutableState<Boolean> = mutableStateOf(true),
val rememberType: MutableState<RememberType> = mutableStateOf(RememberType.NEVER),
val encryptionType: EncryptionType,
) {
fun toJson(): String {
return mapper.writeValueAsString(this)
}
companion object {
val mapper: ObjectMapper =
jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(
SimpleModule()
.addDeserializer(AmberBunkerRequest::class.java, AmberBunkerRequestDeserializer())
.addSerializer(AmberBunkerRequest::class.java, AmberBunkerRequestSerializer()),
)
fun fromJson(jsonObject: JsonNode): AmberBunkerRequest {
return AmberBunkerRequest(
request = JacksonMapper.mapper.readValue(jsonObject.get("request").asText().intern()),
localKey = jsonObject.get("localKey")?.asText()?.intern() ?: "",
relays = jsonObject.get("relays")?.asIterable()?.toList()?.mapNotNull {
RelayUrlNormalizer.normalizeOrNull(it.asText())
} ?: Amber.instance.getSavedRelays().toList(),
currentAccount = jsonObject.get("currentAccount")?.asText()?.intern() ?: "",
nostrConnectSecret = jsonObject.get("nostrConnectSecret")?.asText()?.intern() ?: "",
closeApplication = jsonObject.get("closeApplication")?.asBoolean() != false,
name = jsonObject.get("name")?.asText()?.intern() ?: "",
signedEvent = jsonObject.get("signedEvent")?.asText()?.intern()?.let {
try {
JacksonMapper.fromJson(it)
} catch (_: Exception) {
null
}
},
encryptDecryptResponse = jsonObject.get("encryptDecryptResponse")?.asText()?.intern(),
encryptionType = EncryptionType.valueOf(jsonObject.get("encryptionType")?.asText()?.intern() ?: "NIP44"),
)
}
private class AmberBunkerRequestDeserializer : StdDeserializer<AmberBunkerRequest>(AmberBunkerRequest::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): AmberBunkerRequest {
return fromJson(jp.codec.readTree(jp))
}
}
private class AmberBunkerRequestSerializer : StdSerializer<AmberBunkerRequest>(AmberBunkerRequest::class.java) {
override fun serialize(
value: AmberBunkerRequest,
gen: JsonGenerator,
provider: SerializerProvider,
) {
gen.writeStartObject()
gen.writeStringField("request", mapper.writeValueAsString(value.request))
gen.writeStringField("localKey", value.localKey)
gen.writeArrayFieldStart("relays")
value.relays.forEach {
gen.writeString(it.url)
}
gen.writeEndArray()
gen.writeStringField("currentAccount", value.currentAccount)
gen.writeStringField("nostrConnectSecret", value.nostrConnectSecret)
gen.writeBooleanField("closeApplication", value.closeApplication)
gen.writeStringField("name", value.name)
gen.writeStringField("signedEvent", value.signedEvent?.toJson())
gen.writeStringField("encryptDecryptResponse", value.encryptDecryptResponse)
gen.writeStringField("encryptionType", value.encryptionType.name)
gen.writeEndObject()
}
}
}
}
)
@@ -36,9 +36,12 @@ import com.greenart7c3.nostrsigner.database.HistoryEntity
import com.greenart7c3.nostrsigner.database.LogEntity
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
import com.greenart7c3.nostrsigner.models.EncryptionType
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.service.NotificationUtils.sendNotification
import com.greenart7c3.nostrsigner.service.model.AmberEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
@@ -56,6 +59,7 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Encrypt
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID
import kotlinx.coroutines.launch
@@ -174,21 +178,114 @@ class EventNotificationConsumer(private val applicationContext: Context) {
null
}
val encryptDecryptResponse = if (bunkerRequest is BunkerRequestNip44Decrypt) {
acc.signer.signerSync.nip44Decrypt(bunkerRequest.ciphertext, bunkerRequest.pubKey)
val encryptedDataKind = if (bunkerRequest is BunkerRequestNip44Decrypt) {
val result = acc.signer.signerSync.nip44Decrypt(bunkerRequest.ciphertext, bunkerRequest.pubKey)
if (result.startsWith("{")) {
val event = AmberEvent.fromJson(result)
if (event.kind == SealedRumorEvent.KIND) {
val decryptedSealData = acc.signer.signerSync.decrypt(event.content, acc.hexKey)
if (decryptedSealData.startsWith("{")) {
val sealEvent = AmberEvent.fromJson(decryptedSealData)
EventEncryptedDataKind(event, EventEncryptedDataKind(sealEvent, null, decryptedSealData), result)
} else if (decryptedSealData.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(decryptedSealData)
EventEncryptedDataKind(event, TagArrayEncryptedDataKind(tagArray, decryptedSealData), decryptedSealData)
} else {
EventEncryptedDataKind(event, ClearTextEncryptedDataKind(event.content, decryptedSealData), result)
}
} else {
EventEncryptedDataKind(event, null, result)
}
} else if (result.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(result)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(bunkerRequest.ciphertext, result)
}
} else if (bunkerRequest is BunkerRequestNip44Encrypt) {
acc.signer.signerSync.nip44Encrypt(bunkerRequest.message, bunkerRequest.pubKey)
val result = acc.signer.signerSync.nip44Encrypt(bunkerRequest.message, bunkerRequest.pubKey)
if (bunkerRequest.message.startsWith("{")) {
try {
val event = AmberEvent.fromJson(bunkerRequest.message)
if (event.kind == SealedRumorEvent.KIND) {
val decryptedSealData = acc.signer.signerSync.decrypt(event.content, acc.hexKey)
if (decryptedSealData.startsWith("{")) {
val sealEvent = AmberEvent.fromJson(decryptedSealData)
EventEncryptedDataKind(event, EventEncryptedDataKind(sealEvent, null, decryptedSealData), result)
} else if (decryptedSealData.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(decryptedSealData)
EventEncryptedDataKind(event, TagArrayEncryptedDataKind(tagArray, decryptedSealData), decryptedSealData)
} else {
EventEncryptedDataKind(event, ClearTextEncryptedDataKind(event.content, decryptedSealData), result)
}
} else {
EventEncryptedDataKind(event, null, result)
}
} catch (e: Exception) {
Log.e("IntentUtils", "Error parsing JSON: ${e.message}")
ClearTextEncryptedDataKind(bunkerRequest.message, result)
}
} else if (bunkerRequest.message.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(bunkerRequest.message)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(bunkerRequest.message, result)
}
} else if (bunkerRequest is BunkerRequestNip04Decrypt) {
acc.signer.signerSync.nip04Decrypt(bunkerRequest.ciphertext, bunkerRequest.pubKey)
val result = acc.signer.signerSync.nip04Decrypt(bunkerRequest.ciphertext, bunkerRequest.pubKey)
if (result.startsWith("{")) {
val event = AmberEvent.fromJson(result)
if (event.kind == SealedRumorEvent.KIND) {
val decryptedSealData = acc.signer.signerSync.decrypt(event.content, acc.hexKey)
if (decryptedSealData.startsWith("{")) {
val sealEvent = AmberEvent.fromJson(decryptedSealData)
EventEncryptedDataKind(event, EventEncryptedDataKind(sealEvent, null, decryptedSealData), result)
} else if (decryptedSealData.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(decryptedSealData)
EventEncryptedDataKind(event, TagArrayEncryptedDataKind(tagArray, decryptedSealData), decryptedSealData)
} else {
EventEncryptedDataKind(event, ClearTextEncryptedDataKind(event.content, decryptedSealData), result)
}
} else {
EventEncryptedDataKind(event, null, result)
}
} else if (result.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(result)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(bunkerRequest.ciphertext, result)
}
} else if (bunkerRequest is BunkerRequestNip04Encrypt) {
acc.signer.signerSync.nip04Encrypt(bunkerRequest.message, bunkerRequest.pubKey)
} else if (bunkerRequest.method == "decrypt_zap_event") {
AmberUtils.encryptOrDecryptData(
bunkerRequest.params[1],
SignerType.DECRYPT_ZAP_EVENT,
acc,
bunkerRequest.params.first(),
)
val result = acc.signer.signerSync.nip04Encrypt(bunkerRequest.message, bunkerRequest.pubKey)
if (bunkerRequest.message.startsWith("{")) {
try {
val event = AmberEvent.fromJson(bunkerRequest.message)
if (event.kind == SealedRumorEvent.KIND) {
val decryptedSealData = acc.signer.signerSync.decrypt(event.content, acc.hexKey)
if (decryptedSealData.startsWith("{")) {
val sealEvent = AmberEvent.fromJson(decryptedSealData)
EventEncryptedDataKind(event, EventEncryptedDataKind(sealEvent, null, decryptedSealData), result)
} else if (decryptedSealData.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(decryptedSealData)
EventEncryptedDataKind(event, TagArrayEncryptedDataKind(tagArray, decryptedSealData), decryptedSealData)
} else {
EventEncryptedDataKind(event, ClearTextEncryptedDataKind(event.content, decryptedSealData), result)
}
} else {
EventEncryptedDataKind(event, null, result)
}
} catch (e: Exception) {
Log.e("IntentUtils", "Error parsing JSON: ${e.message}")
ClearTextEncryptedDataKind(bunkerRequest.message, result)
}
} else if (bunkerRequest.message.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(bunkerRequest.message)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(bunkerRequest.message, result)
}
} else {
null
}
@@ -217,7 +314,7 @@ class EventNotificationConsumer(private val applicationContext: Context) {
closeApplication = true,
name = "",
signedEvent = signedEvent,
encryptDecryptResponse = encryptDecryptResponse,
encryptedData = encryptedDataKind,
encryptionType = encryptionType,
)
@@ -25,7 +25,6 @@ import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions
import com.greenart7c3.nostrsigner.database.HistoryEntity
import com.greenart7c3.nostrsigner.database.LogEntity
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
import com.greenart7c3.nostrsigner.models.CompressionType
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
@@ -595,22 +594,8 @@ object IntentUtils {
return null
}
val bunkerRequest =
if (intent.getStringExtra("bunker") != null) {
AmberBunkerRequest.mapper.readValue(
intent.getStringExtra("bunker"),
AmberBunkerRequest::class.java,
)
} else {
null
}
var localAccount = currentLoggedInAccount
if (bunkerRequest != null) {
LocalPreferences.loadFromEncryptedStorageSync(context, bunkerRequest.currentAccount)?.let {
localAccount = it
}
} else if (intent.getStringExtra("current_user") != null) {
if (intent.getStringExtra("current_user") != null) {
var npub = intent.getStringExtra("current_user")
if (npub != null) {
npub = parsePubKey(npub)
@@ -622,8 +607,6 @@ object IntentUtils {
if (intent.dataString?.startsWith("nostrconnect:") == true) {
NostrConnectUtils.getIntentFromNostrConnect(intent, localAccount)
} else if (bunkerRequest != null) {
BunkerRequestUtils.addRequest(bunkerRequest)
} else if (intent.extras?.getString(Browser.EXTRA_APPLICATION_ID) == null) {
return getIntentDataFromIntent(context, intent, packageName, route, localAccount)
} else {
@@ -124,7 +124,7 @@ object NostrConnectUtils {
closeApplication = intent.getBooleanExtra("closeApplication", true),
name = name,
signedEvent = null,
encryptDecryptResponse = null,
encryptedData = null,
encryptionType = EncryptionType.NIP44,
),
)
@@ -183,7 +183,21 @@ fun SeeDetailsScreen(
val event = bunkerRequest.signedEvent!!
if (event.kind == 22242) AmberEvent.relay(event) else event.content
} else {
bunkerRequest.encryptDecryptResponse ?: BunkerRequestUtils.getDataFromBunker(bunkerRequest.request)
if (type.name.contains("ENCRYPT") && bunkerRequest.encryptedData is ClearTextEncryptedDataKind) {
bunkerRequest.encryptedData.text
} else if (bunkerRequest.encryptedData is EventEncryptedDataKind) {
if (bunkerRequest.encryptedData.sealEncryptedDataKind != null) {
if (bunkerRequest.encryptedData.sealEncryptedDataKind is EventEncryptedDataKind) {
bunkerRequest.encryptedData.sealEncryptedDataKind.event.content
} else {
bunkerRequest.encryptedData.sealEncryptedDataKind.result
}
} else {
bunkerRequest.encryptedData.event.content
}
} else {
bunkerRequest.encryptedData?.result ?: BunkerRequestUtils.getDataFromBunker(bunkerRequest.request)
}
}
Text(
@@ -306,7 +306,7 @@ fun BunkerSingleEventHomeScreen(
account = account,
modifier = modifier,
content = bunkerRequest.request.message,
encryptedData = bunkerRequest.encryptDecryptResponse ?: "",
encryptedData = bunkerRequest.encryptedData,
shouldRunOnAccept = acceptOrReject,
appName = appName,
type = type,
@@ -315,7 +315,7 @@ fun BunkerSingleEventHomeScreen(
context = context,
account = account,
key = key,
response = bunkerRequest.encryptDecryptResponse ?: "",
response = bunkerRequest.encryptedData?.result ?: "",
bunkerRequest = bunkerRequest,
kind = null,
onLoading = onLoading,
@@ -371,12 +371,12 @@ fun BunkerSingleEventHomeScreen(
account = account,
modifier = modifier,
content = bunkerRequest.request.ciphertext,
encryptedData = bunkerRequest.encryptDecryptResponse ?: "",
encryptedData = bunkerRequest.encryptedData,
shouldRunOnAccept = acceptOrReject,
appName = appName,
type = type,
onAccept = {
val result = bunkerRequest.encryptDecryptResponse ?: ""
val result = bunkerRequest.encryptedData?.result ?: ""
BunkerRequestUtils.sendResult(
context = context,
@@ -438,12 +438,12 @@ fun BunkerSingleEventHomeScreen(
account = account,
modifier = modifier,
content = bunkerRequest.request.message,
encryptedData = bunkerRequest.encryptDecryptResponse ?: "",
encryptedData = bunkerRequest.encryptedData,
shouldRunOnAccept = acceptOrReject,
appName = appName,
type = type,
onAccept = {
val result = bunkerRequest.encryptDecryptResponse ?: ""
val result = bunkerRequest.encryptedData?.result ?: ""
BunkerRequestUtils.sendResult(
context = context,
@@ -505,12 +505,12 @@ fun BunkerSingleEventHomeScreen(
account = account,
modifier = modifier,
content = bunkerRequest.request.ciphertext,
encryptedData = bunkerRequest.encryptDecryptResponse ?: "",
encryptedData = bunkerRequest.encryptedData,
shouldRunOnAccept = acceptOrReject,
appName = appName,
type = type,
onAccept = {
val result = bunkerRequest.encryptDecryptResponse ?: ""
val result = bunkerRequest.encryptedData?.result ?: ""
BunkerRequestUtils.sendResult(
context = context,
@@ -658,16 +658,16 @@ fun BunkerSingleEventHomeScreen(
account = account,
modifier = modifier,
content = bunkerRequest.request.params[1],
encryptedData = bunkerRequest.encryptDecryptResponse ?: "",
encryptedData = bunkerRequest.encryptedData,
shouldRunOnAccept = acceptOrReject,
appName = appName,
type = type,
onAccept = {
val result =
if (bunkerRequest.encryptDecryptResponse == "Could not decrypt the message") {
if (bunkerRequest.encryptedData?.result == "Could not decrypt the message") {
""
} else {
bunkerRequest.encryptDecryptResponse ?: ""
bunkerRequest.encryptedData?.result ?: ""
}
BunkerRequestUtils.sendResult(
@@ -1,11 +1,8 @@
package com.greenart7c3.nostrsigner.ui.components
import android.content.ClipData
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
@@ -17,7 +14,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -27,7 +23,6 @@ import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -42,7 +37,6 @@ import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.ui.RememberType
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import kotlinx.coroutines.launch
@Composable
fun EncryptDecryptData(
@@ -200,7 +194,7 @@ fun BunkerEncryptDecryptData(
account: Account,
modifier: Modifier,
content: String,
encryptedData: String,
encryptedData: EncryptedDataKind?,
shouldRunOnAccept: Boolean?,
appName: String,
type: SignerType,
@@ -222,41 +216,91 @@ fun BunkerEncryptDecryptData(
) {
ProfilePicture(account)
val message =
when (type) {
SignerType.NIP44_ENCRYPT -> stringResource(R.string.encrypt_nip44)
SignerType.NIP04_ENCRYPT -> stringResource(R.string.encrypt_nip04)
SignerType.NIP44_DECRYPT -> stringResource(R.string.decrypt_nip44)
SignerType.NIP04_DECRYPT -> stringResource(R.string.decrypt_nip04)
SignerType.DECRYPT_ZAP_EVENT -> stringResource(R.string.decrypt_zap_event)
else -> stringResource(R.string.encrypt_decrypt)
}
Text(
buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(appName)
}
append(" requests $message")
if (type.name.contains("ENCRYPT")) {
when (encryptedData) {
is EventEncryptedDataKind -> {
val permission = Permission("sign_event", encryptedData.event.kind)
append(stringResource(R.string.wants_to_encrypt_with, permission.toLocalizedString(Amber.instance), type.name.split("_").first()))
}
is TagArrayEncryptedDataKind -> {
append(stringResource(R.string.wants_to_encrypt_this_list_of_tags_with, type.name.split("_").first()))
}
else -> append(stringResource(R.string.wants_to_encrypt_this_text_with, type.name.split("_").first()))
}
} else {
when (encryptedData) {
is EventEncryptedDataKind -> {
val permission = Permission("sign_event", encryptedData.event.kind)
append(stringResource(R.string.wants_to_read_from_encrypted_content, permission.toLocalizedString(Amber.instance), type.name.split("_").first()))
}
is TagArrayEncryptedDataKind -> {
append(stringResource(R.string.wants_to_read_this_list_of_tags_from_encrypted_content, type.name.split("_").first()))
}
else -> append(stringResource(R.string.wants_to_read_this_text_from_encrypted_content, type.name.split("_").first()))
}
}
},
fontSize = 18.sp,
)
Spacer(Modifier.size(4.dp))
if (!type.toString().contains("DECRYPT")) {
Card(
modifier = Modifier
.fillMaxWidth(),
) {
Column(Modifier.padding(6.dp)) {
Text(
"Event content",
fontWeight = FontWeight.Bold,
)
Card(
modifier = Modifier
.fillMaxWidth(),
) {
Column(Modifier.padding(6.dp)) {
Text(
stringResource(R.string.content).capitalize(Locale.current),
fontWeight = FontWeight.Bold,
)
if (encryptedData is EventEncryptedDataKind) {
if (encryptedData.sealEncryptedDataKind != null) {
if (encryptedData.sealEncryptedDataKind is EventEncryptedDataKind) {
val content = if (encryptedData.sealEncryptedDataKind.event.kind == 22242) encryptedData.sealEncryptedDataKind.event.relay() else encryptedData.sealEncryptedDataKind.event.content
if (encryptedData.event.kind == ContactListEvent.KIND) {
ContactListDetail(
title = stringResource(R.string.following),
text = "${encryptedData.event.verifiedFollowKeySet().size}",
)
} else {
Text(
content,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
)
}
}
} else {
val content = if (encryptedData.event.kind == 22242) encryptedData.event.relay() else encryptedData.event.content
if (encryptedData.event.kind == ContactListEvent.KIND) {
ContactListDetail(
title = stringResource(R.string.following),
text = "${encryptedData.event.verifiedFollowKeySet().size}",
)
} else {
Text(
content,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
)
}
}
} else {
val content = if (type.name.contains("ENCRYPT") && encryptedData is ClearTextEncryptedDataKind) encryptedData.text else encryptedData?.result ?: ""
Text(
content,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
@@ -264,37 +308,8 @@ fun BunkerEncryptDecryptData(
}
}
}
RawJsonButton(
onCLick = {
showMore = !showMore
},
if (!showMore) stringResource(R.string.show_details) else stringResource(R.string.hide_details),
)
if (showMore) {
RawJson(
content,
encryptedData,
Modifier.height(200.dp),
stringResource(R.string.encrypted_decrypted_data),
type,
) {
coroutineScope.launch {
clipboardManager.setClipEntry(
ClipEntry(
ClipData.newPlainText("", encryptedData),
),
)
Toast.makeText(
context,
context.getString(R.string.data_copied_to_the_clipboard),
Toast.LENGTH_SHORT,
).show()
}
}
} else {
Spacer(modifier = Modifier.weight(1f))
}
Spacer(modifier = Modifier.weight(1f))
RememberMyChoice(
shouldRunOnAccept,
@@ -894,7 +894,7 @@ fun BunkerMultiEventHomeScreen(
thisAccount.npub,
)
val signature = request.encryptDecryptResponse ?: continue
val signature = request.encryptedData?.result ?: continue
val localBunkerRequest = request.copy()
BunkerRequestUtils.remove(request.request.id)
if (request.checked.value) {
@@ -1,37 +0,0 @@
package com.greenart7c3.nostrsigner.models
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect
import org.junit.Assert.assertEquals
import org.junit.Test
class AmberBunkerRequestTest {
@Test
fun `encryption type survives serialization`() {
val relay = RelayUrlNormalizer.normalizeOrNull("wss://relay.example.com")
requireNotNull(relay) { "Expected relay URL to normalize" }
val request =
AmberBunkerRequest(
request = BunkerRequestConnect(remoteKey = HEX_KEY, secret = "secret", permissions = null),
localKey = HEX_KEY,
relays = listOf(relay),
currentAccount = "npub1test",
nostrConnectSecret = "secret",
closeApplication = true,
name = "Test App",
signedEvent = null,
encryptDecryptResponse = null,
encryptionType = EncryptionType.NIP04,
)
val json = request.toJson()
val roundTrip = AmberBunkerRequest.mapper.readValue(json, AmberBunkerRequest::class.java)
assertEquals(EncryptionType.NIP04, roundTrip.encryptionType)
}
private companion object {
private const val HEX_KEY = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
}