feat(federation): presence-signing overlay on discovery enable
Demo images / Build & push demo images (push) Failing after 1m43s

Enabling Nostr discoverability now opens a signer overlay (same visual
language as the app NIP-07 identity picker) before anything is published:
a locked signer row fixed to the node's discovery key — deliberately not
pickable, personal identities are never used — plus the exact signed
content (DID, npub, software version, kind 30078/NIP-33 format) and a
Sign & Publish confirm. Disabling stays immediate. Also seeds the mock
with discovery OFF to match the production default, and defaults
dwnSyncLabel to 'Unknown' when the backend omits sync_status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-22 02:55:06 -04:00
co-authored by Claude Fable 5
parent 72fcf96016
commit b288be314c
3 changed files with 285 additions and 4 deletions
+38 -3
View File
@@ -212,6 +212,17 @@
@close="showDiscoverModal = false"
@sent="loadPendingRequests"
/>
<PresenceSignModal
:show="showPresenceSignModal"
:server-name="appStore.serverName"
:npub="nodeNpub"
:did="selfDid"
:version="appVersion"
:busy="discoveryToggling"
@confirm="confirmPresenceSign"
@cancel="showPresenceSignModal = false"
/>
</div>
</template>
@@ -230,6 +241,7 @@ import NodeDetailModal from './federation/NodeDetailModal.vue'
import JoinModal from './federation/JoinModal.vue'
import PendingRequestsPanel from './federation/PendingRequestsPanel.vue'
import DiscoverModal from './federation/DiscoverModal.vue'
import PresenceSignModal from './federation/PresenceSignModal.vue'
import type { FederatedNode, DwnStatus, SyncResult } from './federation/types'
import type { PendingPeerRequest } from '@/api/rpc-client'
import { nodeName, timeAgo } from './federation/utils'
@@ -327,7 +339,7 @@ const dwnSyncLabel = computed(() => {
case 'synced': return 'Synced'
case 'syncing': return 'Syncing...'
case 'error': return 'Error'
default: return dwnStatus.value.sync_status
default: return dwnStatus.value.sync_status || 'Unknown'
}
})
@@ -348,6 +360,7 @@ const showDiscoverModal = ref(false)
// Signing-details disclosure: who signs the presence event (always the node's
// own discovery key) and a human-readable view of the signed content.
const showSigningInfo = ref(false)
const showPresenceSignModal = ref(false)
const nodeNpub = ref('')
const appVersion = computed(() => syncStore.serverInfo?.version || '')
@@ -378,11 +391,19 @@ async function loadDiscoveryState() {
}
async function toggleDiscovery() {
if (!discoveryEnabled.value) {
// Enabling publishes a signed presence event — surface the signer overlay
// first so the user sees exactly what gets signed and with which key.
// Nothing is published until they confirm.
if (!nodeNpub.value) loadNodeNpub()
showPresenceSignModal.value = true
return
}
// Disabling publishes nothing new — apply immediately.
discoveryToggling.value = true
discoveryError.value = ''
const next = !discoveryEnabled.value
try {
const result = await rpcClient.nostrSetDiscovery(next)
const result = await rpcClient.nostrSetDiscovery(false)
discoveryEnabled.value = !!result.enabled
} catch (e: unknown) {
discoveryError.value = e instanceof Error ? e.message : 'Failed to toggle discoverability'
@@ -391,6 +412,20 @@ async function toggleDiscovery() {
}
}
async function confirmPresenceSign() {
discoveryToggling.value = true
discoveryError.value = ''
try {
const result = await rpcClient.nostrSetDiscovery(true)
discoveryEnabled.value = !!result.enabled
showPresenceSignModal.value = false
} catch (e: unknown) {
discoveryError.value = e instanceof Error ? e.message : 'Failed to enable discoverability'
} finally {
discoveryToggling.value = false
}
}
async function loadPendingRequests() {
try {
const result = await rpcClient.federationListPendingRequests()