feat(multi-account): extract SignerType, AccountInfo, AccountStorage to commons
Phase 1 of multi-account support. Move account types to commons/commonMain so both Android and Desktop share the same data model: - SignerType sealed class (Internal, Remote, ViewOnly) - AccountInfo data class (npub, signerType, isTransient) - AccountStorage interface (loadAccounts, saveAccount, etc.) Desktop AccountManager now imports SignerType from commons instead of defining its own. All existing tests updated with new import path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.amethyst.commons.model.account
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight account metadata for account listing and switching.
|
||||||
|
* Does NOT contain private keys — those are stored in SecureKeyStorage.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
data class AccountInfo(
|
||||||
|
val npub: String,
|
||||||
|
val signerType: SignerType,
|
||||||
|
val isTransient: Boolean = false,
|
||||||
|
)
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.amethyst.commons.model.account
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform-agnostic interface for persisting account metadata.
|
||||||
|
* Implementations:
|
||||||
|
* - Android: LocalPreferences (EncryptedSharedPreferences)
|
||||||
|
* - Desktop: DesktopAccountStorage (encrypted JSON file)
|
||||||
|
*
|
||||||
|
* Does NOT handle private key storage — that's SecureKeyStorage's job.
|
||||||
|
* Constructor-injected, not expect/actual.
|
||||||
|
*/
|
||||||
|
interface AccountStorage {
|
||||||
|
/** Load all saved account metadata */
|
||||||
|
suspend fun loadAccounts(): List<AccountInfo>
|
||||||
|
|
||||||
|
/** Save or update account metadata */
|
||||||
|
suspend fun saveAccount(info: AccountInfo)
|
||||||
|
|
||||||
|
/** Delete account metadata by npub */
|
||||||
|
suspend fun deleteAccount(npub: String)
|
||||||
|
|
||||||
|
/** Get the npub of the currently active account, or null if none */
|
||||||
|
suspend fun currentAccount(): String?
|
||||||
|
|
||||||
|
/** Set which account is currently active */
|
||||||
|
suspend fun setCurrentAccount(npub: String)
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.amethyst.commons.model.account
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents how an account's signing is handled.
|
||||||
|
* Sealed class (not enum) because Remote carries additional data.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
sealed class SignerType {
|
||||||
|
/** Account has a local private key (nsec) */
|
||||||
|
data object Internal : SignerType()
|
||||||
|
|
||||||
|
/** Account uses a NIP-46 remote signer (bunker) */
|
||||||
|
@Immutable
|
||||||
|
data class Remote(
|
||||||
|
val bunkerUri: String,
|
||||||
|
) : SignerType()
|
||||||
|
|
||||||
|
/** Account is view-only (npub only, no signing capability) */
|
||||||
|
data object ViewOnly : SignerType()
|
||||||
|
}
|
||||||
@@ -670,7 +670,7 @@ fun App(
|
|||||||
val result = accountManager.loadSavedAccount()
|
val result = accountManager.loadSavedAccount()
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
val current = accountManager.currentAccount()
|
val current = accountManager.currentAccount()
|
||||||
if (current?.signerType is com.vitorpamplona.amethyst.desktop.account.SignerType.Remote) {
|
if (current?.signerType is com.vitorpamplona.amethyst.commons.model.account.SignerType.Remote) {
|
||||||
accountManager.startHeartbeat(scope)
|
accountManager.startHeartbeat(scope)
|
||||||
}
|
}
|
||||||
} else if (accountManager.hasBunkerAccount()) {
|
} else if (accountManager.hasBunkerAccount()) {
|
||||||
@@ -702,7 +702,7 @@ fun App(
|
|||||||
onLoginSuccess = {
|
onLoginSuccess = {
|
||||||
// Start heartbeat if bunker account
|
// Start heartbeat if bunker account
|
||||||
val current = accountManager.currentAccount()
|
val current = accountManager.currentAccount()
|
||||||
if (current?.signerType is com.vitorpamplona.amethyst.desktop.account.SignerType.Remote) {
|
if (current?.signerType is com.vitorpamplona.amethyst.commons.model.account.SignerType.Remote) {
|
||||||
accountManager.startHeartbeat(scope)
|
accountManager.startHeartbeat(scope)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-8
@@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.commons.domain.nip46.NostrConnectLoginUseCase
|
|||||||
import com.vitorpamplona.amethyst.commons.domain.nip46.SignerConnectionState
|
import com.vitorpamplona.amethyst.commons.domain.nip46.SignerConnectionState
|
||||||
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
||||||
import com.vitorpamplona.amethyst.commons.keystorage.SecureStorageException
|
import com.vitorpamplona.amethyst.commons.keystorage.SecureStorageException
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.account.SignerType
|
||||||
import com.vitorpamplona.amethyst.desktop.network.DesktopHttpClient
|
import com.vitorpamplona.amethyst.desktop.network.DesktopHttpClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||||
@@ -65,14 +66,6 @@ import java.io.File
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.attribute.PosixFilePermission
|
import java.nio.file.attribute.PosixFilePermission
|
||||||
|
|
||||||
sealed class SignerType {
|
|
||||||
data object Internal : SignerType()
|
|
||||||
|
|
||||||
data class Remote(
|
|
||||||
val bunkerUri: String,
|
|
||||||
) : SignerType()
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class AccountState {
|
sealed class AccountState {
|
||||||
data object LoggedOut : AccountState()
|
data object LoggedOut : AccountState()
|
||||||
|
|
||||||
|
|||||||
+1
@@ -21,6 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.desktop.account
|
package com.vitorpamplona.amethyst.desktop.account
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.account.SignerType
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient
|
||||||
|
|||||||
+1
@@ -21,6 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.desktop.account
|
package com.vitorpamplona.amethyst.desktop.account
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.account.SignerType
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||||
|
|||||||
+1
@@ -21,6 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.desktop.account
|
package com.vitorpamplona.amethyst.desktop.account
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.account.SignerType
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||||
|
|||||||
+1
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.desktop.account
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.domain.nip46.SignerConnectionState
|
import com.vitorpamplona.amethyst.commons.domain.nip46.SignerConnectionState
|
||||||
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
import com.vitorpamplona.amethyst.commons.keystorage.SecureKeyStorage
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.account.SignerType
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||||
|
|||||||
Reference in New Issue
Block a user