feat: deploy-to-target supports .253 + mesh/federation/VPN updates
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Failing after 3m27s
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Failing after 3m27s
- Add deploy_secondary() function for deploying to multiple LAN nodes - --both now deploys to .198 and .253 (previously .198 only) - Fleet deploy updated for 3 LAN nodes - Mesh DM fixes: protocol frame format, DM-via-channel routing - Federation pending requests, discover modal - VPN status UI improvements - Image versions and container specs updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,21 @@ export interface RPCResponse<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors `crate::federation::pending::PendingPeerRequest` on the backend.
|
||||
export type PendingState = 'pending' | 'sent' | 'approved' | 'rejected' | 'expired'
|
||||
|
||||
export interface PendingPeerRequest {
|
||||
id: string
|
||||
from_nostr_pubkey: string
|
||||
from_nostr_npub: string
|
||||
from_did: string
|
||||
from_name: string | null
|
||||
message: string | null
|
||||
received_at: string
|
||||
state: PendingState
|
||||
outbound: boolean
|
||||
}
|
||||
|
||||
function getCsrfToken(): string | null {
|
||||
const match = document.cookie.match(/(?:^|;\s*)csrf_token=([^;]+)/)
|
||||
if (match) return match[1]!
|
||||
@@ -262,7 +277,7 @@ class RPCClient {
|
||||
|
||||
// ─── Node Identity ───────────────────────────────────────────────
|
||||
|
||||
async getNodeDid(): Promise<{ did: string; pubkey: string }> {
|
||||
async getNodeDid(): Promise<{ did: string; pubkey: string; nostr_pubkey?: string; nostr_npub?: string }> {
|
||||
return this.call({
|
||||
method: 'node.did',
|
||||
params: {},
|
||||
@@ -579,6 +594,21 @@ class RPCClient {
|
||||
})
|
||||
}
|
||||
|
||||
async meshContactsList(): Promise<{
|
||||
contacts: Array<{ pubkey: string; alias?: string | null; notes?: string | null; pinned?: boolean; blocked?: boolean }>
|
||||
}> {
|
||||
return this.call({ method: 'mesh.contacts-list', params: {} })
|
||||
}
|
||||
|
||||
async meshContactsSave(
|
||||
pubkey: string,
|
||||
alias?: string | null,
|
||||
): Promise<{ saved: boolean; pubkey: string; alias: string | null }> {
|
||||
const params: Record<string, unknown> = { pubkey }
|
||||
if (alias !== undefined) params.alias = alias
|
||||
return this.call({ method: 'mesh.contacts-save', params })
|
||||
}
|
||||
|
||||
async federationListNodes(): Promise<{
|
||||
nodes: Array<{
|
||||
did: string
|
||||
@@ -590,6 +620,7 @@ class RPCClient {
|
||||
last_seen?: string
|
||||
last_state?: {
|
||||
timestamp: string
|
||||
node_name?: string
|
||||
apps: Array<{ id: string; status: string; version?: string }>
|
||||
cpu_usage_percent?: number
|
||||
mem_used_bytes?: number
|
||||
@@ -598,6 +629,7 @@ class RPCClient {
|
||||
disk_total_bytes?: number
|
||||
uptime_secs?: number
|
||||
tor_active?: boolean
|
||||
nostr_npub?: string
|
||||
}
|
||||
}>
|
||||
}> {
|
||||
@@ -624,6 +656,92 @@ class RPCClient {
|
||||
})
|
||||
}
|
||||
|
||||
// Nostr peer-discovery — see `core/archipelago/src/nostr_handshake.rs`.
|
||||
// None of these methods ever exchange the local onion address on a public
|
||||
// relay. `handshake.discover` returns presence-only events (DID + npub);
|
||||
// `handshake.connect` ships a NIP-44-encrypted PeerRequest with no onion;
|
||||
// `handshake.poll` queues inbound requests into the federation pending
|
||||
// inbox for manual approval (it does NOT auto-accept).
|
||||
|
||||
async nostrDiscoveryStatus(): Promise<{ enabled: boolean }> {
|
||||
return this.call({ method: 'nostr.discovery-status', params: {} })
|
||||
}
|
||||
|
||||
async nostrSetDiscovery(enabled: boolean): Promise<{ enabled: boolean }> {
|
||||
return this.call({
|
||||
method: 'nostr.set-discovery',
|
||||
params: { enabled },
|
||||
timeout: 30000,
|
||||
})
|
||||
}
|
||||
|
||||
async handshakeDiscover(): Promise<{
|
||||
nodes: Array<{
|
||||
nostr_pubkey: string
|
||||
nostr_npub: string
|
||||
did: string
|
||||
version: string
|
||||
}>
|
||||
}> {
|
||||
return this.call({ method: 'handshake.discover', params: {}, timeout: 30000 })
|
||||
}
|
||||
|
||||
async handshakeConnect(
|
||||
recipient: string,
|
||||
message?: string,
|
||||
name?: string,
|
||||
): Promise<{ ok: boolean; sent_to: string; id: string }> {
|
||||
return this.call({
|
||||
method: 'handshake.connect',
|
||||
params: {
|
||||
recipient_nostr_pubkey: recipient,
|
||||
...(message ? { message } : {}),
|
||||
...(name ? { name } : {}),
|
||||
},
|
||||
timeout: 30000,
|
||||
})
|
||||
}
|
||||
|
||||
async handshakePoll(): Promise<{
|
||||
polled: number
|
||||
new_requests: PendingPeerRequest[]
|
||||
applied_invites: string[]
|
||||
rejected_outbound: string[]
|
||||
skipped: string[]
|
||||
}> {
|
||||
return this.call({ method: 'handshake.poll', params: {}, timeout: 30000 })
|
||||
}
|
||||
|
||||
async federationListPendingRequests(): Promise<{
|
||||
requests: PendingPeerRequest[]
|
||||
}> {
|
||||
return this.call({ method: 'federation.list-pending-requests', params: {} })
|
||||
}
|
||||
|
||||
async federationApproveRequest(id: string): Promise<{ approved: boolean; id: string }> {
|
||||
return this.call({
|
||||
method: 'federation.approve-request',
|
||||
params: { id },
|
||||
timeout: 30000,
|
||||
})
|
||||
}
|
||||
|
||||
async federationRejectRequest(
|
||||
id: string,
|
||||
reason?: string,
|
||||
notify = false,
|
||||
): Promise<{ rejected: boolean; id: string }> {
|
||||
return this.call({
|
||||
method: 'federation.reject-request',
|
||||
params: {
|
||||
id,
|
||||
...(reason ? { reason } : {}),
|
||||
notify,
|
||||
},
|
||||
timeout: 30000,
|
||||
})
|
||||
}
|
||||
|
||||
async federationSyncState(): Promise<{
|
||||
synced: number
|
||||
failed: number
|
||||
|
||||
Reference in New Issue
Block a user