diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt index 73c1ff23..7d9434e7 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -373,25 +373,14 @@ fun MainScreen( content = { val scrollState = rememberScrollState() - val modifier = if (intents.isEmpty() || packageName == null || destinationRoute != Route.IncomingRequest.route) { - Modifier - .fillMaxSize() - .padding(padding) - .verticalScrollbar(scrollState) - .verticalScroll(scrollState) - .padding(horizontal = verticalPadding) - .padding(top = verticalPadding * 1.5f) - } else { - Modifier - .fillMaxSize() - .padding(padding) - .verticalScrollbar(scrollState) - .verticalScroll(scrollState) - .padding(horizontal = verticalPadding) - } IncomingRequestScreen( horizontalPadding = verticalPadding, - modifier = modifier, + modifier = Modifier + .fillMaxSize() + .padding(padding) + .verticalScrollbar(scrollState) + .verticalScroll(scrollState) + .padding(horizontal = verticalPadding), scaffoldPadding = padding, intents = intents, bunkerRequests = bunkerRequests, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt index 399700fd..fbcb3051 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt @@ -78,11 +78,11 @@ fun BunkerMultiEventHomeScreen( onLoading: (Boolean) -> Unit, ) { val context = LocalContext.current - val hasRelayAuthEvents = bunkerRequests.any { it.request is BunkerRequestSign && it.request.event.kind == 22242 } var localAccount by remember { mutableStateOf("") } val key = bunkerRequests.first().localKey - var rememberType by remember { mutableStateOf(RememberType.NEVER) } - var relayAuthScope by remember { mutableStateOf(RelayAuthScope.SPECIFIC) } + val groupRememberTypes = remember { mutableStateMapOf() } + val groupRelayAuthScopes = remember { mutableStateMapOf() } + val groupDecryptScopes = remember { mutableStateMapOf() } var appName by remember { mutableStateOf(ApplicationNameCache["$localAccount-$key"] ?: key.toShortenHex()) } var appIcon by remember { mutableStateOf(bunkerRequests.first().clientMetadata?.image ?: "") } @@ -190,6 +190,19 @@ fun BunkerMultiEventHomeScreen( ) } } + if (groupKey.hasGroupOptions()) { + item(key = "group-options:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") { + RequestGroupOptions( + groupKey = groupKey, + rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER, + onRememberTypeChanged = { groupRememberTypes[groupKey] = it }, + relayAuthScope = groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC, + onRelayAuthScopeChanged = { groupRelayAuthScopes[groupKey] = it }, + decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(groupKey.type), + onDecryptTypeScopeChanged = { groupDecryptScopes[groupKey] = it }, + ) + } + } if (expanded) { items(groupItems, key = { it.request.id }) { bunkerRequest -> BunkerRequestCard( @@ -206,41 +219,6 @@ fun BunkerMultiEventHomeScreen( } } - if (hasRelayAuthEvents) { - LabeledBorderBox( - label = stringResource(R.string.relay_auth_scope), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 4.dp, vertical = 8.dp), - ) { - AmberToggles( - selected = relayAuthScope, - options = listOf(RelayAuthScope.SPECIFIC, RelayAuthScope.ALL), - onSelected = { relayAuthScope = it }, - label = { - stringResource( - when (it) { - RelayAuthScope.SPECIFIC -> R.string.for_this_relay_only - RelayAuthScope.ALL -> R.string.for_all_relays - }, - ) - }, - ) - } - } - - RememberMyChoice( - alwaysShow = true, - shouldRunAcceptOrReject = null, - onAccept = {}, - onReject = {}, - onChanged = { - rememberType = it - MultiEventScreenIntents.rememberType = it - }, - packageName = packageName, - ) - Row( Modifier .fillMaxWidth() @@ -297,10 +275,24 @@ fun BunkerMultiEventHomeScreen( ) val isChecked = MultiEventScreenIntents.checkedStates[request.request.id] ?: true + val requestType = BunkerRequestUtils.getTypeFromBunker(request.request) + val groupKey = requestGroupKey( + type = requestType, + eventKind = (request.request as? BunkerRequestSign)?.event?.kind, + encryptedData = request.encryptedData, + nip44v3Kind = BunkerRequestUtils.getNip44v3Kind(request.request), + ) + val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER if (rememberType != RememberType.NEVER && isChecked) { - val rejectKind = if (request.request is BunkerRequestSign) request.request.event.kind else null + val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(requestType) + val rejectKind = when { + request.request is BunkerRequestSign -> request.request.event.kind + requestType == SignerType.NIP44_V3_ENCRYPT || requestType == SignerType.NIP44_V3_DECRYPT -> + if (decryptTypeScope == DecryptTypeScope.SPECIFIC) BunkerRequestUtils.getNip44v3Kind(request.request) else null + else -> null + } val rejectRelay = if (request.request is BunkerRequestSign && request.request.event.kind == 22242) { - if (relayAuthScope == RelayAuthScope.ALL) { + if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) { "*" } else { RelayUrlUtils.extractHostAndPort(AmberEvent.relay(request.request.event)) @@ -311,13 +303,14 @@ fun BunkerMultiEventHomeScreen( AmberUtils.acceptOrRejectPermission( application, localKey, - BunkerRequestUtils.getTypeFromBunker(request.request), + requestType, rejectKind, false, rememberType, thisAccount, relay = rejectRelay, encryptedData = request.encryptedData, + decryptTypeScope = decryptTypeScope, ) } @@ -389,13 +382,21 @@ fun BunkerMultiEventHomeScreen( ) val isChecked = MultiEventScreenIntents.checkedStates[request.request.id] ?: true + val requestType = BunkerRequestUtils.getTypeFromBunker(request.request) + val groupKey = requestGroupKey( + type = requestType, + eventKind = (request.request as? BunkerRequestSign)?.event?.kind, + encryptedData = request.encryptedData, + nip44v3Kind = BunkerRequestUtils.getNip44v3Kind(request.request), + ) + val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER if (request.request is BunkerRequestSign) { val localEvent = request.signedEvent!! if (rememberType != RememberType.NEVER && isChecked) { val signRelay = if (localEvent.kind == 22242) { - if (relayAuthScope == RelayAuthScope.ALL) { + if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) { "*" } else { RelayUrlUtils.extractHostAndPort(AmberEvent.relay(localEvent)) @@ -497,17 +498,24 @@ fun BunkerMultiEventHomeScreen( } } } else { - val type = BunkerRequestUtils.getTypeFromBunker(request.request) + val type = requestType if (rememberType != RememberType.NEVER && isChecked) { + val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(type) + val permissionKind = if (type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) { + if (decryptTypeScope == DecryptTypeScope.SPECIFIC) BunkerRequestUtils.getNip44v3Kind(request.request) else null + } else { + null + } AmberUtils.acceptOrRejectPermission( application, localKey, type, - null, + permissionKind, true, rememberType, thisAccount, encryptedData = request.encryptedData, + decryptTypeScope = decryptTypeScope, ) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt index 132ac5c0..bb914703 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt @@ -75,9 +75,9 @@ fun IntentMultiEventHomeScreen( onLoading: (Boolean) -> Unit, ) { val context = LocalContext.current - val hasRelayAuthEvents = intents.any { it.type == SignerType.SIGN_EVENT && it.event?.kind == 22242 } - var rememberType by remember { mutableStateOf(RememberType.NEVER) } - var relayAuthScope by remember { mutableStateOf(RelayAuthScope.SPECIFIC) } + val groupRememberTypes = remember { mutableStateMapOf() } + val groupRelayAuthScopes = remember { mutableStateMapOf() } + val groupDecryptScopes = remember { mutableStateMapOf() } LaunchedEffect(Unit) { MultiEventScreenIntents.checkedStates.clear() @@ -156,6 +156,19 @@ fun IntentMultiEventHomeScreen( ) } } + if (groupKey.hasGroupOptions()) { + item(key = "group-options:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") { + RequestGroupOptions( + groupKey = groupKey, + rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER, + onRememberTypeChanged = { groupRememberTypes[groupKey] = it }, + relayAuthScope = groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC, + onRelayAuthScopeChanged = { groupRelayAuthScopes[groupKey] = it }, + decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(groupKey.type), + onDecryptTypeScopeChanged = { groupDecryptScopes[groupKey] = it }, + ) + } + } if (expanded) { items(groupIntents, key = { it.id }) { intent -> IntentRequestCard( @@ -172,41 +185,6 @@ fun IntentMultiEventHomeScreen( } } - if (hasRelayAuthEvents) { - LabeledBorderBox( - label = stringResource(R.string.relay_auth_scope), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 4.dp, vertical = 8.dp), - ) { - AmberToggles( - selected = relayAuthScope, - options = listOf(RelayAuthScope.SPECIFIC, RelayAuthScope.ALL), - onSelected = { relayAuthScope = it }, - label = { - stringResource( - when (it) { - RelayAuthScope.SPECIFIC -> R.string.for_this_relay_only - RelayAuthScope.ALL -> R.string.for_all_relays - }, - ) - }, - ) - } - } - - RememberMyChoice( - alwaysShow = true, - shouldRunAcceptOrReject = null, - onAccept = {}, - onReject = {}, - onChanged = { - rememberType = it - MultiEventScreenIntents.rememberType = it - }, - packageName = packageName, - ) - Row( Modifier .fillMaxWidth() @@ -260,10 +238,18 @@ fun IntentMultiEventHomeScreen( var permissionsChanged = false for (intentData in accountIntents) { val isChecked = MultiEventScreenIntents.checkedStates[intentData.id] ?: true + val groupKey = requestGroupKey(intentData.type, intentData.event?.kind, intentData.encryptedData, intentData.nip44v3Kind) + val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER if (rememberType != RememberType.NEVER && isChecked) { - val rejectKind = if (intentData.type == SignerType.SIGN_EVENT) intentData.event?.kind else null + val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(intentData.type) + val rejectKind = when { + intentData.type == SignerType.SIGN_EVENT -> intentData.event?.kind + intentData.type == SignerType.NIP44_V3_ENCRYPT || intentData.type == SignerType.NIP44_V3_DECRYPT -> + if (decryptTypeScope == DecryptTypeScope.SPECIFIC) intentData.nip44v3Kind else null + else -> null + } val rejectRelay = if (intentData.type == SignerType.SIGN_EVENT && intentData.event?.kind == 22242) { - if (relayAuthScope == RelayAuthScope.ALL) { + if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) { "*" } else { RelayUrlUtils.extractHostAndPort(AmberEvent.relay(intentData.event)) @@ -280,6 +266,7 @@ fun IntentMultiEventHomeScreen( rememberType, relay = rejectRelay, encryptedData = intentData.encryptedData, + decryptTypeScope = decryptTypeScope, ) permissionsChanged = true } @@ -359,13 +346,15 @@ fun IntentMultiEventHomeScreen( for (intentData in accountIntents) { val isChecked = MultiEventScreenIntents.checkedStates[intentData.id] ?: true val type = intentData.type + val groupKey = requestGroupKey(type, intentData.event?.kind, intentData.encryptedData, intentData.nip44v3Kind) + val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER if (type == SignerType.SIGN_EVENT) { val localEvent = intentData.event!! if (rememberType != RememberType.NEVER && isChecked) { val signRelay = if (localEvent.kind == 22242) { - if (relayAuthScope == RelayAuthScope.ALL) { + if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) { "*" } else { RelayUrlUtils.extractHostAndPort(AmberEvent.relay(localEvent)) @@ -420,14 +409,21 @@ fun IntentMultiEventHomeScreen( } } else { if (rememberType != RememberType.NEVER && isChecked) { + val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(type) + val permissionKind = if (type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) { + if (decryptTypeScope == DecryptTypeScope.SPECIFIC) intentData.nip44v3Kind else null + } else { + null + } AmberUtils.updatePermission( application, localKey, type, - null, + permissionKind, true, rememberType, encryptedData = intentData.encryptedData, + decryptTypeScope = decryptTypeScope, ) permissionsChanged = true } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt index d1576025..d5b120f6 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt @@ -59,6 +59,23 @@ fun LabeledBorderBox( } } +@Composable +fun RememberMyChoiceToggles( + selected: RememberType, + onSelected: (RememberType) -> Unit, +) { + LabeledBorderBox( + label = stringResource(R.string.automatically_sign_this_for), + ) { + AmberToggles( + selected = selected, + options = rememberTypeDisplayOrder, + onSelected = onSelected, + label = { stringResource(it.shortResourceId) }, + ) + } +} + @Composable fun RememberMyChoice( shouldRunAcceptOrReject: Boolean?, @@ -79,19 +96,13 @@ fun RememberMyChoice( } } if (packageName != null || alwaysShow) { - LabeledBorderBox( - label = stringResource(R.string.automatically_sign_this_for), - ) { - AmberToggles( - selected = selected, - options = rememberTypeDisplayOrder, - onSelected = { - selected = it - onChanged(it) - }, - label = { stringResource(it.shortResourceId) }, - ) - } + RememberMyChoiceToggles( + selected = selected, + onSelected = { + selected = it + onChanged(it) + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtils.kt index 71171be3..16f06fce 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtils.kt @@ -3,6 +3,7 @@ package com.greenart7c3.nostrsigner.ui.components import android.content.Context import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -17,6 +18,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate +import androidx.compose.ui.res.stringResource import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp import com.greenart7c3.nostrsigner.R @@ -28,6 +30,7 @@ import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind import com.greenart7c3.nostrsigner.models.encryptDecryptSignerTypes +import com.greenart7c3.nostrsigner.ui.RememberType enum class RequestPayloadShape { EVENT, @@ -109,6 +112,124 @@ fun RequestGroupKey.toLabel(context: Context): String { } } +enum class RequestGroupScopeKind { + NONE, + RELAY_AUTH, + ENCRYPTION_METHOD, + NIP44_V3_KIND, +} + +fun RequestGroupKey.scopeKind(): RequestGroupScopeKind = when { + type == SignerType.SIGN_EVENT && kind == 22242 -> RequestGroupScopeKind.RELAY_AUTH + type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT -> RequestGroupScopeKind.NIP44_V3_KIND + type in encryptDecryptSignerTypes && type != SignerType.DECRYPT_ZAP_EVENT -> RequestGroupScopeKind.ENCRYPTION_METHOD + else -> RequestGroupScopeKind.NONE +} + +// Mirrors the single-event defaults: EncryptDecryptData defaults to ALL, Nip44v3ApprovalData to SPECIFIC +fun defaultDecryptTypeScope(type: SignerType): DecryptTypeScope = if (type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) { + DecryptTypeScope.SPECIFIC +} else { + DecryptTypeScope.ALL +} + +// CONNECT / GET_PUBLIC_KEY approvals never persist a permission from the grouped flow +fun RequestGroupKey.hasGroupOptions(): Boolean = type != SignerType.CONNECT && type != SignerType.GET_PUBLIC_KEY + +@Composable +fun RequestGroupOptions( + groupKey: RequestGroupKey, + rememberType: RememberType, + onRememberTypeChanged: (RememberType) -> Unit, + relayAuthScope: RelayAuthScope, + onRelayAuthScopeChanged: (RelayAuthScope) -> Unit, + decryptTypeScope: DecryptTypeScope, + onDecryptTypeScopeChanged: (DecryptTypeScope) -> Unit, +) { + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 4.dp), + ) { + when (groupKey.scopeKind()) { + RequestGroupScopeKind.RELAY_AUTH -> { + LabeledBorderBox( + label = stringResource(R.string.relay_auth_scope), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + ) { + AmberToggles( + selected = relayAuthScope, + options = listOf(RelayAuthScope.SPECIFIC, RelayAuthScope.ALL), + onSelected = onRelayAuthScopeChanged, + label = { + stringResource( + when (it) { + RelayAuthScope.SPECIFIC -> R.string.for_this_relay_only + RelayAuthScope.ALL -> R.string.for_all_relays + }, + ) + }, + ) + } + } + RequestGroupScopeKind.ENCRYPTION_METHOD -> { + LabeledBorderBox( + label = stringResource(R.string.encryption_scope), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + ) { + AmberToggles( + selected = decryptTypeScope, + options = listOf(DecryptTypeScope.SPECIFIC, DecryptTypeScope.ALL), + onSelected = onDecryptTypeScopeChanged, + label = { + stringResource( + when (it) { + DecryptTypeScope.SPECIFIC -> R.string.for_this_method_only + DecryptTypeScope.ALL -> R.string.for_all_methods + }, + ) + }, + ) + } + } + RequestGroupScopeKind.NIP44_V3_KIND -> { + if (groupKey.kind != null) { + LabeledBorderBox( + label = stringResource(R.string.encryption_scope), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + ) { + AmberToggles( + selected = decryptTypeScope, + options = listOf(DecryptTypeScope.SPECIFIC, DecryptTypeScope.ALL), + onSelected = onDecryptTypeScopeChanged, + label = { + stringResource( + when (it) { + DecryptTypeScope.SPECIFIC -> R.string.for_this_kind_only + DecryptTypeScope.ALL -> R.string.for_all_kinds + }, + ) + }, + ) + } + } + } + RequestGroupScopeKind.NONE -> {} + } + + RememberMyChoiceToggles( + selected = rememberType, + onSelected = onRememberTypeChanged, + ) + } +} + @Composable fun RequestGroupHeader( label: String, diff --git a/app/src/test/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtilsTest.kt b/app/src/test/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtilsTest.kt index a40f10e1..ed524b4e 100644 --- a/app/src/test/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtilsTest.kt +++ b/app/src/test/java/com/greenart7c3/nostrsigner/ui/components/RequestGroupUtilsTest.kt @@ -99,6 +99,35 @@ class RequestGroupUtilsTest { ) } + @Test + fun `scopeKind maps relay auth encryption and v3 groups`() { + assertEquals(RequestGroupScopeKind.RELAY_AUTH, RequestGroupKey(SignerType.SIGN_EVENT, 22242, null).scopeKind()) + assertEquals(RequestGroupScopeKind.NONE, RequestGroupKey(SignerType.SIGN_EVENT, 1, null).scopeKind()) + assertEquals(RequestGroupScopeKind.ENCRYPTION_METHOD, RequestGroupKey(SignerType.NIP04_ENCRYPT, null, RequestPayloadShape.CLEAR_TEXT).scopeKind()) + assertEquals(RequestGroupScopeKind.ENCRYPTION_METHOD, RequestGroupKey(SignerType.NIP44_DECRYPT, 4, RequestPayloadShape.EVENT).scopeKind()) + assertEquals(RequestGroupScopeKind.NIP44_V3_KIND, RequestGroupKey(SignerType.NIP44_V3_ENCRYPT, 9, null).scopeKind()) + assertEquals(RequestGroupScopeKind.NIP44_V3_KIND, RequestGroupKey(SignerType.NIP44_V3_DECRYPT, null, null).scopeKind()) + assertEquals(RequestGroupScopeKind.NONE, RequestGroupKey(SignerType.DECRYPT_ZAP_EVENT, null, RequestPayloadShape.PRIVATE_ZAP).scopeKind()) + assertEquals(RequestGroupScopeKind.NONE, RequestGroupKey(SignerType.CONNECT, null, null).scopeKind()) + } + + @Test + fun `defaultDecryptTypeScope is SPECIFIC for v3 and ALL otherwise`() { + assertEquals(DecryptTypeScope.SPECIFIC, defaultDecryptTypeScope(SignerType.NIP44_V3_ENCRYPT)) + assertEquals(DecryptTypeScope.SPECIFIC, defaultDecryptTypeScope(SignerType.NIP44_V3_DECRYPT)) + assertEquals(DecryptTypeScope.ALL, defaultDecryptTypeScope(SignerType.NIP04_DECRYPT)) + assertEquals(DecryptTypeScope.ALL, defaultDecryptTypeScope(SignerType.NIP44_ENCRYPT)) + assertEquals(DecryptTypeScope.ALL, defaultDecryptTypeScope(SignerType.DECRYPT_ZAP_EVENT)) + } + + @Test + fun `hasGroupOptions excludes connect and get public key`() { + assertEquals(false, RequestGroupKey(SignerType.CONNECT, null, null).hasGroupOptions()) + assertEquals(false, RequestGroupKey(SignerType.GET_PUBLIC_KEY, null, null).hasGroupOptions()) + assertEquals(true, RequestGroupKey(SignerType.SIGN_EVENT, 1, null).hasGroupOptions()) + assertEquals(true, RequestGroupKey(SignerType.NIP44_DECRYPT, null, RequestPayloadShape.CLEAR_TEXT).hasGroupOptions()) + } + @Test fun `groupRequests preserves input order within a group`() { data class Item(val id: String, val kind: Int)