fix(quartz): connect() returns Unit, caller calls getPublicKey separately

Per Amber maintainer: connect never returns a pubkey — response is
"ack" or the secret string. Rewrote ConnectResponse.parse() to check
result/error fields directly on base BunkerResponse (matching Jackson
deserialization). connect() is now a pure protocol handshake; callers
call getPublicKey() separately as a domain concern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-03-10 07:24:57 +02:00
parent 39393d328b
commit e56b0d2b83
4 changed files with 35 additions and 56 deletions
@@ -21,33 +21,23 @@
package com.vitorpamplona.quartz.nip46RemoteSigner.signer
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseAck
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseError
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponsePublicKey
class ConnectResponse {
companion object {
fun parse(response: BunkerResponse): SignerResult.RequestAddressed<ConnectResult> =
when (response) {
is BunkerResponsePublicKey -> {
SignerResult.RequestAddressed.Successful(ConnectResult.PubKey(response.pubkey))
}
is BunkerResponseAck -> {
SignerResult.RequestAddressed.Successful(ConnectResult.Ack)
}
is BunkerResponseError -> {
if (response.error?.contains("already connected", ignoreCase = true) == true) {
SignerResult.RequestAddressed.Successful(ConnectResult.AlreadyConnected)
} else {
SignerResult.RequestAddressed.Rejected()
}
}
else -> {
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
fun parse(response: BunkerResponse): SignerResult.RequestAddressed<ConnectResult> {
if (response.error != null) {
return if (response.error.contains("already connected", ignoreCase = true)) {
SignerResult.RequestAddressed.Successful(ConnectResult.AlreadyConnected)
} else {
SignerResult.RequestAddressed.Rejected()
}
}
if (response.result != null) {
return SignerResult.RequestAddressed.Successful(ConnectResult.Ack)
}
return SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
}
}
}
@@ -220,7 +220,7 @@ class NostrSignerRemote(
throw convertExceptions("Could not ping", result)
}
suspend fun connect(): HexKey {
suspend fun connect() {
val result =
manager.launchWaitAndParse(
bunkerRequestBuilder = {
@@ -233,19 +233,11 @@ class NostrSignerRemote(
parser = ConnectResponse::parse,
)
return when (result) {
is SignerResult.RequestAddressed.Successful<ConnectResult> -> {
when (val r = result.result) {
is ConnectResult.PubKey -> r.pubkey
is ConnectResult.Ack -> getPublicKey()
is ConnectResult.AlreadyConnected -> getPublicKey()
}
}
else -> {
throw convertExceptions("Could not connect", result)
}
if (result is SignerResult.RequestAddressed.Successful<ConnectResult>) {
return
}
throw convertExceptions("Could not connect", result)
}
suspend fun getPublicKey(): HexKey {
@@ -69,10 +69,6 @@ data class PublicKeyResult(
) : IResult
sealed interface ConnectResult : IResult {
data class PubKey(
val pubkey: String,
) : ConnectResult
data object Ack : ConnectResult
data object AlreadyConnected : ConnectResult
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip46RemoteSigner.signer
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseAck
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseDecrypt
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseEncrypt
@@ -37,20 +38,20 @@ import kotlin.test.assertIs
class ResponseParserTest {
// --- ConnectResponse ---
@Test
fun connectParsePubKey() {
val hex = "a".repeat(64)
val response = BunkerResponsePublicKey("req-0", hex)
val result = ConnectResponse.parse(response)
assertIs<SignerResult.RequestAddressed.Successful<ConnectResult>>(result)
assertIs<ConnectResult.PubKey>(result.result)
assertEquals(hex, (result.result as ConnectResult.PubKey).pubkey)
}
// connect never returns a pubkey — it's "ack" or the secret string.
// Tests use base BunkerResponse to match production deserialization behavior.
@Test
fun connectParseAck() {
val response = BunkerResponseAck("req-0")
val response = BunkerResponse("req-0", "ack", null)
val result = ConnectResponse.parse(response)
assertIs<SignerResult.RequestAddressed.Successful<ConnectResult>>(result)
assertIs<ConnectResult.Ack>(result.result)
}
@Test
fun connectParseSecret() {
val response = BunkerResponse("req-0", "my-secret-token", null)
val result = ConnectResponse.parse(response)
assertIs<SignerResult.RequestAddressed.Successful<ConnectResult>>(result)
assertIs<ConnectResult.Ack>(result.result)
@@ -58,7 +59,7 @@ class ResponseParserTest {
@Test
fun connectParseAlreadyConnected() {
val response = BunkerResponseError("req-0", "already connected")
val response = BunkerResponse("req-0", null, "already connected")
val result = ConnectResponse.parse(response)
assertIs<SignerResult.RequestAddressed.Successful<ConnectResult>>(result)
assertIs<ConnectResult.AlreadyConnected>(result.result)
@@ -66,7 +67,7 @@ class ResponseParserTest {
@Test
fun connectParseAlreadyConnectedCaseInsensitive() {
val response = BunkerResponseError("req-0", "Already Connected")
val response = BunkerResponse("req-0", null, "Already Connected")
val result = ConnectResponse.parse(response)
assertIs<SignerResult.RequestAddressed.Successful<ConnectResult>>(result)
assertIs<ConnectResult.AlreadyConnected>(result.result)
@@ -74,14 +75,14 @@ class ResponseParserTest {
@Test
fun connectParseRealError() {
val response = BunkerResponseError("req-0", "unauthorized")
val response = BunkerResponse("req-0", null, "unauthorized")
val result = ConnectResponse.parse(response)
assertIs<SignerResult.RequestAddressed.Rejected<ConnectResult>>(result)
}
@Test
fun connectParseUnexpected() {
val response = BunkerResponsePong("req-0")
fun connectParseNoResultNoError() {
val response = BunkerResponse("req-0", null, null)
val result = ConnectResponse.parse(response)
assertIs<SignerResult.RequestAddressed.ReceivedButCouldNotPerform<ConnectResult>>(result)
}