create a RemoteSignerManager
This commit is contained in:
+201
-174
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip46RemoteSigner.signer
|
|||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
import com.vitorpamplona.quartz.nip01Core.crypto.verify
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.req
|
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.req
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
@@ -38,16 +38,15 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Decrypt
|
|||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Encrypt
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Encrypt
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestPing
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestPing
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseDecrypt
|
||||||
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseEncrypt
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseEvent
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseEvent
|
||||||
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponsePong
|
||||||
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponsePublicKey
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
|
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||||
import com.vitorpamplona.quartz.utils.Hex
|
import com.vitorpamplona.quartz.utils.Hex
|
||||||
import com.vitorpamplona.quartz.utils.cache.LargeCache
|
|
||||||
import com.vitorpamplona.quartz.utils.tryAndWait
|
|
||||||
import kotlin.coroutines.Continuation
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
class NostrSignerRemote(
|
class NostrSignerRemote(
|
||||||
val signer: NostrSignerInternal,
|
val signer: NostrSignerInternal,
|
||||||
@@ -57,8 +56,13 @@ class NostrSignerRemote(
|
|||||||
val permissions: String? = null,
|
val permissions: String? = null,
|
||||||
val secret: String? = null,
|
val secret: String? = null,
|
||||||
) : NostrSigner(signer.pubKey) {
|
) : NostrSigner(signer.pubKey) {
|
||||||
private val timeout = 30_000L
|
private val manager =
|
||||||
private val awaitingRequests = LargeCache<String, Continuation<BunkerResponse>>()
|
RemoteSignerManager(
|
||||||
|
signer = signer,
|
||||||
|
remoteKey = remotePubkey,
|
||||||
|
relayList = relays,
|
||||||
|
client = client,
|
||||||
|
)
|
||||||
|
|
||||||
val subscription =
|
val subscription =
|
||||||
client.req(
|
client.req(
|
||||||
@@ -69,11 +73,7 @@ class NostrSignerRemote(
|
|||||||
tags = mapOf("p" to listOf(signer.pubKey)),
|
tags = mapOf("p" to listOf(signer.pubKey)),
|
||||||
),
|
),
|
||||||
) { event ->
|
) { event ->
|
||||||
val message = signer.signerSync.nip44Decrypt(event.content, remotePubkey)
|
manager.newResponse(event)
|
||||||
val bunkerResponse = OptimizedJsonMapper.fromJsonTo<BunkerResponse>(message)
|
|
||||||
|
|
||||||
awaitingRequests.get(bunkerResponse.id)?.resume(bunkerResponse)
|
|
||||||
awaitingRequests.remove(bunkerResponse.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openSubscription() {
|
fun openSubscription() {
|
||||||
@@ -92,224 +92,251 @@ class NostrSignerRemote(
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
): T {
|
): T {
|
||||||
val template =
|
|
||||||
EventTemplate<Event>(
|
|
||||||
createdAt = createdAt,
|
|
||||||
kind = kind,
|
|
||||||
tags = tags,
|
|
||||||
content = content,
|
|
||||||
)
|
|
||||||
|
|
||||||
val request =
|
|
||||||
BunkerRequestSign(
|
|
||||||
event = template,
|
|
||||||
)
|
|
||||||
|
|
||||||
val event =
|
|
||||||
NostrConnectEvent.create(
|
|
||||||
message = request,
|
|
||||||
remoteKey = remotePubkey,
|
|
||||||
signer = signer,
|
|
||||||
)
|
|
||||||
|
|
||||||
val result =
|
val result =
|
||||||
tryAndWait(timeout) { continuation ->
|
manager.launchWaitAndParse(
|
||||||
continuation.invokeOnCancellation {
|
bunkerRequestBuilder = {
|
||||||
awaitingRequests.remove(request.id)
|
val template =
|
||||||
}
|
EventTemplate<Event>(
|
||||||
|
createdAt = createdAt,
|
||||||
|
kind = kind,
|
||||||
|
tags = tags,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
BunkerRequestSign(
|
||||||
|
event = template,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
parser = { response ->
|
||||||
|
if (response is BunkerResponseEvent) {
|
||||||
|
if (!response.event.verify()) {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotVerifyResultingEvent(response.event)
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Successful(SignResult(response.event))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
if (result is SignerResult.RequestAddressed.Successful<SignResult>) {
|
||||||
|
(result.result.event as? T)?.let {
|
||||||
|
return it
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (result as BunkerResponseEvent).event as T
|
throw Exception("Could not sign")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun nip04Encrypt(
|
override suspend fun nip04Encrypt(
|
||||||
plaintext: String,
|
plaintext: String,
|
||||||
toPublicKey: HexKey,
|
toPublicKey: HexKey,
|
||||||
): String {
|
): String {
|
||||||
val request =
|
|
||||||
BunkerRequestNip04Encrypt(
|
|
||||||
message = plaintext,
|
|
||||||
pubKey = toPublicKey,
|
|
||||||
)
|
|
||||||
val event =
|
|
||||||
NostrConnectEvent.create(
|
|
||||||
message = request,
|
|
||||||
remoteKey = remotePubkey,
|
|
||||||
signer = signer,
|
|
||||||
)
|
|
||||||
val result =
|
val result =
|
||||||
tryAndWait(timeout) { continuation ->
|
manager.launchWaitAndParse(
|
||||||
continuation.invokeOnCancellation {
|
bunkerRequestBuilder = {
|
||||||
awaitingRequests.remove(request.id)
|
BunkerRequestNip04Encrypt(
|
||||||
}
|
message = plaintext,
|
||||||
|
pubKey = toPublicKey,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
parser = { response ->
|
||||||
|
if (response is BunkerResponseEncrypt) {
|
||||||
|
if (response.error != null) {
|
||||||
|
SignerResult.RequestAddressed.Rejected()
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Successful(EncryptionResult(response.ciphertext))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
if (result is SignerResult.RequestAddressed.Successful<EncryptionResult>) {
|
||||||
|
return result.result.ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
throw Exception("Could not encrypt")
|
||||||
}
|
|
||||||
return result?.result ?: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun nip04Decrypt(
|
override suspend fun nip04Decrypt(
|
||||||
ciphertext: String,
|
ciphertext: String,
|
||||||
fromPublicKey: HexKey,
|
fromPublicKey: HexKey,
|
||||||
): String {
|
): String {
|
||||||
val request =
|
|
||||||
BunkerRequestNip04Decrypt(
|
|
||||||
ciphertext = ciphertext,
|
|
||||||
pubKey = fromPublicKey,
|
|
||||||
)
|
|
||||||
val event =
|
|
||||||
NostrConnectEvent.create(
|
|
||||||
message = request,
|
|
||||||
remoteKey = remotePubkey,
|
|
||||||
signer = signer,
|
|
||||||
)
|
|
||||||
val result =
|
val result =
|
||||||
tryAndWait(timeout) { continuation ->
|
manager.launchWaitAndParse(
|
||||||
continuation.invokeOnCancellation {
|
bunkerRequestBuilder = {
|
||||||
awaitingRequests.remove(request.id)
|
BunkerRequestNip04Decrypt(
|
||||||
}
|
ciphertext = ciphertext,
|
||||||
|
pubKey = fromPublicKey,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
parser = { response ->
|
||||||
|
if (response is BunkerResponseDecrypt) {
|
||||||
|
if (response.error != null) {
|
||||||
|
SignerResult.RequestAddressed.Rejected()
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Successful(DecryptionResult(response.plaintext))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
if (result is SignerResult.RequestAddressed.Successful<DecryptionResult>) {
|
||||||
|
return result.result.plaintext
|
||||||
|
}
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
throw Exception("Could not decrypt")
|
||||||
}
|
|
||||||
return result?.result ?: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun nip44Encrypt(
|
override suspend fun nip44Encrypt(
|
||||||
plaintext: String,
|
plaintext: String,
|
||||||
toPublicKey: HexKey,
|
toPublicKey: HexKey,
|
||||||
): String {
|
): String {
|
||||||
val request =
|
|
||||||
BunkerRequestNip44Encrypt(
|
|
||||||
message = plaintext,
|
|
||||||
pubKey = toPublicKey,
|
|
||||||
)
|
|
||||||
val event =
|
|
||||||
NostrConnectEvent.create(
|
|
||||||
message = request,
|
|
||||||
remoteKey = remotePubkey,
|
|
||||||
signer = signer,
|
|
||||||
)
|
|
||||||
val result =
|
val result =
|
||||||
tryAndWait(timeout) { continuation ->
|
manager.launchWaitAndParse(
|
||||||
continuation.invokeOnCancellation {
|
bunkerRequestBuilder = {
|
||||||
awaitingRequests.remove(request.id)
|
BunkerRequestNip44Encrypt(
|
||||||
}
|
message = plaintext,
|
||||||
|
pubKey = toPublicKey,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
parser = { response ->
|
||||||
|
if (response is BunkerResponseEncrypt) {
|
||||||
|
if (response.error != null) {
|
||||||
|
SignerResult.RequestAddressed.Rejected()
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Successful(EncryptionResult(response.ciphertext))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
if (result is SignerResult.RequestAddressed.Successful<EncryptionResult>) {
|
||||||
|
return result.result.ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
throw Exception("Could not encrypt")
|
||||||
}
|
|
||||||
return result?.result ?: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun nip44Decrypt(
|
override suspend fun nip44Decrypt(
|
||||||
ciphertext: String,
|
ciphertext: String,
|
||||||
fromPublicKey: HexKey,
|
fromPublicKey: HexKey,
|
||||||
): String {
|
): String {
|
||||||
val request =
|
|
||||||
BunkerRequestNip44Decrypt(
|
|
||||||
ciphertext = ciphertext,
|
|
||||||
pubKey = fromPublicKey,
|
|
||||||
)
|
|
||||||
val event =
|
|
||||||
NostrConnectEvent.create(
|
|
||||||
message = request,
|
|
||||||
remoteKey = remotePubkey,
|
|
||||||
signer = signer,
|
|
||||||
)
|
|
||||||
val result =
|
val result =
|
||||||
tryAndWait(timeout) { continuation ->
|
manager.launchWaitAndParse(
|
||||||
continuation.invokeOnCancellation {
|
bunkerRequestBuilder = {
|
||||||
awaitingRequests.remove(request.id)
|
BunkerRequestNip44Decrypt(
|
||||||
}
|
ciphertext = ciphertext,
|
||||||
|
pubKey = fromPublicKey,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
parser = { response ->
|
||||||
|
if (response is BunkerResponseDecrypt) {
|
||||||
|
if (response.error != null) {
|
||||||
|
SignerResult.RequestAddressed.Rejected()
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Successful(DecryptionResult(response.plaintext))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
if (result is SignerResult.RequestAddressed.Successful<DecryptionResult>) {
|
||||||
|
return result.result.plaintext
|
||||||
|
}
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
throw Exception("Could not decrypt")
|
||||||
}
|
|
||||||
return result?.result ?: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun ping(): String? {
|
suspend fun ping(): String? {
|
||||||
val request = BunkerRequestPing()
|
val result =
|
||||||
val event =
|
manager.launchWaitAndParse(
|
||||||
NostrConnectEvent.create(
|
bunkerRequestBuilder = {
|
||||||
message = request,
|
BunkerRequestPing()
|
||||||
remoteKey = remotePubkey,
|
},
|
||||||
signer = signer,
|
parser = { response ->
|
||||||
|
if (response is BunkerResponsePong) {
|
||||||
|
if (response.result != null) {
|
||||||
|
SignerResult.RequestAddressed.Successful(PingResult(response.result))
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Rejected()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val result =
|
if (result is SignerResult.RequestAddressed.Successful<PingResult>) {
|
||||||
tryAndWait(timeout) { continuation ->
|
return result.result.pong
|
||||||
continuation.invokeOnCancellation {
|
}
|
||||||
awaitingRequests.remove(request.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
throw Exception("Could not ping")
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result?.result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun connect(): HexKey? {
|
suspend fun connect(): HexKey {
|
||||||
val request =
|
|
||||||
BunkerRequestConnect(
|
|
||||||
remoteKey = remotePubkey,
|
|
||||||
permissions = permissions,
|
|
||||||
secret = secret,
|
|
||||||
)
|
|
||||||
val event =
|
|
||||||
NostrConnectEvent.create(
|
|
||||||
message = request,
|
|
||||||
remoteKey = remotePubkey,
|
|
||||||
signer = signer,
|
|
||||||
)
|
|
||||||
|
|
||||||
val result =
|
val result =
|
||||||
tryAndWait(timeout) { continuation ->
|
manager.launchWaitAndParse(
|
||||||
continuation.invokeOnCancellation {
|
bunkerRequestBuilder = {
|
||||||
awaitingRequests.remove(request.id)
|
BunkerRequestConnect(
|
||||||
}
|
remoteKey = remotePubkey,
|
||||||
|
permissions = permissions,
|
||||||
|
secret = secret,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
parser = { response ->
|
||||||
|
if (response is BunkerResponsePublicKey) {
|
||||||
|
if (response.result != null) {
|
||||||
|
SignerResult.RequestAddressed.Successful(PublicKeyResult(response.result))
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Rejected()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
if (result is SignerResult.RequestAddressed.Successful<PublicKeyResult>) {
|
||||||
|
return result.result.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
throw Exception("Could not connect")
|
||||||
}
|
|
||||||
|
|
||||||
return result?.result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getPublicKey(): HexKey? {
|
suspend fun getPublicKey(): HexKey {
|
||||||
val request = BunkerRequestGetPublicKey()
|
val result =
|
||||||
val event =
|
manager.launchWaitAndParse(
|
||||||
NostrConnectEvent.create(
|
bunkerRequestBuilder = {
|
||||||
message = request,
|
BunkerRequestGetPublicKey()
|
||||||
remoteKey = remotePubkey,
|
},
|
||||||
signer = signer,
|
parser = { response ->
|
||||||
|
if (response is BunkerResponsePublicKey) {
|
||||||
|
if (response.result != null) {
|
||||||
|
SignerResult.RequestAddressed.Successful(PublicKeyResult(response.result))
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.Rejected()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignerResult.RequestAddressed.ReceivedButCouldNotPerform()
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val result =
|
if (result is SignerResult.RequestAddressed.Successful<PublicKeyResult>) {
|
||||||
tryAndWait(timeout) { continuation ->
|
return result.result.pubkey
|
||||||
continuation.invokeOnCancellation {
|
}
|
||||||
awaitingRequests.remove(request.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
awaitingRequests.put(request.id, continuation)
|
throw Exception("Could not get public key")
|
||||||
|
|
||||||
client.send(event, relayList = relays)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result?.result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun decryptZapEvent(event: LnZapRequestEvent): LnZapPrivateEvent {
|
override suspend fun decryptZapEvent(event: LnZapRequestEvent): LnZapPrivateEvent {
|
||||||
|
|||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip46RemoteSigner.signer
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||||
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||||
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||||
|
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.cache.LargeCache
|
||||||
|
import com.vitorpamplona.quartz.utils.tryAndWait
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
class RemoteSignerManager(
|
||||||
|
val timeout: Long = 30000,
|
||||||
|
val client: INostrClient,
|
||||||
|
val signer: NostrSignerInternal,
|
||||||
|
val remoteKey: String,
|
||||||
|
val relayList: Set<NormalizedRelayUrl>,
|
||||||
|
) {
|
||||||
|
private val awaitingRequests = LargeCache<String, Continuation<BunkerResponse>>()
|
||||||
|
|
||||||
|
fun newResponse(responseEvent: Event) {
|
||||||
|
val bunkerResponse = OptimizedJsonMapper.fromJsonTo<BunkerResponse>(responseEvent.content)
|
||||||
|
awaitingRequests.get(bunkerResponse.id)?.resume(bunkerResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the signer, waits and parses the result
|
||||||
|
*
|
||||||
|
* @param bunkerRequestBuilder The BunkerRequest to be sent.
|
||||||
|
* @param parser A function that parses the BunkerResponse into a [SignerResult.RequestAddressed<T>].
|
||||||
|
* @return The result after parsing the BunkerResponse using the provided parser.
|
||||||
|
*
|
||||||
|
* This function uses the [tryAndWait] utility to implement a timeout on the Bunker Request approval.
|
||||||
|
* It assigns a unique ID to the request and keeps a continuation to resume once the result is received.
|
||||||
|
* If the timeout occurs or the continuation is cancelled, the request ID is cleaned up from [awaitingRequests].
|
||||||
|
*/
|
||||||
|
suspend fun <T : IResult> launchWaitAndParse(
|
||||||
|
bunkerRequestBuilder: () -> BunkerRequest,
|
||||||
|
parser: (response: BunkerResponse) -> SignerResult.RequestAddressed<T>,
|
||||||
|
): SignerResult.RequestAddressed<T> {
|
||||||
|
val request = bunkerRequestBuilder()
|
||||||
|
val event =
|
||||||
|
NostrConnectEvent.create(
|
||||||
|
message = request,
|
||||||
|
remoteKey = remoteKey,
|
||||||
|
signer = signer,
|
||||||
|
)
|
||||||
|
val result =
|
||||||
|
tryAndWait(timeout) { continuation ->
|
||||||
|
continuation.invokeOnCancellation {
|
||||||
|
awaitingRequests.remove(request.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitingRequests.put(request.id, continuation)
|
||||||
|
|
||||||
|
client.send(event, relayList = relayList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (result) {
|
||||||
|
null -> SignerResult.RequestAddressed.TimedOut()
|
||||||
|
else -> parser(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -47,3 +47,23 @@ sealed interface SignerResult<T : IResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IResult
|
interface IResult
|
||||||
|
|
||||||
|
data class SignResult(
|
||||||
|
val event: Event,
|
||||||
|
) : IResult
|
||||||
|
|
||||||
|
data class EncryptionResult(
|
||||||
|
val ciphertext: String,
|
||||||
|
) : IResult
|
||||||
|
|
||||||
|
data class DecryptionResult(
|
||||||
|
val plaintext: String,
|
||||||
|
) : IResult
|
||||||
|
|
||||||
|
data class PingResult(
|
||||||
|
val pong: String,
|
||||||
|
) : IResult
|
||||||
|
|
||||||
|
data class PublicKeyResult(
|
||||||
|
val pubkey: String,
|
||||||
|
) : IResult
|
||||||
|
|||||||
Reference in New Issue
Block a user